From a848cf20b2940b93c11f59b54f8fedcd58d85065 Mon Sep 17 00:00:00 2001 From: Michal Ostrowski Date: Sat, 28 May 2016 15:21:57 -0700 Subject: [PATCH] Support optional AWS_SESSION_TOKEN. The AWS_SESSION_TOKEN must be passed in the x-amz-security-token header. This is required if the credentials being used are temporary credentials, (e.g. acquired via an "assume role" operation). This header, if present must be signed as well. This change implements this support with an "aws_security_token" configuration option. This is then plugged through the various relevant APIs. --- aws_functions.h | 32 +++++++++++++++++++++++--------- ngx_http_aws_auth.c | 19 +++++++++++++++++-- test_suite.c | 4 ++-- 3 files changed, 42 insertions(+), 13 deletions(-) diff --git a/aws_functions.h b/aws_functions.h index 8853e3c..8567941 100644 --- a/aws_functions.h +++ b/aws_functions.h @@ -56,6 +56,7 @@ struct AwsSignedRequestDetails { static const ngx_str_t EMPTY_STRING_SHA256 = ngx_string("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); static const ngx_str_t EMPTY_STRING = ngx_null_string; static const ngx_str_t AMZ_HASH_HEADER = ngx_string("x-amz-content-sha256"); +static const ngx_str_t AMZ_SECURITY_TOKEN_HEADER = ngx_string("x-amz-security-token"); static const ngx_str_t AMZ_DATE_HEADER = ngx_string("x-amz-date"); static const ngx_str_t HOST_HEADER = ngx_string("host"); static const ngx_str_t AUTHZ_HEADER = ngx_string("authorization"); @@ -177,13 +178,17 @@ static inline const ngx_str_t* ngx_aws_auth__host_from_bucket(ngx_pool_t *pool, static inline struct AwsCanonicalHeaderDetails ngx_aws_auth__canonize_headers(ngx_pool_t *pool, const ngx_http_request_t *req, const ngx_str_t *s3_bucket, const ngx_str_t *amz_date, - const ngx_str_t *content_hash) { + const ngx_str_t *content_hash, + const ngx_str_t *amz_security_token) { size_t header_names_size = 1, header_nameval_size = 1; size_t i, used; u_char *buf_progress; + size_t count = 3; struct AwsCanonicalHeaderDetails retval; - - ngx_array_t *settable_header_array = ngx_array_create(pool, 3, sizeof(header_pair_t)); + if (amz_security_token != NULL) { + count += 1; + } + ngx_array_t *settable_header_array = ngx_array_create(pool, count, sizeof(header_pair_t)); header_pair_t *header_ptr; header_ptr = ngx_array_push(settable_header_array); @@ -200,6 +205,12 @@ static inline struct AwsCanonicalHeaderDetails ngx_aws_auth__canonize_headers(ng header_ptr->value.data = ngx_palloc(pool, header_ptr->value.len); header_ptr->value.len = ngx_snprintf(header_ptr->value.data, header_ptr->value.len, "%V.s3.amazonaws.com", s3_bucket) - header_ptr->value.data; + if (amz_security_token) { + header_ptr = ngx_array_push(settable_header_array); + header_ptr->key = AMZ_SECURITY_TOKEN_HEADER; + header_ptr->value = *amz_security_token; + } + ngx_qsort(settable_header_array->elts, (size_t) settable_header_array->nelts, sizeof(header_pair_t), ngx_aws_auth__cmp_hnames); retval.header_list = settable_header_array; @@ -269,7 +280,8 @@ static inline const ngx_str_t* ngx_aws_auth__canon_url(ngx_pool_t *pool, const n static inline struct AwsCanonicalRequestDetails ngx_aws_auth__make_canonical_request(ngx_pool_t *pool, const ngx_http_request_t *req, - const ngx_str_t *s3_bucket_name, const ngx_str_t *amz_date) { + const ngx_str_t *s3_bucket_name, const ngx_str_t *amz_date, + const ngx_str_t *amz_security_token) { struct AwsCanonicalRequestDetails retval; // canonize query string @@ -279,7 +291,7 @@ static inline struct AwsCanonicalRequestDetails ngx_aws_auth__make_canonical_req const ngx_str_t *request_body_hash = ngx_aws_auth__request_body_hash(pool, req); const struct AwsCanonicalHeaderDetails canon_headers = - ngx_aws_auth__canonize_headers(pool, req, s3_bucket_name, amz_date, request_body_hash); + ngx_aws_auth__canonize_headers(pool, req, s3_bucket_name, amz_date, request_body_hash, amz_security_token); retval.signed_header_names = canon_headers.signed_header_names; const ngx_str_t *http_method = &(req->method_name); @@ -331,12 +343,13 @@ static inline const ngx_str_t* ngx_aws_auth__make_auth_token(ngx_pool_t *pool, static inline struct AwsSignedRequestDetails ngx_aws_auth__compute_signature(ngx_pool_t *pool, ngx_http_request_t *req, const ngx_str_t *signing_key, const ngx_str_t *key_scope, - const ngx_str_t *s3_bucket_name) { + const ngx_str_t *s3_bucket_name, + const ngx_str_t *aws_security_token) { struct AwsSignedRequestDetails retval; const ngx_str_t *date = ngx_aws_auth__compute_request_time(pool, &req->start_sec); const struct AwsCanonicalRequestDetails canon_request = - ngx_aws_auth__make_canonical_request(pool, req, s3_bucket_name, date); + ngx_aws_auth__make_canonical_request(pool, req, s3_bucket_name, date, aws_security_token); const ngx_str_t *canon_request_hash = ngx_aws_auth__hash_sha256(pool, canon_request.canon_request); // get string to sign @@ -357,8 +370,9 @@ static inline const ngx_array_t* ngx_aws_auth__sign(ngx_pool_t *pool, ngx_http_r const ngx_str_t *access_key_id, const ngx_str_t *signing_key, const ngx_str_t *key_scope, - const ngx_str_t *s3_bucket_name) { - const struct AwsSignedRequestDetails signature_details = ngx_aws_auth__compute_signature(pool, req, signing_key, key_scope, s3_bucket_name); + const ngx_str_t *s3_bucket_name, + const ngx_str_t *aws_security_token) { + const struct AwsSignedRequestDetails signature_details = ngx_aws_auth__compute_signature(pool, req, signing_key, key_scope, s3_bucket_name, aws_security_token); const ngx_str_t *auth_header_value = ngx_aws_auth__make_auth_token(pool, signature_details.signature, diff --git a/ngx_http_aws_auth.c b/ngx_http_aws_auth.c index e83f945..13b924a 100644 --- a/ngx_http_aws_auth.c +++ b/ngx_http_aws_auth.c @@ -17,6 +17,7 @@ typedef struct { ngx_str_t signing_key; ngx_str_t signing_key_decoded; ngx_str_t bucket_name; + ngx_str_t security_token; ngx_uint_t enabled; } ngx_http_aws_auth_conf_t; @@ -50,6 +51,13 @@ static ngx_command_t ngx_http_aws_auth_commands[] = { offsetof(ngx_http_aws_auth_conf_t, bucket_name), NULL }, + { ngx_string("aws_security_token"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_str_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_aws_auth_conf_t, security_token), + NULL }, + { ngx_string("aws_sign"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_NOARGS, ngx_http_aws_sign, @@ -145,9 +153,16 @@ ngx_http_aws_proxy_sign(ngx_http_request_t *r) /* We do not wish to support anything with a body as signing for a body is unimplemented */ return NGX_HTTP_NOT_ALLOWED; } - + const ngx_str_t *security_token = &conf->security_token; + if (ngx_strlen(security_token) == 0) { + security_token = NULL; + } const ngx_array_t* headers_out = ngx_aws_auth__sign(r->pool, r, - &conf->access_key, &conf->signing_key_decoded, &conf->key_scope, &conf->bucket_name); + &conf->access_key, + &conf->signing_key_decoded, + &conf->key_scope, + &conf->bucket_name, + security_token); ngx_uint_t i; for(i = 0; i < headers_out->nelts; i++) diff --git a/test_suite.c b/test_suite.c index 5eda105..39b849a 100644 --- a/test_suite.c +++ b/test_suite.c @@ -97,7 +97,7 @@ static void canon_header_string(void **state) { date.data = "20160221T063112Z"; date.len = 16; hash.data = "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b"; hash.len = 64; - retval = ngx_aws_auth__canonize_headers(pool, NULL, &bucket, &date, &hash); + retval = ngx_aws_auth__canonize_headers(pool, NULL, &bucket, &date, &hash, NULL); assert_string_equal(retval.canon_header_str->data, "host:bugait.s3.amazonaws.com\nx-amz-content-sha256:f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b\nx-amz-date:20160221T063112Z\n"); } @@ -112,7 +112,7 @@ static void signed_headers(void **state) { date.data = "20160221T063112Z"; date.len = 16; hash.data = "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b"; hash.len = 64; - retval = ngx_aws_auth__canonize_headers(pool, NULL, &bucket, &date, &hash); + retval = ngx_aws_auth__canonize_headers(pool, NULL, &bucket, &date, &hash, NULL); assert_string_equal(retval.signed_header_names->data, "host;x-amz-content-sha256;x-amz-date"); } -- 2.1.4