implement container credentials
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Emil Lerch 2022-02-09 11:46:53 -08:00
parent 5f3a201ffd
commit 5bb382bda3
Signed by: lobo
GPG Key ID: A7B62D657EF764F8

View File

@ -29,15 +29,16 @@ pub fn getCredentials(allocator: std.mem.Allocator, options: Options) !auth.Cred
log.debug("Found credentials in environment. Access key: {s}", .{cred.access_key}); log.debug("Found credentials in environment. Access key: {s}", .{cred.access_key});
return cred; return cred;
} }
// TODO: 2-5
// Note that boto and Java disagree on where this fits in the order // Note that boto and Java disagree on where this fits in the order
// GetWebIdentity is not currently implemented. The rest are tested and gtg
// Note: Lambda just sets environment variables
if (try getWebIdentityToken(allocator)) |cred| return cred; if (try getWebIdentityToken(allocator)) |cred| return cred;
if (try getProfileCredentials(allocator, options.profile)) |cred| return cred; if (try getProfileCredentials(allocator, options.profile)) |cred| return cred;
if (try getContainerCredentials(allocator)) |cred| return cred; if (try getContainerCredentials(allocator)) |cred| return cred;
// I don't think we need v1 at all? // I don't think we need v1 at all?
if (try getImdsv2Credentials(allocator)) |cred| return cred; if (try getImdsv2Credentials(allocator)) |cred| return cred;
return error.NotImplemented; return error.CredentialsNotFound;
} }
fn getEnvironmentCredentials(allocator: std.mem.Allocator) !?auth.Credentials { fn getEnvironmentCredentials(allocator: std.mem.Allocator) !?auth.Credentials {
@ -71,15 +72,106 @@ fn getWebIdentityToken(allocator: std.mem.Allocator) !?auth.Credentials {
return null; return null;
} }
fn getContainerCredentials(allocator: std.mem.Allocator) !?auth.Credentials { fn getContainerCredentials(allocator: std.mem.Allocator) !?auth.Credentials {
_ = allocator; // A note on testing: The best way I have found to test this process is
// the following. Setup an ECS Fargate cluster and create a task definition
// with the command ["/bin/bash","-c","while true; do sleep 10; done"].
//
// In the console, this would be represented as:
//
// /bin/bash,-c,while true; do sleep 10; done
//
// Then we run the task with ECS exec-command enabled. The cli for this
// will look something like the following:
//
// aws ecs run-task --enable-execute-command \
// --cluster Fargate \
// --network-configuration "awsvpcConfiguration={subnets=[subnet-1f3f4278],securityGroups=[sg-0aab58c6b2bde2105],assignPublicIp=ENABLED}" \
// --launch-type FARGATE \
// --task-definition zig-demo:3
//
// Of course, subnets and security groups will be different. Public
// IP is necessary or you won't be able to pull the image. I used
// AL2 from the ECR public image:
//
// public.ecr.aws/amazonlinux/amazonlinux:latest
//
// With the task running, now we need to execute it. I used CloudShell
// from the AWS console because everything is already installed and
// configured, ymmv. You need AWS CLI v2 with the session manager extension.
//
// It's good to do a pre-flight check to make sure you can run the
// execute command. I used this tool to do so:
//
// https://github.com/aws-containers/amazon-ecs-exec-checker
//
// A couple yellows were ok, but no red.
//
// From there, get your task id and Bob's your uncle:
//
// aws ecs execute-command --cluster Fargate --command "/bin/bash" --interactive --task ec65b4d9887b429cba5d45ec70a8afa1
//
// Compile code, copy to S3, install AWS CLI within the session, download
// from s3 and run
const container_relative_uri = (try getEnvironmentVariable(allocator, "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")) orelse return null;
defer allocator.free(container_relative_uri);
try zfetch.init();
defer zfetch.deinit();
const container_uri = try std.fmt.allocPrint(allocator, "http://169.254.170.2{s}", .{container_relative_uri});
defer allocator.free(container_uri);
var req = try zfetch.Request.init(allocator, container_uri, null);
defer req.deinit();
try req.do(.GET, null, null);
if (req.status.code != 200 and req.status.code != 404) {
log.warn("Bad status code received from container credentials endpoint: {}", .{req.status.code});
return null; return null;
}
if (req.status.code == 404) return null;
const reader = req.reader();
var buf: [2048]u8 = undefined;
const read = try reader.read(&buf);
if (read == 2048) {
log.warn("Unexpected long response from container credentials endpoint: {s}", .{buf});
return null;
}
log.debug("Read {d} bytes from container credentials endpoint", .{read});
if (read == 0) return null;
const CredsResponse = struct {
AccessKeyId: []const u8,
Expiration: []const u8,
RoleArn: []const u8,
SecretAccessKey: []const u8,
Token: []const u8,
};
const creds_response = blk: {
var stream = std.json.TokenStream.init(buf[0..read]);
const res = std.json.parse(CredsResponse, &stream, .{ .allocator = allocator }) catch |e| {
log.err("Unexpected Json response from container credentials endpoint: {s}", .{buf});
log.err("Error parsing json: {}", .{e});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
return null;
};
break :blk res;
};
defer std.json.parseFree(CredsResponse, creds_response, .{ .allocator = allocator });
return auth.Credentials.init(
allocator,
try allocator.dupe(u8, creds_response.AccessKeyId),
try allocator.dupe(u8, creds_response.SecretAccessKey),
try allocator.dupe(u8, creds_response.Token),
);
} }
fn getImdsv2Credentials(allocator: std.mem.Allocator) !?auth.Credentials { fn getImdsv2Credentials(allocator: std.mem.Allocator) !?auth.Credentials {
try zfetch.init(); try zfetch.init();
defer zfetch.deinit(); defer zfetch.deinit();
var token: [65535]u8 = undefined; var token: [1024]u8 = undefined;
var len: usize = undefined; var len: usize = undefined;
// Get token // Get token
{ {
@ -96,7 +188,7 @@ fn getImdsv2Credentials(allocator: std.mem.Allocator) !?auth.Credentials {
} }
const reader = req.reader(); const reader = req.reader();
const read = try reader.read(&token); const read = try reader.read(&token);
if (read == 0 or read == 65535) { if (read == 0 or read == 1024) {
log.warn("Unexpected zero or long response from IMDS v2: {s}", .{token}); log.warn("Unexpected zero or long response from IMDS v2: {s}", .{token});
return null; return null;
} }
@ -120,7 +212,7 @@ fn getImdsRoleName(allocator: std.mem.Allocator, imds_token: []u8) !?[]const u8
// "InstanceProfileArn" : "arn:aws:iam::550620852718:instance-profile/ec2-dev", // "InstanceProfileArn" : "arn:aws:iam::550620852718:instance-profile/ec2-dev",
// "InstanceProfileId" : "AIPAYAM4POHXCFNKZ7HU2" // "InstanceProfileId" : "AIPAYAM4POHXCFNKZ7HU2"
// } // }
var buf: [65535]u8 = undefined; var buf: [255]u8 = undefined;
var headers = zfetch.Headers.init(allocator); var headers = zfetch.Headers.init(allocator);
defer headers.deinit(); defer headers.deinit();
try headers.appendValue("X-aws-ec2-metadata-token", imds_token); try headers.appendValue("X-aws-ec2-metadata-token", imds_token);
@ -137,7 +229,7 @@ fn getImdsRoleName(allocator: std.mem.Allocator, imds_token: []u8) !?[]const u8
if (req.status.code == 404) return null; if (req.status.code == 404) return null;
const reader = req.reader(); const reader = req.reader();
const read = try reader.read(&buf); const read = try reader.read(&buf);
if (read == 65535) { if (read == 255) {
log.warn("Unexpected zero or long response from IMDS endpoint post token: {s}", .{buf}); log.warn("Unexpected zero or long response from IMDS endpoint post token: {s}", .{buf});
return null; return null;
} }
@ -175,7 +267,7 @@ fn getImdsRoleName(allocator: std.mem.Allocator, imds_token: []u8) !?[]const u8
/// Note - this internal function assumes zfetch is initialized prior to use /// Note - this internal function assumes zfetch is initialized prior to use
fn getImdsCredentials(allocator: std.mem.Allocator, role_name: []const u8, imds_token: []u8) !?auth.Credentials { fn getImdsCredentials(allocator: std.mem.Allocator, role_name: []const u8, imds_token: []u8) !?auth.Credentials {
var buf: [65535]u8 = undefined; var buf: [2048]u8 = undefined;
var headers = zfetch.Headers.init(allocator); var headers = zfetch.Headers.init(allocator);
defer headers.deinit(); defer headers.deinit();
try headers.appendValue("X-aws-ec2-metadata-token", imds_token); try headers.appendValue("X-aws-ec2-metadata-token", imds_token);
@ -193,10 +285,11 @@ fn getImdsCredentials(allocator: std.mem.Allocator, role_name: []const u8, imds_
} }
const reader = req.reader(); const reader = req.reader();
const read = try reader.read(&buf); const read = try reader.read(&buf);
if (read == 0 or read == 65535) { if (read == 0 or read == 2048) {
log.warn("Unexpected zero or long response from IMDS role endpoint: {s}", .{buf}); log.warn("Unexpected zero or long response from IMDS role endpoint: {s}", .{buf});
return null; return null;
} }
// log.debug("Read {d} bytes from imds v2 credentials endpoint", .{read});
const ImdsResponse = struct { const ImdsResponse = struct {
Code: []const u8, Code: []const u8,
LastUpdated: []const u8, LastUpdated: []const u8,