initial IMDSv2 implementation
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Emil Lerch 2022-02-08 22:37:38 -08:00
parent 628dd1da1d
commit 5f3a201ffd
Signed by: lobo
GPG Key ID: A7B62D657EF764F8

View File

@ -7,6 +7,7 @@
const std = @import("std");
const builtin = @import("builtin");
const auth = @import("aws_authentication.zig");
const zfetch = @import("zfetch");
const log = std.log.scoped(.aws_credentials);
@ -24,23 +25,29 @@ pub const Options = struct {
};
pub fn getCredentials(allocator: std.mem.Allocator, options: Options) !auth.Credentials {
if (try getEnvironmentCredentials(allocator)) |cred| return cred;
if (try getEnvironmentCredentials(allocator)) |cred| {
log.debug("Found credentials in environment. Access key: {s}", .{cred.access_key});
return cred;
}
// TODO: 2-5
// Note that boto and Java disagree on where this fits in the order
if (try getWebIdentityToken(allocator)) |cred| return cred;
if (try getProfileCredentials(allocator, options.profile)) |cred| return cred;
if (try getContainerCredentials(allocator)) |cred| return cred;
// I don't think we need v1 at all?
if (try getImdsv2Credentials(allocator)) |cred| return cred;
return error.NotImplemented;
}
fn getEnvironmentCredentials(allocator: std.mem.Allocator) !?auth.Credentials {
const secret_key = (try getEnvironmentVariable(allocator, "AWS_SECRET_ACCESS_KEY")) orelse return null;
defer allocator.free(secret_key); //yes, we're not zeroing. But then, the secret key is in an environment var anyway
const mutable_key = try allocator.dupe(u8, secret_key);
// Use cross-platform API (requires allocation)
return auth.Credentials.init(
allocator,
(try getEnvironmentVariable(allocator, "AWS_ACCESS_KEY_ID")) orelse return null,
mutable_key,
try allocator.dupe(u8, secret_key),
(try getEnvironmentVariable(allocator, "AWS_SESSION_TOKEN")) orelse
try getEnvironmentVariable(allocator, "AWS_SECURITY_TOKEN"), // Security token is backward compat only
);
@ -63,6 +70,178 @@ fn getWebIdentityToken(allocator: std.mem.Allocator) !?auth.Credentials {
// TODO: implement
return null;
}
fn getContainerCredentials(allocator: std.mem.Allocator) !?auth.Credentials {
_ = allocator;
return null;
}
fn getImdsv2Credentials(allocator: std.mem.Allocator) !?auth.Credentials {
try zfetch.init();
defer zfetch.deinit();
var token: [65535]u8 = undefined;
var len: usize = undefined;
// Get token
{
var headers = zfetch.Headers.init(allocator);
defer headers.deinit();
try headers.appendValue("X-aws-ec2-metadata-token-ttl-seconds", "21600");
var req = try zfetch.Request.init(allocator, "http://169.254.169.254/latest/api/token", null);
defer req.deinit();
try req.do(.PUT, headers, "");
if (req.status.code != 200) {
log.warn("Bad status code received from IMDS v2: {}", .{req.status.code});
return null;
}
const reader = req.reader();
const read = try reader.read(&token);
if (read == 0 or read == 65535) {
log.warn("Unexpected zero or long response from IMDS v2: {s}", .{token});
return null;
}
len = read;
}
log.debug("Got token from IMDSv2", .{});
const role_name = try getImdsRoleName(allocator, token[0..len]);
if (role_name == null) {
log.info("No role is associated with this instance", .{});
return null;
}
defer allocator.free(role_name.?);
log.debug("Got role name '{s}'", .{role_name});
return getImdsCredentials(allocator, role_name.?, token[0..len]);
}
fn getImdsRoleName(allocator: std.mem.Allocator, imds_token: []u8) !?[]const u8 {
// {
// "Code" : "Success",
// "LastUpdated" : "2022-02-09T05:42:09Z",
// "InstanceProfileArn" : "arn:aws:iam::550620852718:instance-profile/ec2-dev",
// "InstanceProfileId" : "AIPAYAM4POHXCFNKZ7HU2"
// }
var buf: [65535]u8 = undefined;
var headers = zfetch.Headers.init(allocator);
defer headers.deinit();
try headers.appendValue("X-aws-ec2-metadata-token", imds_token);
var req = try zfetch.Request.init(allocator, "http://169.254.169.254/latest/meta-data/iam/info", null);
defer req.deinit();
try req.do(.GET, headers, null);
if (req.status.code != 200 and req.status.code != 404) {
log.warn("Bad status code received from IMDS iam endpoint: {}", .{req.status.code});
return null;
}
if (req.status.code == 404) return null;
const reader = req.reader();
const read = try reader.read(&buf);
if (read == 65535) {
log.warn("Unexpected zero or long response from IMDS endpoint post token: {s}", .{buf});
return null;
}
if (read == 0) return null;
const ImdsResponse = struct {
Code: []const u8,
LastUpdated: []const u8,
InstanceProfileArn: []const u8,
InstanceProfileId: []const u8,
};
const imds_response = blk: {
var stream = std.json.TokenStream.init(buf[0..read]);
const res = std.json.parse(ImdsResponse, &stream, .{ .allocator = allocator }) catch |e| {
log.err("Unexpected Json response from IMDS 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(ImdsResponse, imds_response, .{ .allocator = allocator });
const role_arn = imds_response.InstanceProfileArn;
const first_slash = std.mem.indexOf(u8, role_arn, "/"); // I think this is valid
if (first_slash == null) {
log.err("Could not find role name in arn '{s}'", .{role_arn});
return null;
}
return try allocator.dupe(u8, role_arn[first_slash.? + 1 ..]);
}
/// 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 {
var buf: [65535]u8 = undefined;
var headers = zfetch.Headers.init(allocator);
defer headers.deinit();
try headers.appendValue("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 req = try zfetch.Request.init(allocator, url, null);
defer req.deinit();
try req.do(.GET, headers, null);
if (req.status.code != 200) {
log.warn("Bad status code received from IMDS role endpoint: {}", .{req.status.code});
return null;
}
const reader = req.reader();
const read = try reader.read(&buf);
if (read == 0 or read == 65535) {
log.warn("Unexpected zero or long response from IMDS role endpoint: {s}", .{buf});
return null;
}
const ImdsResponse = struct {
Code: []const u8,
LastUpdated: []const u8,
Type: []const u8,
AccessKeyId: []const u8,
SecretAccessKey: []const u8,
Token: []const u8,
Expiration: []const u8,
};
const imds_response = blk: {
var stream = std.json.TokenStream.init(buf[0..read]);
const res = std.json.parse(ImdsResponse, &stream, .{ .allocator = allocator }) catch |e| {
log.err("Unexpected Json response from IMDS 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(ImdsResponse, imds_response, .{ .allocator = allocator });
const ret = auth.Credentials.init(
allocator,
try allocator.dupe(u8, imds_response.AccessKeyId),
try allocator.dupe(u8, imds_response.SecretAccessKey),
try allocator.dupe(u8, imds_response.Token),
);
log.debug("IMDSv2 credentials found. Access key: {s}", .{ret.access_key});
return ret;
// {
// "Code" : "Success",
// "LastUpdated" : "2022-02-08T23:49:02Z",
// "Type" : "AWS-HMAC",
// "AccessKeyId" : "ASEXAMPLE",
// "SecretAccessKey" : "example",
// "Token" : "IQoJb==",
// "Expiration" : "2022-02-09T06:02:23Z"
// }
}
fn getProfileCredentials(allocator: std.mem.Allocator, options: Profile) !?auth.Credentials {
var default_path: ?[]const u8 = null;