Skip to content

Commit

Permalink
upgrade to nominated zig 2024.3.0-mach (0.12.0-dev.3180+83e578a18)
Browse files Browse the repository at this point in the history
There were significant changes to the way HTTP operates since 0.11,
effecting client operations, but more substantially, the server
implementation, which effected the test harness.

std.http.Headers was removed, including the getFirstValue function, which
needed to be replicated. On the plus side, a std.http.Header struct was
added, identical to our own structure, so I have removed out own header
in favor of stdlib.

On the Http client side, I have switched to use the fetch API. Proxy
support is built in, but we are using (mostly) our own implementation
for now, with the remaining conversion left as a TODO item. Raw URIs are
now supported, so the workaround for issue 17015 has been removed. Large
payloads should also be fixed, but this has not been tested.

The standard library now adds the content-length header
(unconditionally), which is a decision of dubious nature. I have removed
the addition of content-length, which also means it is not present
during signing. This should be allowed.

Dependency loop on fieldTransformer was fixed. This should have been
a problem on zig 0.11, but was not. This effected the API for the json
parsing, but we were not using that. At the call site, these did not
need to be specified as references.

With the http server no longer doing all the allocations it once was,
the test harness now has a lot more allocations to perform. To alleviate
the bookeeping, this was moved to an Arena allocator. The client,
which is really what is under test, continues to use the allocator
passed.
  • Loading branch information
elerch committed Apr 2, 2024
1 parent d442671 commit 7dcf3d3
Show file tree
Hide file tree
Showing 11 changed files with 395 additions and 569 deletions.
296 changes: 141 additions & 155 deletions src/aws.zig

Large diffs are not rendered by default.

