add s3 exception for virtual host addressing

This commit is contained in:
Emil Lerch 2022-05-29 14:12:01 -07:00
parent c531164cfa
commit b9aaffb08d
Signed by: lobo
GPG Key ID: A7B62D657EF764F8

View File

@ -56,11 +56,13 @@ const EndPoint = struct {
host: []const u8, host: []const u8,
scheme: []const u8, scheme: []const u8,
port: u16, port: u16,
path: []const u8,
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
fn deinit(self: EndPoint) void { fn deinit(self: EndPoint) void {
self.allocator.free(self.uri); self.allocator.free(self.uri);
self.allocator.free(self.host); self.allocator.free(self.host);
self.allocator.free(self.path);
} }
}; };
pub const AwsHttp = struct { pub const AwsHttp = struct {
@ -112,7 +114,7 @@ pub const AwsHttp = struct {
// S3 control uses <account-id>.s3-control.<region>.amazonaws.com // S3 control uses <account-id>.s3-control.<region>.amazonaws.com
// //
// So this regionSubDomain call needs to handle generic customization // So this regionSubDomain call needs to handle generic customization
const endpoint = try endpointForRequest(self.allocator, service, options.region, options.dualstack); const endpoint = try endpointForRequest(self.allocator, service, request, options);
defer endpoint.deinit(); defer endpoint.deinit();
log.debug("Calling endpoint {s}", .{endpoint.uri}); log.debug("Calling endpoint {s}", .{endpoint.uri});
// TODO: Should we allow customization here? // TODO: Should we allow customization here?
@ -145,12 +147,17 @@ pub const AwsHttp = struct {
pub fn makeRequest(self: Self, endpoint: EndPoint, request: HttpRequest, signing_config: ?signing.Config) !HttpResult { pub fn makeRequest(self: Self, endpoint: EndPoint, request: HttpRequest, signing_config: ?signing.Config) !HttpResult {
var request_cp = request; var request_cp = request;
log.debug("Path: {s}", .{request_cp.path}); log.debug("Request Path: {s}", .{request_cp.path});
log.debug("Endpoint Path (actually used): {s}", .{endpoint.path});
log.debug("Query: {s}", .{request_cp.query}); log.debug("Query: {s}", .{request_cp.query});
log.debug("Method: {s}", .{request_cp.method}); log.debug("Method: {s}", .{request_cp.method});
log.debug("body length: {d}", .{request_cp.body.len}); log.debug("body length: {d}", .{request_cp.body.len});
log.debug("Body\n====\n{s}\n====", .{request_cp.body}); log.debug("Body\n====\n{s}\n====", .{request_cp.body});
// Endpoint calculation might be different from the request (e.g. S3 requests)
// We will use endpoint instead
request_cp.path = endpoint.path;
var request_headers = std.ArrayList(base.Header).init(self.allocator); var request_headers = std.ArrayList(base.Header).init(self.allocator);
defer request_headers.deinit(); defer request_headers.deinit();
@ -176,7 +183,7 @@ pub const AwsHttp = struct {
log.debug("\t{s}: {s}", .{ h.name, h.value }); log.debug("\t{s}: {s}", .{ h.name, h.value });
} }
const url = try std.fmt.allocPrint(self.allocator, "{s}{s}{s}", .{ endpoint.uri, request.path, request.query }); const url = try std.fmt.allocPrint(self.allocator, "{s}{s}{s}", .{ endpoint.uri, request_cp.path, request_cp.query });
defer self.allocator.free(url); defer self.allocator.free(url);
log.debug("Request url: {s}", .{url}); log.debug("Request url: {s}", .{url});
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
@ -253,32 +260,26 @@ fn getEnvironmentVariable(allocator: std.mem.Allocator, key: []const u8) !?[]con
}; };
} }
fn endpointForRequest(allocator: std.mem.Allocator, service: []const u8, region: []const u8, use_dual_stack: bool) !EndPoint { fn endpointForRequest(allocator: std.mem.Allocator, service: []const u8, request: HttpRequest, options: Options) !EndPoint {
const environment_override = try getEnvironmentVariable(allocator, "AWS_ENDPOINT_URL"); const environment_override = try getEnvironmentVariable(allocator, "AWS_ENDPOINT_URL");
if (environment_override) |override| { if (environment_override) |override| {
const uri = try allocator.dupeZ(u8, override); const uri = try allocator.dupeZ(u8, override);
return endPointFromUri(allocator, uri); return endPointFromUri(allocator, uri);
} }
if (std.mem.eql(u8, service, "cloudfront")) {
return EndPoint{
.uri = try allocator.dupe(u8, "https://cloudfront.amazonaws.com"),
.host = try allocator.dupe(u8, "cloudfront.amazonaws.com"),
.scheme = "https",
.port = 443,
.allocator = allocator,
};
}
// Fallback to us-east-1 if global endpoint does not exist. // Fallback to us-east-1 if global endpoint does not exist.
const realregion = if (std.mem.eql(u8, region, "aws-global")) "us-east-1" else region; const realregion = if (std.mem.eql(u8, options.region, "aws-global")) "us-east-1" else options.region;
const dualstack = if (use_dual_stack) ".dualstack" else ""; const dualstack = if (options.dualstack) ".dualstack" else "";
const domain = switch (std.hash_map.hashString(region)) { const domain = switch (std.hash_map.hashString(options.region)) {
US_ISO_EAST_1_HASH => "c2s.ic.gov", US_ISO_EAST_1_HASH => "c2s.ic.gov",
CN_NORTH_1_HASH, CN_NORTHWEST_1_HASH => "amazonaws.com.cn", CN_NORTH_1_HASH, CN_NORTHWEST_1_HASH => "amazonaws.com.cn",
US_ISOB_EAST_1_HASH => "sc2s.sgov.gov", US_ISOB_EAST_1_HASH => "sc2s.sgov.gov",
else => "amazonaws.com", else => "amazonaws.com",
}; };
if (try endpointException(allocator, service, request, options, realregion, dualstack, domain)) |e|
return e;
const uri = try std.fmt.allocPrintZ(allocator, "https://{s}{s}.{s}.{s}", .{ service, dualstack, realregion, domain }); const uri = try std.fmt.allocPrintZ(allocator, "https://{s}{s}.{s}.{s}", .{ service, dualstack, realregion, domain });
const host = try allocator.dupe(u8, uri["https://".len..]); const host = try allocator.dupe(u8, uri["https://".len..]);
log.debug("host: {s}, scheme: {s}, port: {}", .{ host, "https", 443 }); log.debug("host: {s}, scheme: {s}, port: {}", .{ host, "https", 443 });
@ -288,9 +289,68 @@ fn endpointForRequest(allocator: std.mem.Allocator, service: []const u8, region:
.scheme = "https", .scheme = "https",
.port = 443, .port = 443,
.allocator = allocator, .allocator = allocator,
.path = try allocator.dupe(u8, request.path),
}; };
} }
fn endpointException(
allocator: std.mem.Allocator,
service: []const u8,
request: HttpRequest,
options: Options,
realregion: []const u8,
dualstack: []const u8,
domain: []const u8,
) !?EndPoint {
if (std.mem.eql(u8, service, "cloudfront")) {
return EndPoint{
.uri = try allocator.dupe(u8, "https://cloudfront.amazonaws.com"),
.host = try allocator.dupe(u8, "cloudfront.amazonaws.com"),
.scheme = "https",
.port = 443,
.allocator = allocator,
.path = try allocator.dupe(u8, request.path),
};
}
if (std.mem.eql(u8, service, "s3")) {
if (request.path.len == 1 or std.mem.indexOf(u8, request.path[1..], "/") == null)
return null;
// We need to adjust the host and the path to accomodate virtual
// host addressing. This only applies to bucket operations, but
// right now I'm hoping that bucket operations do not include a path
// component, so will be handled by the return null statement above.
const bucket_name = s3BucketFromPath(request.path);
const rest_of_path = request.path[bucket_name.len + 1 ..];
// TODO: Implement
_ = options;
const uri = try std.fmt.allocPrintZ(allocator, "https://{s}.{s}{s}.{s}.{s}", .{ bucket_name, service, dualstack, realregion, domain });
const host = try allocator.dupe(u8, uri["https://".len..]);
log.debug("S3 host: {s}, scheme: {s}, port: {}", .{ host, "https", 443 });
return EndPoint{
.uri = uri,
.host = host,
.scheme = "https",
.port = 443,
.allocator = allocator,
.path = try allocator.dupe(u8, rest_of_path),
};
}
return null;
}
fn s3BucketFromPath(path: []const u8) []const u8 {
var in_bucket = false;
var start: usize = 0;
for (path) |c, inx| {
if (c == '/') {
if (in_bucket) return path[start..inx];
start = inx + 1;
in_bucket = true;
}
}
unreachable;
}
/// creates an endpoint from a uri string. /// creates an endpoint from a uri string.
/// ///
/// allocator: Will be used only to construct the EndPoint struct /// allocator: Will be used only to construct the EndPoint struct
@ -337,27 +397,68 @@ fn endPointFromUri(allocator: std.mem.Allocator, uri: []const u8) !EndPoint {
.scheme = scheme, .scheme = scheme,
.allocator = allocator, .allocator = allocator,
.port = port, .port = port,
.path = try allocator.dupe(u8, "/"),
}; };
} }
test "endpointForRequest standard operation" { test "endpointForRequest standard operation" {
const request: HttpRequest = .{};
const options: Options = .{
.region = "us-west-2",
.dualstack = false,
.sigv4_service_name = null,
};
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
const service = "dynamodb"; const service = "dynamodb";
const region = "us-west-2";
const use_dual_stack = false;
const endpoint = try endpointForRequest(allocator, service, region, use_dual_stack); const endpoint = try endpointForRequest(allocator, service, request, options);
defer endpoint.deinit(); defer endpoint.deinit();
try std.testing.expectEqualStrings("https://dynamodb.us-west-2.amazonaws.com", endpoint.uri); try std.testing.expectEqualStrings("https://dynamodb.us-west-2.amazonaws.com", endpoint.uri);
} }
test "endpointForRequest for cloudfront" { test "endpointForRequest for cloudfront" {
const request = HttpRequest{};
const options = Options{
.region = "us-west-2",
.dualstack = false,
.sigv4_service_name = null,
};
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
const service = "cloudfront"; const service = "cloudfront";
const region = "us-west-2";
const use_dual_stack = false;
const endpoint = try endpointForRequest(allocator, service, region, use_dual_stack); const endpoint = try endpointForRequest(allocator, service, request, options);
defer endpoint.deinit(); defer endpoint.deinit();
try std.testing.expectEqualStrings("https://cloudfront.amazonaws.com", endpoint.uri); try std.testing.expectEqualStrings("https://cloudfront.amazonaws.com", endpoint.uri);
} }
test "endpointForRequest for s3" {
const request = HttpRequest{};
const options = Options{
.region = "us-east-2",
.dualstack = false,
.sigv4_service_name = null,
};
const allocator = std.testing.allocator;
const service = "s3";
const endpoint = try endpointForRequest(allocator, service, request, options);
defer endpoint.deinit();
try std.testing.expectEqualStrings("https://s3.us-east-2.amazonaws.com", endpoint.uri);
}
test "endpointForRequest for s3 - specific bucket" {
const request = HttpRequest{
.path = "/bucket/key",
};
const options = Options{
.region = "us-east-2",
.dualstack = false,
.sigv4_service_name = null,
};
const allocator = std.testing.allocator;
const service = "s3";
const endpoint = try endpointForRequest(allocator, service, request, options);
defer endpoint.deinit();
try std.testing.expectEqualStrings("https://bucket.s3.us-east-2.amazonaws.com", endpoint.uri);
try std.testing.expectEqualStrings("/key", endpoint.path);
}