Compare commits
5 Commits
25f92b741d
...
f56360d501
Author | SHA1 | Date | |
---|---|---|---|
f56360d501 | |||
ff81524caa | |||
4f0c608392 | |||
9d6527acf4 | |||
aa051b5220 |
106
build.zig
106
build.zig
|
@ -153,34 +153,29 @@ pub fn build(b: *std.Build) !void {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generateCredentials(s: *std.build.Step, prog_node: *std.Progress.Node) error{ MakeFailed, MakeSkipped }!void {
|
fn generateCredentials(s: *std.build.Step, prog_node: *std.Progress.Node) error{ MakeFailed, MakeSkipped }!void {
|
||||||
|
_ = s;
|
||||||
// Account id:
|
// Account id:
|
||||||
// Documentation describes account id as a 12 digit number:
|
// Documentation describes account id as a 12 digit number:
|
||||||
// https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-identifiers.html
|
// https://docs.aws.amazon.com/accounts/latest/reference/manage-acct-identifiers.html
|
||||||
// This can be a random u64, but must be in a 12 digit range, which
|
// This can be a random number, but must be in a 12 digit range.
|
||||||
// is:
|
|
||||||
//
|
//
|
||||||
// Min: 0x05f5e100 (0d100000000)
|
// The access key is 32 bit encoded, which leaves us with
|
||||||
// Max: 0x3b9ac9ff (0d999999999)
|
// 8 * 5 = 40 bits of information to work with. The maximum value of
|
||||||
|
// a u40 in decimal is 1099511627775, a 13 digit number. So our maximum
|
||||||
|
// decimal is below, and fits into u40.
|
||||||
|
//
|
||||||
|
// Min: 0x0000000000 (0d000000000000)
|
||||||
|
// Max: 0xe8d4a50fff (0d999999999999)
|
||||||
//
|
//
|
||||||
// Access key:
|
// Access key:
|
||||||
// Access key is 20 characters and can be represented by base36
|
// This page shows how the access key is put together:
|
||||||
// https://en.wikipedia.org/wiki/Base36
|
// https://medium.com/@TalBeerySec/a-short-note-on-aws-key-id-f88cc4317489
|
||||||
// (it is nearly definitely base36 in AWS in practice)
|
// tl;dr
|
||||||
// At least the first two characters are not part of the number...they
|
// * First 4 characters: designates type of key: We will use "ELAK" for access key
|
||||||
// have meaning. AK for a permanent key, AS for a session token.
|
// * Next 8 characters: Account ID, base32 encoded, shifted by one bit
|
||||||
// We shall use "EL" just...because. Maybe ET later for session tokens.
|
// * Next 8 characters: Unknown. Assume random base32, which would give us 8 * 5 = u40;
|
||||||
// This gives us 18 characters to work with, making our range like this:
|
|
||||||
//
|
//
|
||||||
// Min:
|
|
||||||
// NN100000000000000000 (hex: 0xECFF3BCC40CA2000000000)
|
|
||||||
// Max:
|
|
||||||
// NNZZZZZZZZZZZZZZZZZZ (hex: 0x2153E468B91C6E0000000000)
|
|
||||||
//
|
//
|
||||||
// The max value therefore requires a u96 to represent, as does the
|
|
||||||
// difference between max and min (0x2066e52cecdba40000000000). However,
|
|
||||||
// Zig 0.11.0 cannot handle random numbers that large
|
|
||||||
// (https://github.com/ziglang/zig/blob/0.11.0/lib/std/rand.zig#L145),
|
|
||||||
// so for now we use a random u64 and call it good.
|
|
||||||
//
|
//
|
||||||
// Secret Access Key:
|
// Secret Access Key:
|
||||||
// In the wild, these are 40 characters and appear to be base64 encoded.
|
// In the wild, these are 40 characters and appear to be base64 encoded.
|
||||||
|
@ -194,19 +189,16 @@ fn generateCredentials(s: *std.build.Step, prog_node: *std.Progress.Node) error{
|
||||||
const seed = @as(u64, @truncate(@as(u128, @bitCast(std.time.nanoTimestamp()))));
|
const seed = @as(u64, @truncate(@as(u128, @bitCast(std.time.nanoTimestamp()))));
|
||||||
var prng = std.rand.DefaultPrng.init(seed);
|
var prng = std.rand.DefaultPrng.init(seed);
|
||||||
var rand = prng.random();
|
var rand = prng.random();
|
||||||
const account_number = rand.intRangeAtMost(u64, 100000000000, 999999999999);
|
const account_number = rand.intRangeAtMost(u40, 0, 999999999999); // 100000000000, 999999999999);
|
||||||
const access_key_suffix: u128 = blk: { // workaround for u64 max on rand.intRangeAtMost
|
const access_key_random_suffix = rand.int(u39);
|
||||||
const min = 0xECFF3BCC40CA2000000000;
|
// We need the most significant bit as a 1 to make the key compatible with
|
||||||
// const max = 0x2153E468B91C6E0000000000;
|
// AWS. Like...you can literally send these keys to public AWS `aws sts get-access-key-info --access-key-id <blah>`
|
||||||
// const diff = max - min; // 0x2066e52cecdba40000000000 (is 12 bytes/96 bits)
|
// and get your account number (after changing ELAK to AKIA!
|
||||||
// So we can use a full 64 bit range and just add to the min
|
//
|
||||||
break :blk @as(u128, rand.int(u64)) + min;
|
// Without this bit set, AWS' sts will complain that this is not a valid key
|
||||||
};
|
const access_key_suffix: u80 = (1 << 79) | (@as(u80, account_number) << 39) + @as(u80, access_key_random_suffix);
|
||||||
const access_key_suffix_encoded = encode(
|
const access_key_suffix_encoded = base32Encode(u80, access_key_suffix);
|
||||||
u128,
|
// std.debug.assert(access_key_suffix_encoded.len == 16);
|
||||||
s.owner.allocator,
|
|
||||||
access_key_suffix,
|
|
||||||
) catch return error.MakeFailed;
|
|
||||||
var secret_key: [30]u8 = undefined;
|
var secret_key: [30]u8 = undefined;
|
||||||
rand.bytes(&secret_key); // The rest don't need to be cryptographically secure...does this?
|
rand.bytes(&secret_key); // The rest don't need to be cryptographically secure...does this?
|
||||||
var encoded_secret: [40]u8 = undefined;
|
var encoded_secret: [40]u8 = undefined;
|
||||||
|
@ -215,8 +207,20 @@ fn generateCredentials(s: *std.build.Step, prog_node: *std.Progress.Node) error{
|
||||||
const stdout_raw = std.io.getStdOut().writer();
|
const stdout_raw = std.io.getStdOut().writer();
|
||||||
var stdout_writer = std.io.bufferedWriter(stdout_raw);
|
var stdout_writer = std.io.bufferedWriter(stdout_raw);
|
||||||
const stdout = stdout_writer.writer();
|
const stdout = stdout_writer.writer();
|
||||||
|
// stdout.print(
|
||||||
|
// \\# account_number: {b:0>80}
|
||||||
|
// \\# random_suffix : {b:0>80}
|
||||||
|
// \\# access_key_suffix: {b:0>80}
|
||||||
|
// \\
|
||||||
|
// ,
|
||||||
|
// .{
|
||||||
|
// @as(u80, account_number) << 39,
|
||||||
|
// @as(u80, access_key_random_suffix),
|
||||||
|
// access_key_suffix,
|
||||||
|
// },
|
||||||
|
// ) catch return error.MakeFailed;
|
||||||
stdout.print(
|
stdout.print(
|
||||||
"# access_key: EL{s}, secret_key: {s}, account_number: {d}, db_encryption_key: {s}",
|
"# access_key: ELAK{s}, secret_key: {s}, account_number: {d:0>12}, db_encryption_key: {s}",
|
||||||
.{
|
.{
|
||||||
access_key_suffix_encoded,
|
access_key_suffix_encoded,
|
||||||
encoded_secret,
|
encoded_secret,
|
||||||
|
@ -225,7 +229,7 @@ fn generateCredentials(s: *std.build.Step, prog_node: *std.Progress.Node) error{
|
||||||
},
|
},
|
||||||
) catch return error.MakeFailed;
|
) catch return error.MakeFailed;
|
||||||
stdout.print(
|
stdout.print(
|
||||||
"\n#\n# You can copy/paste the following line into access_keys.csv:\nEL{s},{s}{d}{s}\n",
|
"\n#\n# You can copy/paste the following line into access_keys.csv:\nELAK{s},{s},{d:0>12},{s}\n",
|
||||||
.{
|
.{
|
||||||
access_key_suffix_encoded,
|
access_key_suffix_encoded,
|
||||||
encoded_secret,
|
encoded_secret,
|
||||||
|
@ -237,8 +241,9 @@ fn generateCredentials(s: *std.build.Step, prog_node: *std.Progress.Node) error{
|
||||||
}
|
}
|
||||||
|
|
||||||
/// encodes an unsigned integer into base36
|
/// encodes an unsigned integer into base36
|
||||||
pub fn encode(comptime T: type, allocator: std.mem.Allocator, data: T) ![]const u8 {
|
pub fn base36encode(comptime T: type, allocator: std.mem.Allocator, data: T) ![]const u8 {
|
||||||
const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
const alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
|
std.debug.assert(alphabet.len == 36);
|
||||||
const ti = @typeInfo(T);
|
const ti = @typeInfo(T);
|
||||||
if (ti != .Int or ti.Int.signedness != .unsigned)
|
if (ti != .Int or ti.Int.signedness != .unsigned)
|
||||||
@compileError("encode only works with unsigned integers");
|
@compileError("encode only works with unsigned integers");
|
||||||
|
@ -248,11 +253,36 @@ pub fn encode(comptime T: type, allocator: std.mem.Allocator, data: T) ![]const
|
||||||
defer al.deinit();
|
defer al.deinit();
|
||||||
|
|
||||||
var remaining = data;
|
var remaining = data;
|
||||||
while (remaining > 0) : (remaining /= 36) {
|
while (remaining > 0) : (remaining /= @as(T, @intCast(alphabet.len))) {
|
||||||
al.appendAssumeCapacity(alphabet[@as(usize, @intCast(remaining % 36))]);
|
al.appendAssumeCapacity(alphabet[@as(usize, @intCast(remaining % alphabet.len))]);
|
||||||
}
|
}
|
||||||
// This is not exact, but 6 bits
|
// This is not exact, but 6 bits
|
||||||
var rc = try al.toOwnedSlice();
|
var rc = try al.toOwnedSlice();
|
||||||
std.mem.reverse(u8, rc);
|
std.mem.reverse(u8, rc);
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Because Base32 is a power of 2, we can directly return an array and avoid
|
||||||
|
/// allocations entirely
|
||||||
|
/// To trim leading 0s, simply std.mem.trimLeft(u8, encoded_data, "A");
|
||||||
|
pub fn base32Encode(comptime T: type, data: T) [@typeInfo(T).Int.bits / 5]u8 {
|
||||||
|
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||||
|
std.debug.assert(alphabet.len == 32);
|
||||||
|
const ti = @typeInfo(T);
|
||||||
|
if (ti != .Int or ti.Int.signedness != .unsigned)
|
||||||
|
@compileError("encode only works with unsigned integers");
|
||||||
|
const bits = ti.Int.bits;
|
||||||
|
// We will have exactly 5 bits (2^5 = 32) represented per byte in our final output
|
||||||
|
var rc: [bits / 5]u8 = undefined;
|
||||||
|
var inx: usize = 0;
|
||||||
|
const Shift_type = @Type(.{ .Int = .{
|
||||||
|
.signedness = .unsigned,
|
||||||
|
.bits = @ceil(@log2(@as(f128, @floatFromInt(bits)))),
|
||||||
|
} });
|
||||||
|
// TODO: I think we need a table here to determine the size below
|
||||||
|
while (inx < rc.len) : (inx += 1) {
|
||||||
|
const char_bits: u5 = @as(u5, @truncate(data >> (@as(Shift_type, @intCast(inx * 5)))));
|
||||||
|
rc[rc.len - @as(usize, @intCast(inx)) - 1] = alphabet[@as(usize, @intCast(char_bits))]; // 5 bits from inx
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@
|
||||||
.hash = "12208c654deea149cee27eaa45d0e6515c3d8f97d775a4156cbcce0ff424b5d26ea3",
|
.hash = "12208c654deea149cee27eaa45d0e6515c3d8f97d775a4156cbcce0ff424b5d26ea3",
|
||||||
},
|
},
|
||||||
.universal_lambda_build = .{
|
.universal_lambda_build = .{
|
||||||
.url = "https://git.lerch.org/lobo/universal-lambda-zig/archive/e5a1099f741ddd6327e015e4c068de5c18d09393.tar.gz",
|
.url = "https://git.lerch.org/lobo/universal-lambda-zig/archive/5f1b1a52beea841e130ea4d878437f9488da0eb7.tar.gz",
|
||||||
.hash = "122037f0b35ab67002ef039410ae4ddb6805e14c111557ab0ae2ec7837211f7a1c51",
|
.hash = "12202e3f5cc4db196d9bef727e10b407413d6dd95a6e94d66f11c4c14dc5ee060b58",
|
||||||
},
|
},
|
||||||
.flexilib = .{
|
.flexilib = .{
|
||||||
.url = "https://git.lerch.org/lobo/flexilib/archive/3d3dab9c792651477932e2b61c9f4794ac694dcb.tar.gz",
|
.url = "https://git.lerch.org/lobo/flexilib/archive/3d3dab9c792651477932e2b61c9f4794ac694dcb.tar.gz",
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
const builtin = @import("builtin");
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const encryption = @import("encryption.zig");
|
const encryption = @import("encryption.zig");
|
||||||
const sqlite = @import("sqlite"); // TODO: If we use this across all services, Account should not have this, and we should have a localdbaccount struct
|
const sqlite = @import("sqlite"); // TODO: If we use this across all services, Account should not have this, and we should have a localdbaccount struct
|
||||||
|
@ -10,10 +11,10 @@ const Self = @This();
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator,
|
||||||
root_account_key: *[encryption.key_length]u8,
|
root_account_key: *[encryption.key_length]u8,
|
||||||
|
|
||||||
pub var root_key_mapping: ?std.StringHashMap([]const u8) = null;
|
pub var root_key_mapping: ?std.AutoHashMap(u40, []const u8) = null;
|
||||||
|
|
||||||
pub fn accountForId(allocator: std.mem.Allocator, account_id: []const u8) !Self {
|
pub fn accountForId(allocator: std.mem.Allocator, account_id: u40) !Self {
|
||||||
if (std.mem.eql(u8, account_id, "1234")) {
|
if (account_id == 1234) {
|
||||||
var key = try allocator.alloc(u8, encryption.key_length);
|
var key = try allocator.alloc(u8, encryption.key_length);
|
||||||
errdefer allocator.free(key);
|
errdefer allocator.free(key);
|
||||||
try encryption.decodeKey(key[0..encryption.key_length], test_account_key.*);
|
try encryption.decodeKey(key[0..encryption.key_length], test_account_key.*);
|
||||||
|
@ -37,7 +38,7 @@ pub fn accountForId(allocator: std.mem.Allocator, account_id: []const u8) !Self
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Check STS
|
// TODO: Check STS
|
||||||
log.err("Got account id '{s}', but could not find this ('1234' is test account). STS GetAccessKeyInfo not implemented", .{account_id});
|
log.err("Got account id '{d:0>12}', but could not find this ('1234' is test account). STS GetAccessKeyInfo not implemented", .{account_id});
|
||||||
return error.NotImplemented;
|
return error.NotImplemented;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,14 +60,13 @@ pub fn testDbDeinit() void {
|
||||||
}
|
}
|
||||||
/// Gets the database for this account. If under test, a memory database is used
|
/// Gets the database for this account. If under test, a memory database is used
|
||||||
/// instead. Will initialize the database with appropriate metadata tables
|
/// instead. Will initialize the database with appropriate metadata tables
|
||||||
pub fn dbForAccount(allocator: std.mem.Allocator, account_id: []const u8) !*sqlite.Db {
|
pub fn dbForAccount(allocator: std.mem.Allocator, account_id: u40) !*sqlite.Db {
|
||||||
const builtin = @import("builtin");
|
|
||||||
if (builtin.is_test and test_retain_db)
|
if (builtin.is_test and test_retain_db)
|
||||||
if (test_db) |db| return db;
|
if (test_db) |db| return db;
|
||||||
// TODO: Need to move this function somewhere central
|
// TODO: Need to move this function somewhere central
|
||||||
// TODO: Need configuration for what directory to use
|
// TODO: Need configuration for what directory to use
|
||||||
// TODO: Should this be a pool, and if so, how would we know when to close?
|
// TODO: Should this be a pool, and if so, how would we know when to close?
|
||||||
const file_without_path = try std.fmt.allocPrint(allocator, "ddb-{s}.sqlite3", .{account_id});
|
const file_without_path = try std.fmt.allocPrint(allocator, "ddb-{d:0>12}.sqlite3", .{account_id});
|
||||||
defer allocator.free(file_without_path);
|
defer allocator.free(file_without_path);
|
||||||
const db_file_name = try std.fs.path.joinZ(allocator, &[_][]const u8{ data_dir, file_without_path });
|
const db_file_name = try std.fs.path.joinZ(allocator, &[_][]const u8{ data_dir, file_without_path });
|
||||||
defer allocator.free(db_file_name);
|
defer allocator.free(db_file_name);
|
||||||
|
|
|
@ -5,7 +5,7 @@ event_data: []const u8,
|
||||||
headers: std.http.Headers,
|
headers: std.http.Headers,
|
||||||
status: std.http.Status,
|
status: std.http.Status,
|
||||||
reason: ?[]const u8,
|
reason: ?[]const u8,
|
||||||
account_id: []const u8,
|
account_id: u40,
|
||||||
output_format: OutputFormat,
|
output_format: OutputFormat,
|
||||||
|
|
||||||
pub const OutputFormat = enum {
|
pub const OutputFormat = enum {
|
||||||
|
|
|
@ -33,6 +33,13 @@ pub fn handler(request: *AuthenticatedRequest, writer: anytype) ![]const u8 {
|
||||||
var parsed = try std.json.parseFromSlice(std.json.Value, allocator, request.event_data, .{});
|
var parsed = try std.json.parseFromSlice(std.json.Value, allocator, request.event_data, .{});
|
||||||
defer parsed.deinit();
|
defer parsed.deinit();
|
||||||
const request_params = try parseRequest(request, parsed, writer);
|
const request_params = try parseRequest(request, parsed, writer);
|
||||||
|
defer {
|
||||||
|
for (request_params.table_info.attribute_definitions) |d| {
|
||||||
|
allocator.free(d.*.name);
|
||||||
|
allocator.destroy(d);
|
||||||
|
}
|
||||||
|
allocator.free(request_params.table_info.attribute_definitions);
|
||||||
|
}
|
||||||
// Parsing does most validation for us, but we also need to make sure that
|
// Parsing does most validation for us, but we also need to make sure that
|
||||||
// the attributes specified in the key schema actually exist
|
// the attributes specified in the key schema actually exist
|
||||||
var found_keys: u2 = if (request_params.table_info.range_key_attribute_name == null) 0b01 else 0b00;
|
var found_keys: u2 = if (request_params.table_info.range_key_attribute_name == null) 0b01 else 0b00;
|
||||||
|
@ -54,13 +61,6 @@ pub fn handler(request: *AuthenticatedRequest, writer: anytype) ![]const u8 {
|
||||||
writer,
|
writer,
|
||||||
"Attribute names in KeySchema must also exist in AttributeDefinitions",
|
"Attribute names in KeySchema must also exist in AttributeDefinitions",
|
||||||
);
|
);
|
||||||
defer {
|
|
||||||
for (request_params.table_info.attribute_definitions) |d| {
|
|
||||||
allocator.free(d.*.name);
|
|
||||||
allocator.destroy(d);
|
|
||||||
}
|
|
||||||
allocator.free(request_params.table_info.attribute_definitions);
|
|
||||||
}
|
|
||||||
var db = try Account.dbForAccount(allocator, account_id);
|
var db = try Account.dbForAccount(allocator, account_id);
|
||||||
defer allocator.destroy(db);
|
defer allocator.destroy(db);
|
||||||
defer db.deinit();
|
defer db.deinit();
|
||||||
|
@ -144,7 +144,7 @@ pub fn handler(request: *AuthenticatedRequest, writer: anytype) ![]const u8 {
|
||||||
|
|
||||||
var al = std.ArrayList(u8).init(allocator);
|
var al = std.ArrayList(u8).init(allocator);
|
||||||
var response_writer = al.writer();
|
var response_writer = al.writer();
|
||||||
try response_writer.print("table created for account {s}\n", .{account_id});
|
try response_writer.print("table created for account {d:0>12}\n", .{account_id});
|
||||||
return al.toOwnedSlice();
|
return al.toOwnedSlice();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
10
src/ddb.zig
10
src/ddb.zig
|
@ -482,7 +482,7 @@ pub const Table = struct {
|
||||||
/// are stored in here, realistically, this will be the first function called
|
/// are stored in here, realistically, this will be the first function called
|
||||||
/// every time anything interacts with the database, so this function opens
|
/// every time anything interacts with the database, so this function opens
|
||||||
/// the database for you
|
/// the database for you
|
||||||
pub fn tablesForAccount(allocator: std.mem.Allocator, account_id: []const u8) !AccountTables {
|
pub fn tablesForAccount(allocator: std.mem.Allocator, account_id: u40) !AccountTables {
|
||||||
|
|
||||||
// TODO: This function should take a list of table names, which can then be used
|
// TODO: This function should take a list of table names, which can then be used
|
||||||
// to filter the query below rather than just grabbing everything
|
// to filter the query below rather than just grabbing everything
|
||||||
|
@ -676,7 +676,7 @@ fn insertIntoDm(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn testCreateTable(allocator: std.mem.Allocator, account_id: []const u8) !*sqlite.Db {
|
fn testCreateTable(allocator: std.mem.Allocator, account_id: u40) !*sqlite.Db {
|
||||||
var db = try Account.dbForAccount(allocator, account_id);
|
var db = try Account.dbForAccount(allocator, account_id);
|
||||||
const account = try Account.accountForId(allocator, account_id); // This will get us the encryption key needed
|
const account = try Account.accountForId(allocator, account_id); // This will get us the encryption key needed
|
||||||
defer account.deinit();
|
defer account.deinit();
|
||||||
|
@ -707,7 +707,7 @@ fn testCreateTable(allocator: std.mem.Allocator, account_id: []const u8) !*sqlit
|
||||||
}
|
}
|
||||||
test "can create a table" {
|
test "can create a table" {
|
||||||
const allocator = std.testing.allocator;
|
const allocator = std.testing.allocator;
|
||||||
const account_id = "1234";
|
const account_id = 1234;
|
||||||
var db = try testCreateTable(allocator, account_id);
|
var db = try testCreateTable(allocator, account_id);
|
||||||
defer allocator.destroy(db);
|
defer allocator.destroy(db);
|
||||||
defer db.deinit();
|
defer db.deinit();
|
||||||
|
@ -715,7 +715,7 @@ test "can create a table" {
|
||||||
test "can list tables in an account" {
|
test "can list tables in an account" {
|
||||||
Account.test_retain_db = true;
|
Account.test_retain_db = true;
|
||||||
const allocator = std.testing.allocator;
|
const allocator = std.testing.allocator;
|
||||||
const account_id = "1234";
|
const account_id = 1234;
|
||||||
var db = try testCreateTable(allocator, account_id);
|
var db = try testCreateTable(allocator, account_id);
|
||||||
defer allocator.destroy(db);
|
defer allocator.destroy(db);
|
||||||
defer Account.testDbDeinit();
|
defer Account.testDbDeinit();
|
||||||
|
@ -729,7 +729,7 @@ test "can list tables in an account" {
|
||||||
test "can put an item in a table in an account" {
|
test "can put an item in a table in an account" {
|
||||||
Account.test_retain_db = true;
|
Account.test_retain_db = true;
|
||||||
const allocator = std.testing.allocator;
|
const allocator = std.testing.allocator;
|
||||||
const account_id = "1234";
|
const account_id = 1234;
|
||||||
var db = try testCreateTable(allocator, account_id);
|
var db = try testCreateTable(allocator, account_id);
|
||||||
defer allocator.destroy(db);
|
defer allocator.destroy(db);
|
||||||
defer Account.testDbDeinit();
|
defer Account.testDbDeinit();
|
||||||
|
|
101
src/main.zig
101
src/main.zig
|
@ -1,3 +1,4 @@
|
||||||
|
const builtin = @import("builtin");
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const universal_lambda = @import("universal_lambda_handler");
|
const universal_lambda = @import("universal_lambda_handler");
|
||||||
const universal_lambda_interface = @import("universal_lambda_interface");
|
const universal_lambda_interface = @import("universal_lambda_interface");
|
||||||
|
@ -24,24 +25,6 @@ pub fn main() !u8 {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handler(allocator: std.mem.Allocator, event_data: []const u8, context: universal_lambda_interface.Context) ![]const u8 {
|
pub fn handler(allocator: std.mem.Allocator, event_data: []const u8, context: universal_lambda_interface.Context) ![]const u8 {
|
||||||
const builtin = @import("builtin");
|
|
||||||
var rss: if (builtin.os.tag == .linux) std.os.rusage else usize = undefined;
|
|
||||||
if (builtin.os.tag == .linux and builtin.mode == .Debug)
|
|
||||||
rss = std.os.getrusage(std.os.rusage.SELF);
|
|
||||||
defer if (builtin.os.tag == .linux and builtin.mode == .Debug) { // and debug mode) {
|
|
||||||
const rusage = std.os.getrusage(std.os.rusage.SELF);
|
|
||||||
log.debug(
|
|
||||||
"Request complete, max RSS of process: {d}M. Incremental: {d}K, User: {d}μs, System: {d}μs",
|
|
||||||
.{
|
|
||||||
@divTrunc(rusage.maxrss, 1024),
|
|
||||||
rusage.maxrss - rss.maxrss,
|
|
||||||
(rusage.utime.tv_sec - rss.utime.tv_sec) * std.time.us_per_s +
|
|
||||||
rusage.utime.tv_usec - rss.utime.tv_usec,
|
|
||||||
(rusage.stime.tv_sec - rss.stime.tv_sec) * std.time.us_per_s +
|
|
||||||
rusage.stime.tv_usec - rss.stime.tv_usec,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const access_key = try allocator.dupe(u8, "ACCESS");
|
const access_key = try allocator.dupe(u8, "ACCESS");
|
||||||
const secret_key = try allocator.dupe(u8, "SECRET");
|
const secret_key = try allocator.dupe(u8, "SECRET");
|
||||||
test_credential = signing.Credentials.init(allocator, access_key, secret_key, null);
|
test_credential = signing.Credentials.init(allocator, access_key, secret_key, null);
|
||||||
|
@ -146,7 +129,7 @@ fn authenticateUser(allocator: std.mem.Allocator, context: universal_lambda_inte
|
||||||
|
|
||||||
var test_credential: signing.Credentials = undefined;
|
var test_credential: signing.Credentials = undefined;
|
||||||
var root_creds: std.StringHashMap(signing.Credentials) = undefined;
|
var root_creds: std.StringHashMap(signing.Credentials) = undefined;
|
||||||
var root_account_mapping: std.StringHashMap([]const u8) = undefined;
|
// var root_account_mapping: std.StringHashMap([]const u8) = undefined;
|
||||||
var creds_buf: [8192]u8 = undefined;
|
var creds_buf: [8192]u8 = undefined;
|
||||||
fn getCreds(access: []const u8) ?signing.Credentials {
|
fn getCreds(access: []const u8) ?signing.Credentials {
|
||||||
// We have 3 levels of access here
|
// We have 3 levels of access here
|
||||||
|
@ -163,8 +146,8 @@ fn getCreds(access: []const u8) ?signing.Credentials {
|
||||||
|
|
||||||
fn fillRootCreds(allocator: std.mem.Allocator) !void {
|
fn fillRootCreds(allocator: std.mem.Allocator) !void {
|
||||||
root_creds = std.StringHashMap(signing.Credentials).init(allocator);
|
root_creds = std.StringHashMap(signing.Credentials).init(allocator);
|
||||||
root_account_mapping = std.StringHashMap([]const u8).init(allocator);
|
// root_account_mapping = std.StringHashMap([]const u8).init(allocator);
|
||||||
Account.root_key_mapping = std.StringHashMap([]const u8).init(allocator);
|
Account.root_key_mapping = std.AutoHashMap(u40, []const u8).init(allocator);
|
||||||
var file = std.fs.cwd().openFile("access_keys.csv", .{}) catch |e| {
|
var file = std.fs.cwd().openFile("access_keys.csv", .{}) catch |e| {
|
||||||
log.err("Could not open access_keys.csv to access root creds: {}", .{e});
|
log.err("Could not open access_keys.csv to access root creds: {}", .{e});
|
||||||
return e;
|
return e;
|
||||||
|
@ -219,8 +202,9 @@ fn fillRootCreds(allocator: std.mem.Allocator) !void {
|
||||||
.session_token = null,
|
.session_token = null,
|
||||||
.allocator = NullAllocator.init(),
|
.allocator = NullAllocator.init(),
|
||||||
});
|
});
|
||||||
const global_account_id = try allocator.dupe(u8, account_id);
|
const global_account_id = try std.fmt.parseInt(u40, account_id, 10);
|
||||||
try root_account_mapping.put(global_access_key, global_account_id);
|
// unnecessary. Account ids are embedded in access keys!
|
||||||
|
// try root_account_mapping.put(global_access_key, global_account_id);
|
||||||
try Account.root_key_mapping.?.put(global_account_id, try allocator.dupe(u8, existing_key));
|
try Account.root_key_mapping.?.put(global_account_id, try allocator.dupe(u8, existing_key));
|
||||||
// TODO: key rotation will need another hash map, can be triggered on val_num == 5
|
// TODO: key rotation will need another hash map, can be triggered on val_num == 5
|
||||||
|
|
||||||
|
@ -270,19 +254,21 @@ const NullAllocator = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fn accountForAccessKey(allocator: std.mem.Allocator, access_key: []const u8) ![]const u8 {
|
fn accountForAccessKey(allocator: std.mem.Allocator, access_key: []const u8) !u40 {
|
||||||
_ = allocator;
|
_ = allocator;
|
||||||
log.debug("Finding account for access key: '{s}'", .{access_key});
|
log.debug("Finding account for access key: '{s}'", .{access_key});
|
||||||
|
if (access_key.len != 20) return error.InvalidAccessKey;
|
||||||
|
return try accountIdForAccessKey(@as(*[20]u8, @ptrCast(@constCast(access_key))).*);
|
||||||
// Since this happens after authentication, we can assume our root creds store
|
// Since this happens after authentication, we can assume our root creds store
|
||||||
// is populated
|
// is populated
|
||||||
if (root_account_mapping.get(access_key)) |account| return account;
|
// if (root_account_mapping.get(access_key)) |account| return account;
|
||||||
log.err("Creds not found in store. STS GetAccessKeyInfo call is not yet implemented", .{});
|
// log.err("Creds not found in store. STS GetAccessKeyInfo call is not yet implemented", .{});
|
||||||
return error.NotImplemented;
|
// return error.NotImplemented;
|
||||||
}
|
}
|
||||||
/// Function assumes an authenticated request, so signing.verify must be called
|
/// Function assumes an authenticated request, so signing.verify must be called
|
||||||
/// and returned true before calling this function. If authentication header
|
/// and returned true before calling this function. If authentication header
|
||||||
/// is not found, environment variable will be used
|
/// is not found, environment variable will be used
|
||||||
fn accountId(allocator: std.mem.Allocator, headers: std.http.Headers) ![]const u8 {
|
fn accountId(allocator: std.mem.Allocator, headers: std.http.Headers) !u40 {
|
||||||
const auth_header = headers.getFirstValue("Authorization");
|
const auth_header = headers.getFirstValue("Authorization");
|
||||||
if (auth_header) |h| {
|
if (auth_header) |h| {
|
||||||
// AWS4-HMAC-SHA256 Credential=ACCESS/20230908/us-west-2/s3/aws4_request, SignedHeaders=accept;content-length;content-type;host;x-amz-content-sha256;x-amz-date;x-amz-storage-class, Signature=fcc43ce73a34c9bd1ddf17e8a435f46a859812822f944f9eeb2aabcd64b03523
|
// AWS4-HMAC-SHA256 Credential=ACCESS/20230908/us-west-2/s3/aws4_request, SignedHeaders=accept;content-length;content-type;host;x-amz-content-sha256;x-amz-date;x-amz-storage-class, Signature=fcc43ce73a34c9bd1ddf17e8a435f46a859812822f944f9eeb2aabcd64b03523
|
||||||
|
@ -326,8 +312,8 @@ fn iamCredentials(allocator: std.mem.Allocator) ![]const u8 {
|
||||||
iam_credential = signing.Credentials.init(allocator, try iamAccessKey(allocator), try iamSecretKey(allocator), null);
|
iam_credential = signing.Credentials.init(allocator, try iamAccessKey(allocator), try iamSecretKey(allocator), null);
|
||||||
return iam_credential.?;
|
return iam_credential.?;
|
||||||
}
|
}
|
||||||
fn iamAccountId(allocator: std.mem.Allocator) ![]const u8 {
|
fn iamAccountId(allocator: std.mem.Allocator) !u40 {
|
||||||
return try getVariable(allocator, &iam_account_id, "IAM_ACCOUNT_ID");
|
return std.fmt.parseInt(u40, try getVariable(allocator, &iam_account_id, "IAM_ACCOUNT_ID"), 10);
|
||||||
}
|
}
|
||||||
fn iamAccessKey(allocator: std.mem.Allocator) ![]const u8 {
|
fn iamAccessKey(allocator: std.mem.Allocator) ![]const u8 {
|
||||||
return try getVariable(allocator, &iam_access_key, "IAM_ACCESS_KEY");
|
return try getVariable(allocator, &iam_access_key, "IAM_ACCESS_KEY");
|
||||||
|
@ -346,3 +332,58 @@ test {
|
||||||
std.testing.refAllDecls(@import("batchwriteitem.zig"));
|
std.testing.refAllDecls(@import("batchwriteitem.zig"));
|
||||||
std.testing.refAllDecls(@import("batchgetitem.zig"));
|
std.testing.refAllDecls(@import("batchgetitem.zig"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "can get account id from access key" {
|
||||||
|
// ELAKM5YGIGQQAD2B54IZ, Account 888534479904
|
||||||
|
// Also, https://medium.com/@TalBeerySec/a-short-note-on-aws-key-id-f88cc4317489
|
||||||
|
// aws_access_key_id: ASIAY34FZKBOKMUTVV7A yields the expected account id "609629065308"
|
||||||
|
try std.testing.expectEqual(@as(u40, 609629065308), try accountIdForAccessKey(@as(*[20]u8, @ptrCast(@constCast("ASIAY34FZKBOKMUTVV7A"))).*));
|
||||||
|
try std.testing.expectEqual(@as(u40, 888534479904), try accountIdForAccessKey(@as(*[20]u8, @ptrCast(@constCast("ELAKM5YGIGQQAD2B54IZ"))).*));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accountIdForAccessKey(access_key: [20]u8) !u40 {
|
||||||
|
const ak_integer_part = access_key[4..];
|
||||||
|
const ak_integer = try base32Decode(u80, @as(*[16]u8, @ptrCast(@constCast(ak_integer_part.ptr))).*);
|
||||||
|
const account_id = ak_integer >> 39;
|
||||||
|
return @as(u40, @truncate(account_id));
|
||||||
|
// Do we want an array like this? Probably so
|
||||||
|
// import base64
|
||||||
|
// import binascii
|
||||||
|
//
|
||||||
|
// def AWSAccount_from_AWSKeyID(AWSKeyID):
|
||||||
|
//
|
||||||
|
// trimmed_AWSKeyID = AWSKeyID[4:] #remove KeyID prefix
|
||||||
|
// x = base64.b32decode(trimmed_AWSKeyID) #base32 decode
|
||||||
|
// y = x[0:6]
|
||||||
|
//
|
||||||
|
// z = int.from_bytes(y, byteorder='big', signed=False)
|
||||||
|
// mask = int.from_bytes(binascii.unhexlify(b'7fffffffff80'), byteorder='big', signed=False)
|
||||||
|
//
|
||||||
|
// e = (z & mask)>>7
|
||||||
|
// return (e)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn base32Decode(comptime T: type, data: [@typeInfo(T).Int.bits / 5]u8) !T {
|
||||||
|
// const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||||
|
const ti = @typeInfo(T);
|
||||||
|
if (ti != .Int or ti.Int.signedness != .unsigned)
|
||||||
|
@compileError("decode only works with unsigned integers");
|
||||||
|
if (ti.Int.bits % 5 != 0)
|
||||||
|
@compileError("unsigned integer bit length must be a multiple of 5 to use this function");
|
||||||
|
const Shift_type = @Type(.{ .Int = .{
|
||||||
|
.signedness = .unsigned,
|
||||||
|
.bits = @ceil(@log2(@as(f128, @floatFromInt(ti.Int.bits)))),
|
||||||
|
} });
|
||||||
|
var rc: T = 0;
|
||||||
|
for (data, 0..) |b, i| {
|
||||||
|
var curr: T = 0;
|
||||||
|
if (b >= 'A' and b <= 'Z') {
|
||||||
|
curr = b - 'A';
|
||||||
|
} else if (b >= '2' and b <= '7') {
|
||||||
|
curr = b - '2' + 26;
|
||||||
|
} else return error.InvalidCharacter;
|
||||||
|
curr <<= @as(Shift_type, @intCast((data.len - 1 - i) * 5));
|
||||||
|
rc |= curr;
|
||||||
|
}
|
||||||
|
return rc;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user