129 changes: 58 additions & 71 deletions src/aws_credentials.zig
Original file line number Diff line number Diff line change
Expand Up @@ -122,29 +122,22 @@ fn getContainerCredentials(allocator: std.mem.Allocator) !?auth.Credentials {
const container_uri = try std.fmt.allocPrint(allocator, "http://169.254.170.2{s}", .{container_relative_uri});
defer allocator.free(container_uri);

var empty_headers = std.http.Headers.init(allocator);
defer empty_headers.deinit();
var cl = std.http.Client{ .allocator = allocator };
defer cl.deinit(); // I don't belive connection pooling would help much here as it's non-ssl and local
var req = try cl.request(.GET, try std.Uri.parse(container_uri), empty_headers, .{});
defer req.deinit();
try req.start();
try req.wait();
if (req.response.status != .ok and req.response.status != .not_found) {
log.warn("Bad status code received from container credentials endpoint: {}", .{@intFromEnum(req.response.status)});
var resp_payload = std.ArrayList(u8).init(allocator);
defer resp_payload.deinit();
const req = try cl.fetch(.{
.location = .{ .url = container_uri },
.response_storage = .{ .dynamic = &resp_payload },
});
if (req.status != .ok and req.status != .not_found) {
log.warn("Bad status code received from container credentials endpoint: {}", .{@intFromEnum(req.status)});
return null;
}
if (req.response.status == .not_found) return null;
if (req.response.content_length == null or req.response.content_length.? == 0) return null;
if (req.status == .not_found) return null;

var resp_payload = try std.ArrayList(u8).initCapacity(allocator, @intCast(req.response.content_length.?));
defer resp_payload.deinit();
try resp_payload.resize(@intCast(req.response.content_length.?));
const response_data = try resp_payload.toOwnedSlice();
defer allocator.free(response_data);
_ = try req.readAll(response_data);
log.debug("Read {d} bytes from container credentials endpoint", .{response_data.len});
if (response_data.len == 0) return null;
log.debug("Read {d} bytes from container credentials endpoint", .{resp_payload.items.len});
if (resp_payload.items.len == 0) return null;

const CredsResponse = struct {
AccessKeyId: []const u8,
Expand All @@ -154,8 +147,8 @@ fn getContainerCredentials(allocator: std.mem.Allocator) !?auth.Credentials {
Token: []const u8,
};
const creds_response = blk: {
const res = std.json.parseFromSlice(CredsResponse, allocator, response_data, .{}) catch |e| {
log.err("Unexpected Json response from container credentials endpoint: {s}", .{response_data});
const res = std.json.parseFromSlice(CredsResponse, allocator, resp_payload.items, .{}) catch |e| {
log.err("Unexpected Json response from container credentials endpoint: {s}", .{resp_payload.items});
log.err("Error parsing json: {}", .{e});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
Expand All @@ -182,28 +175,27 @@ fn getImdsv2Credentials(allocator: std.mem.Allocator) !?auth.Credentials {
defer cl.deinit(); // I don't belive connection pooling would help much here as it's non-ssl and local
// Get token
{
var headers = std.http.Headers.init(allocator);
defer headers.deinit();
try headers.append("X-aws-ec2-metadata-token-ttl-seconds", "21600");
var req = try cl.request(.PUT, try std.Uri.parse("http://169.254.169.254/latest/api/token"), headers, .{});
defer req.deinit();
try req.start();
try req.wait();
if (req.response.status != .ok) {
log.warn("Bad status code received from IMDS v2: {}", .{@intFromEnum(req.response.status)});
var resp_payload = std.ArrayList(u8).init(allocator);
defer resp_payload.deinit();
const req = try cl.fetch(.{
.method = .PUT,
.location = .{ .url = "http://169.254.169.254/latest/api/token" },
.extra_headers = &[_]std.http.Header{
.{ .name = "X-aws-ec2-metadata-token-ttl-seconds", .value = "21600" },
},
.response_storage = .{ .dynamic = &resp_payload },
});
if (req.status != .ok) {
log.warn("Bad status code received from IMDS v2: {}", .{@intFromEnum(req.status)});
return null;
}
if (req.response.content_length == null or req.response.content_length == 0) {
if (resp_payload.items.len == 0) {
log.warn("Unexpected zero response from IMDS v2", .{});
return null;
}

var resp_payload = try std.ArrayList(u8).initCapacity(allocator, @intCast(req.response.content_length.?));
defer resp_payload.deinit();
try resp_payload.resize(@intCast(req.response.content_length.?));
token = try resp_payload.toOwnedSlice();
errdefer if (token) |t| allocator.free(t);
_ = try req.readAll(token.?);
}
std.debug.assert(token != null);
log.debug("Got token from IMDSv2: {s}", .{token.?});
Expand All @@ -224,37 +216,35 @@ fn getImdsRoleName(allocator: std.mem.Allocator, client: *std.http.Client, imds_
// "InstanceProfileArn" : "arn:aws:iam::550620852718:instance-profile/ec2-dev",
// "InstanceProfileId" : "AIPAYAM4POHXCFNKZ7HU2"
// }
var headers = std.http.Headers.init(allocator);
defer headers.deinit();
try headers.append("X-aws-ec2-metadata-token", imds_token);

var req = try client.request(.GET, try std.Uri.parse("http://169.254.169.254/latest/meta-data/iam/info"), headers, .{});
defer req.deinit();

try req.start();
try req.wait();
var resp_payload = std.ArrayList(u8).init(allocator);
defer resp_payload.deinit();
const req = try client.fetch(.{
.method = .GET,
.location = .{ .url = "http://169.254.169.254/latest/meta-data/iam/info" },
.extra_headers = &[_]std.http.Header{
.{ .name = "X-aws-ec2-metadata-token", .value = imds_token },
},
.response_storage = .{ .dynamic = &resp_payload },
});

if (req.response.status != .ok and req.response.status != .not_found) {
log.warn("Bad status code received from IMDS iam endpoint: {}", .{@intFromEnum(req.response.status)});
if (req.status != .ok and req.status != .not_found) {
log.warn("Bad status code received from IMDS iam endpoint: {}", .{@intFromEnum(req.status)});
return null;
}
if (req.response.status == .not_found) return null;
if (req.response.content_length == null or req.response.content_length.? == 0) {
if (req.status == .not_found) return null;
if (resp_payload.items.len == 0) {
log.warn("Unexpected empty response from IMDS endpoint post token", .{});
return null;
}
const resp = try allocator.alloc(u8, @intCast(req.response.content_length.?));
defer allocator.free(resp);
_ = try req.readAll(resp);

const ImdsResponse = struct {
Code: []const u8,
LastUpdated: []const u8,
InstanceProfileArn: []const u8,
InstanceProfileId: []const u8,
};
const imds_response = std.json.parseFromSlice(ImdsResponse, allocator, resp, .{}) catch |e| {
log.err("Unexpected Json response from IMDS endpoint: {s}", .{resp});
const imds_response = std.json.parseFromSlice(ImdsResponse, allocator, resp_payload.items, .{}) catch |e| {
log.err("Unexpected Json response from IMDS endpoint: {s}", .{resp_payload.items});
log.err("Error parsing json: {}", .{e});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
Expand All @@ -274,31 +264,28 @@ fn getImdsRoleName(allocator: std.mem.Allocator, client: *std.http.Client, imds_

/// Note - this internal function assumes zfetch is initialized prior to use
fn getImdsCredentials(allocator: std.mem.Allocator, client: *std.http.Client, role_name: []const u8, imds_token: []u8) !?auth.Credentials {
var headers = std.http.Headers.init(allocator);
defer headers.deinit();
try headers.append("X-aws-ec2-metadata-token", imds_token);

const url = try std.fmt.allocPrint(allocator, "http://169.254.169.254/latest/meta-data/iam/security-credentials/{s}/", .{role_name});
defer allocator.free(url);
var resp_payload = std.ArrayList(u8).init(allocator);
defer resp_payload.deinit();
const req = try client.fetch(.{
.method = .GET,
.location = .{ .url = url },
.extra_headers = &[_]std.http.Header{
.{ .name = "X-aws-ec2-metadata-token", .value = imds_token },
},
.response_storage = .{ .dynamic = &resp_payload },
});

var req = try client.request(.GET, try std.Uri.parse(url), headers, .{});
defer req.deinit();

try req.start();
try req.wait();

if (req.response.status != .ok and req.response.status != .not_found) {
log.warn("Bad status code received from IMDS role endpoint: {}", .{@intFromEnum(req.response.status)});
if (req.status != .ok and req.status != .not_found) {
log.warn("Bad status code received from IMDS role endpoint: {}", .{@intFromEnum(req.status)});
return null;
}
if (req.response.status == .not_found) return null;
if (req.response.content_length == null or req.response.content_length.? == 0) {
if (req.status == .not_found) return null;
if (resp_payload.items.len == 0) {
log.warn("Unexpected empty response from IMDS role endpoint", .{});
return null;
}
const resp = try allocator.alloc(u8, @intCast(req.response.content_length.?));
defer allocator.free(resp);
_ = try req.readAll(resp);

// log.debug("Read {d} bytes from imds v2 credentials endpoint", .{read});
const ImdsResponse = struct {
Expand All @@ -310,8 +297,8 @@ fn getImdsCredentials(allocator: std.mem.Allocator, client: *std.http.Client, ro
Token: []const u8,
Expiration: []const u8,
};
const imds_response = std.json.parseFromSlice(ImdsResponse, allocator, resp, .{}) catch |e| {
log.err("Unexpected Json response from IMDS endpoint: {s}", .{resp});
const imds_response = std.json.parseFromSlice(ImdsResponse, allocator, resp_payload.items, .{}) catch |e| {
log.err("Unexpected Json response from IMDS endpoint: {s}", .{resp_payload.items});
log.err("Error parsing json: {}", .{e});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
Expand Down
Loading

0 comments on commit 7dcf3d3

Please sign in to comment.