diff --git a/include/aws/auth/auth.h b/include/aws/auth/auth.h index 1b9eff67..7899cb89 100644 --- a/include/aws/auth/auth.h +++ b/include/aws/auth/auth.h @@ -51,6 +51,7 @@ enum aws_auth_errors { AWS_AUTH_CREDENTIALS_PROVIDER_SSO_SOURCE_FAILURE, AWS_AUTH_IMDS_CLIENT_SOURCE_FAILURE, AWS_AUTH_PROFILE_STS_CREDENTIALS_PROVIDER_CYCLE_FAILURE, + AWS_AUTH_CREDENTIALS_PROVIDER_ECS_INVALID_TOKEN_FILE_PATH, AWS_AUTH_ERROR_END_RANGE = AWS_ERROR_ENUM_END_RANGE(AWS_C_AUTH_PACKAGE_ID) }; diff --git a/include/aws/auth/credentials.h b/include/aws/auth/credentials.h index 51254d6f..0c0ca12e 100644 --- a/include/aws/auth/credentials.h +++ b/include/aws/auth/credentials.h @@ -233,10 +233,20 @@ struct aws_credentials_provider_imds_options { * or via a full uri specified by environment variables: * AWS_CONTAINER_CREDENTIALS_RELATIVE_URI * AWS_CONTAINER_CREDENTIALS_FULL_URI - * AWS_CONTAINER_AUTHORIZATION_TOKEN + * * If both relative uri and absolute uri are set, relative uri - * has higher priority. Token is used in auth header but only for - * absolute uri. + * has higher priority. + * + * Currently, the ECS creds provider doesn't read those environment variables and requires host & path_and_query + * TODO: Support AWS_CONTAINER_CREDENTIALS_RELATIVE_URI and AWS_CONTAINER_CREDENTIALS_FULL_URI + * parameters. + * + * For the Authorization token, there are three ways (in order of priority). + * 1. auth_token parameter + * 2. AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE (env var which contains absolute path to the token file. The file will be + * re-read for each call to get credentials.) + * 3. AWS_CONTAINER_AUTHORIZATION_TOKEN (env var which contains static auth token) + * * While above information is used in request only, endpoint info * is needed when creating ecs provider to initiate the connection * manager, more specifically, host and http scheme (tls or not) diff --git a/source/auth.c b/source/auth.c index 82299338..1e8be09e 100644 --- a/source/auth.c +++ b/source/auth.c @@ -108,7 +108,10 @@ static struct aws_error_info s_errors[] = { "Failed to source the IMDS resource"), AWS_DEFINE_ERROR_INFO_AUTH( AWS_AUTH_PROFILE_STS_CREDENTIALS_PROVIDER_CYCLE_FAILURE, - "Failed to resolve credentials because the profile contains a cycle in the assumeRole chain.") + "Failed to resolve credentials because the profile contains a cycle in the assumeRole chain."), + AWS_DEFINE_ERROR_INFO_AUTH( + AWS_AUTH_CREDENTIALS_PROVIDER_ECS_INVALID_TOKEN_FILE_PATH, + "Failed to read the ECS token file specified in the AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE environment variable."), }; /* clang-format on */ diff --git a/source/credentials_provider_default_chain.c b/source/credentials_provider_default_chain.c index 8fd9bf52..ccd2bfbb 100644 --- a/source/credentials_provider_default_chain.c +++ b/source/credentials_provider_default_chain.c @@ -27,7 +27,6 @@ AWS_STATIC_STRING_FROM_LITERAL(s_ecs_creds_env_relative_uri, "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"); AWS_STATIC_STRING_FROM_LITERAL(s_ecs_creds_env_full_uri, "AWS_CONTAINER_CREDENTIALS_FULL_URI"); -AWS_STATIC_STRING_FROM_LITERAL(s_ecs_creds_env_token, "AWS_CONTAINER_AUTHORIZATION_TOKEN"); AWS_STATIC_STRING_FROM_LITERAL(s_ecs_host, "169.254.170.2"); AWS_STATIC_STRING_FROM_LITERAL(s_ec2_creds_env_disable, "AWS_EC2_METADATA_DISABLED"); @@ -41,29 +40,20 @@ static struct aws_credentials_provider *s_aws_credentials_provider_new_ecs_or_im struct aws_client_bootstrap *bootstrap, struct aws_tls_ctx *tls_ctx) { - struct aws_byte_cursor auth_token_cursor; - AWS_ZERO_STRUCT(auth_token_cursor); - struct aws_credentials_provider *ecs_or_imds_provider = NULL; struct aws_string *ecs_relative_uri = NULL; struct aws_string *ecs_full_uri = NULL; struct aws_string *ec2_imds_disable = NULL; - struct aws_string *ecs_token = NULL; if (aws_get_environment_value(allocator, s_ecs_creds_env_relative_uri, &ecs_relative_uri) != AWS_OP_SUCCESS || aws_get_environment_value(allocator, s_ecs_creds_env_full_uri, &ecs_full_uri) != AWS_OP_SUCCESS || - aws_get_environment_value(allocator, s_ec2_creds_env_disable, &ec2_imds_disable) != AWS_OP_SUCCESS || - aws_get_environment_value(allocator, s_ecs_creds_env_token, &ecs_token) != AWS_OP_SUCCESS) { + aws_get_environment_value(allocator, s_ec2_creds_env_disable, &ec2_imds_disable) != AWS_OP_SUCCESS) { AWS_LOGF_ERROR( AWS_LS_AUTH_CREDENTIALS_PROVIDER, "Failed reading environment variables during default credentials provider chain initialization."); goto clean_up; } - if (ecs_token && ecs_token->len) { - auth_token_cursor = aws_byte_cursor_from_string(ecs_token); - } - /* * ToDo: the uri choice logic should be done in the ecs provider init logic. As it stands, it's a nightmare * to try and use the ecs provider anywhere outside the default chain. @@ -79,7 +69,6 @@ static struct aws_credentials_provider *s_aws_credentials_provider_new_ecs_or_im .host = aws_byte_cursor_from_string(s_ecs_host), .path_and_query = aws_byte_cursor_from_string(ecs_relative_uri), .tls_ctx = NULL, - .auth_token = auth_token_cursor, }; ecs_or_imds_provider = aws_credentials_provider_new_ecs(allocator, &ecs_options); @@ -111,7 +100,6 @@ static struct aws_credentials_provider *s_aws_credentials_provider_new_ecs_or_im .host = uri.host_name, .path_and_query = path_and_query, .tls_ctx = aws_byte_cursor_eq_c_str_ignore_case(&(uri.scheme), "HTTPS") ? tls_ctx : NULL, - .auth_token = auth_token_cursor, .port = uri.port, }; @@ -138,7 +126,6 @@ static struct aws_credentials_provider *s_aws_credentials_provider_new_ecs_or_im aws_string_destroy(ecs_relative_uri); aws_string_destroy(ecs_full_uri); aws_string_destroy(ec2_imds_disable); - aws_string_destroy(ecs_token); return ecs_or_imds_provider; } diff --git a/source/credentials_provider_ecs.c b/source/credentials_provider_ecs.c index 5a4e75b3..4f7497cb 100644 --- a/source/credentials_provider_ecs.c +++ b/source/credentials_provider_ecs.c @@ -3,11 +3,13 @@ * SPDX-License-Identifier: Apache-2.0. */ +#include "aws/common/byte_buf.h" #include #include #include #include +#include #include #include #include @@ -28,6 +30,9 @@ #define ECS_RESPONSE_SIZE_LIMIT 10000 #define ECS_CONNECT_TIMEOUT_DEFAULT_IN_SECONDS 2 +AWS_STATIC_STRING_FROM_LITERAL(s_ecs_creds_env_token_file, "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE"); +AWS_STATIC_STRING_FROM_LITERAL(s_ecs_creds_env_token, "AWS_CONTAINER_AUTHORIZATION_TOKEN"); + static void s_on_connection_manager_shutdown(void *user_data); struct aws_credentials_provider_ecs_impl { @@ -35,6 +40,7 @@ struct aws_credentials_provider_ecs_impl { const struct aws_auth_http_system_vtable *function_table; struct aws_string *host; struct aws_string *path_and_query; + struct aws_string *auth_token_file_path; struct aws_string *auth_token; }; @@ -47,6 +53,7 @@ struct aws_credentials_provider_ecs_user_data { struct aws_credentials_provider *ecs_provider; aws_on_get_credentials_callback_fn *original_callback; void *original_user_data; + struct aws_byte_buf auth_token; /* mutable */ struct aws_http_connection *connection; @@ -68,6 +75,7 @@ static void s_aws_credentials_provider_ecs_user_data_destroy(struct aws_credenti impl->connection_manager, user_data->connection); } + aws_byte_buf_clean_up(&user_data->auth_token); aws_byte_buf_clean_up(&user_data->current_result); if (user_data->request) { @@ -84,9 +92,6 @@ static struct aws_credentials_provider_ecs_user_data *s_aws_credentials_provider struct aws_credentials_provider_ecs_user_data *wrapped_user_data = aws_mem_calloc(ecs_provider->allocator, 1, sizeof(struct aws_credentials_provider_ecs_user_data)); - if (wrapped_user_data == NULL) { - goto on_error; - } wrapped_user_data->allocator = ecs_provider->allocator; wrapped_user_data->ecs_provider = ecs_provider; @@ -98,12 +103,33 @@ static struct aws_credentials_provider_ecs_user_data *s_aws_credentials_provider goto on_error; } - return wrapped_user_data; + struct aws_credentials_provider_ecs_impl *impl = ecs_provider->impl; + if (impl->auth_token_file_path != NULL && impl->auth_token_file_path->len > 0) { + if (aws_byte_buf_init_from_file( + &wrapped_user_data->auth_token, + ecs_provider->allocator, + aws_string_c_str(impl->auth_token_file_path))) { + AWS_LOGF_ERROR( + AWS_LS_AUTH_CREDENTIALS_PROVIDER, + "(id=%p) ECS credentials provider failed to read token from the path: %s with error: %d", + (void *)ecs_provider, + aws_string_c_str(impl->auth_token_file_path), + aws_last_error()); + aws_raise_error(AWS_AUTH_CREDENTIALS_PROVIDER_ECS_INVALID_TOKEN_FILE_PATH); + goto on_error; + } + } else if (impl->auth_token != NULL && impl->auth_token->len > 0) { + if (aws_byte_buf_init_copy_from_cursor( + &wrapped_user_data->auth_token, + ecs_provider->allocator, + aws_byte_cursor_from_string(impl->auth_token))) { + goto on_error; + } + } + return wrapped_user_data; on_error: - s_aws_credentials_provider_ecs_user_data_destroy(wrapped_user_data); - return NULL; } @@ -318,10 +344,10 @@ static int s_make_ecs_http_query( goto on_error; } - if (impl->auth_token != NULL) { + if (ecs_user_data->auth_token.len) { struct aws_http_header auth_header = { .name = aws_byte_cursor_from_string(s_ecs_authorization_header), - .value = aws_byte_cursor_from_string(impl->auth_token), + .value = aws_byte_cursor_from_buf(&ecs_user_data->auth_token), }; if (aws_http_message_add_header(request, auth_header)) { goto on_error; @@ -462,6 +488,7 @@ static void s_credentials_provider_ecs_destroy(struct aws_credentials_provider * aws_string_destroy(impl->path_and_query); aws_string_destroy(impl->auth_token); + aws_string_destroy(impl->auth_token_file_path); aws_string_destroy(impl->host); /* aws_http_connection_manager_release will eventually leads to call of s_on_connection_manager_shutdown, @@ -567,7 +594,18 @@ struct aws_credentials_provider *aws_credentials_provider_new_ecs( if (impl->auth_token == NULL) { goto on_error; } + } else { + /* read the environment variables */ + struct aws_string *ecs_env_token_file_path = NULL; + struct aws_string *ecs_env_token = NULL; + if (aws_get_environment_value(allocator, s_ecs_creds_env_token_file, &ecs_env_token_file_path) || + aws_get_environment_value(allocator, s_ecs_creds_env_token, &ecs_env_token)) { + goto on_error; + } + impl->auth_token_file_path = ecs_env_token_file_path; + impl->auth_token = ecs_env_token; } + impl->path_and_query = aws_string_new_from_cursor(allocator, &options->path_and_query); if (impl->path_and_query == NULL) { goto on_error; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1e113e75..c65161bd 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -50,6 +50,9 @@ add_test_case(credentials_provider_ecs_connect_failure) add_test_case(credentials_provider_ecs_request_failure) add_test_case(credentials_provider_ecs_bad_document_failure) add_test_case(credentials_provider_ecs_basic_success) +add_test_case(credentials_provider_ecs_basic_success_token_file) +add_test_case(credentials_provider_ecs_basic_success_token_env) +add_test_case(credentials_provider_ecs_basic_success_token_env_with_parameter_token) add_test_case(credentials_provider_ecs_no_auth_token_success) add_test_case(credentials_provider_ecs_success_multi_part_doc) add_test_case(credentials_provider_ecs_real_new_destroy) diff --git a/tests/credentials_provider_ecs_tests.c b/tests/credentials_provider_ecs_tests.c index dd311409..fa6d384a 100644 --- a/tests/credentials_provider_ecs_tests.c +++ b/tests/credentials_provider_ecs_tests.c @@ -5,11 +5,14 @@ #include +#include "shared_credentials_test_definitions.h" + #include #include #include #include #include +#include #include #include #include @@ -21,6 +24,7 @@ struct aws_mock_ecs_tester { struct aws_byte_buf request_uri; + struct aws_byte_buf request_authorization_header; struct aws_array_list response_data_callbacks; bool is_connection_acquire_successful; @@ -156,6 +160,15 @@ static struct aws_http_stream *s_aws_http_connection_make_request_mock( aws_http_message_get_request_path(options->request, &path); aws_byte_buf_append_dynamic(&s_tester.request_uri, &path); + struct aws_byte_cursor authorization_header_value; + AWS_ZERO_STRUCT(authorization_header_value); + aws_http_headers_get( + aws_http_message_get_headers(options->request), + aws_byte_cursor_from_c_str("Authorization"), + &authorization_header_value); + + aws_byte_buf_append_dynamic(&s_tester.request_authorization_header, &authorization_header_value); + s_invoke_mock_request_callbacks(options, &s_tester.response_data_callbacks, s_tester.is_request_successful); return (struct aws_http_stream *)1; @@ -204,6 +217,8 @@ static int s_aws_ecs_tester_init(struct aws_allocator *allocator) { return AWS_OP_ERR; } + aws_byte_buf_init(&s_tester.request_authorization_header, allocator, 20); + if (aws_mutex_init(&s_tester.lock)) { return AWS_OP_ERR; } @@ -224,6 +239,7 @@ static int s_aws_ecs_tester_init(struct aws_allocator *allocator) { static void s_aws_ecs_tester_cleanup(void) { aws_array_list_clean_up(&s_tester.response_data_callbacks); aws_byte_buf_clean_up(&s_tester.request_uri); + aws_byte_buf_clean_up(&s_tester.request_authorization_header); aws_condition_variable_clean_up(&s_tester.signal); aws_mutex_clean_up(&s_tester.lock); aws_credentials_release(s_tester.credentials); @@ -520,6 +536,151 @@ static int s_credentials_provider_ecs_basic_success(struct aws_allocator *alloca AWS_TEST_CASE(credentials_provider_ecs_basic_success, s_credentials_provider_ecs_basic_success); +AWS_STATIC_STRING_FROM_LITERAL(s_ecs_creds_env_token_file, "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE"); +AWS_STATIC_STRING_FROM_LITERAL(s_ecs_creds_env_token, "AWS_CONTAINER_AUTHORIZATION_TOKEN"); + +static int s_credentials_provider_ecs_basic_success_token_file(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + s_aws_ecs_tester_init(allocator); + + struct aws_string *auth_token = aws_string_new_from_c_str(allocator, "test-token-1234-abcd"); + struct aws_byte_cursor auth_token_cursor = aws_byte_cursor_from_string(auth_token); + struct aws_string *token_file_path = aws_create_process_unique_file_name(allocator); + ASSERT_NOT_NULL(token_file_path); + ASSERT_TRUE(aws_create_profile_file(token_file_path, auth_token) == AWS_OP_SUCCESS); + ASSERT_TRUE(aws_set_environment_value(s_ecs_creds_env_token_file, token_file_path) == AWS_OP_SUCCESS); + + /* test that static auth token is not preferred over file token */ + struct aws_string *bad_auth_token = aws_string_new_from_c_str(allocator, "badtoken"); + ASSERT_TRUE(aws_set_environment_value(s_ecs_creds_env_token, bad_auth_token) == AWS_OP_SUCCESS); + + struct aws_byte_cursor good_response_cursor = aws_byte_cursor_from_string(s_good_response); + aws_array_list_push_back(&s_tester.response_data_callbacks, &good_response_cursor); + struct aws_credentials_provider_ecs_options options = { + .bootstrap = NULL, + .function_table = &s_mock_function_table, + .shutdown_options = + { + .shutdown_callback = s_on_shutdown_complete, + .shutdown_user_data = NULL, + }, + .host = aws_byte_cursor_from_c_str("www.xxx123321testmocknonexsitingawsservice.com"), + .path_and_query = aws_byte_cursor_from_c_str("/path/to/resource/?a=b&c=d"), + }; + + ASSERT_SUCCESS(s_do_ecs_success_test(allocator, &options)); + ASSERT_BIN_ARRAYS_EQUALS( + s_tester.request_authorization_header.buffer, + s_tester.request_authorization_header.len, + auth_token_cursor.ptr, + auth_token_cursor.len); + + /* update the file with updated token */ + struct aws_string *auth_token2 = aws_string_new_from_c_str(allocator, "test-token2-4321-qwer"); + struct aws_byte_cursor auth_token2_cursor = aws_byte_cursor_from_string(auth_token2); + ASSERT_TRUE(aws_create_profile_file(token_file_path, auth_token2) == AWS_OP_SUCCESS); + + /* reset tester */ + s_aws_ecs_tester_cleanup(); + s_aws_ecs_tester_init(allocator); + aws_array_list_push_back(&s_tester.response_data_callbacks, &good_response_cursor); + + ASSERT_SUCCESS(s_do_ecs_success_test(allocator, &options)); + ASSERT_BIN_ARRAYS_EQUALS( + s_tester.request_authorization_header.buffer, + s_tester.request_authorization_header.len, + auth_token2_cursor.ptr, + auth_token2_cursor.len); + + s_aws_ecs_tester_cleanup(); + aws_file_delete(token_file_path); + aws_string_destroy(auth_token); + aws_string_destroy(auth_token2); + aws_string_destroy(token_file_path); + aws_string_destroy(bad_auth_token); + return 0; +} +AWS_TEST_CASE(credentials_provider_ecs_basic_success_token_file, s_credentials_provider_ecs_basic_success_token_file); + +static int s_credentials_provider_ecs_basic_success_token_env(struct aws_allocator *allocator, void *ctx) { + (void)ctx; + + s_aws_ecs_tester_init(allocator); + + struct aws_byte_cursor good_response_cursor = aws_byte_cursor_from_string(s_good_response); + aws_array_list_push_back(&s_tester.response_data_callbacks, &good_response_cursor); + struct aws_string *auth_token = aws_string_new_from_c_str(allocator, "t-token-1234-abcd"); + struct aws_byte_cursor auth_token_cursor = aws_byte_cursor_from_string(auth_token); + aws_set_environment_value(s_ecs_creds_env_token, auth_token); + struct aws_credentials_provider_ecs_options options = { + .bootstrap = NULL, + .function_table = &s_mock_function_table, + .shutdown_options = + { + .shutdown_callback = s_on_shutdown_complete, + .shutdown_user_data = NULL, + }, + .host = aws_byte_cursor_from_c_str("www.xxx123321testmocknonexsitingawsservice.com"), + .path_and_query = aws_byte_cursor_from_c_str("/path/to/resource/?a=b&c=d"), + }; + + ASSERT_SUCCESS(s_do_ecs_success_test(allocator, &options)); + ASSERT_BIN_ARRAYS_EQUALS( + s_tester.request_authorization_header.buffer, + s_tester.request_authorization_header.len, + auth_token_cursor.ptr, + auth_token_cursor.len); + + s_aws_ecs_tester_cleanup(); + aws_string_destroy(auth_token); + return 0; +} +AWS_TEST_CASE(credentials_provider_ecs_basic_success_token_env, s_credentials_provider_ecs_basic_success_token_env); + +static int s_credentials_provider_ecs_basic_success_token_env_with_parameter_token( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + s_aws_ecs_tester_init(allocator); + + struct aws_byte_cursor good_response_cursor = aws_byte_cursor_from_string(s_good_response); + aws_array_list_push_back(&s_tester.response_data_callbacks, &good_response_cursor); + + struct aws_string *auth_token = aws_string_new_from_c_str(allocator, "t-token-1234-abcd"); + aws_set_environment_value(s_ecs_creds_env_token, auth_token); + + struct aws_byte_cursor expected_token_cursor = aws_byte_cursor_from_c_str("t-token-4321-xyz"); + struct aws_credentials_provider_ecs_options options = { + + .bootstrap = NULL, + .function_table = &s_mock_function_table, + .shutdown_options = + { + .shutdown_callback = s_on_shutdown_complete, + .shutdown_user_data = NULL, + }, + .host = aws_byte_cursor_from_c_str("www.xxx123321testmocknonexsitingawsservice.com"), + .path_and_query = aws_byte_cursor_from_c_str("/path/to/resource/?a=b&c=d"), + .auth_token = expected_token_cursor, + }; + + ASSERT_SUCCESS(s_do_ecs_success_test(allocator, &options)); + ASSERT_BIN_ARRAYS_EQUALS( + s_tester.request_authorization_header.buffer, + s_tester.request_authorization_header.len, + expected_token_cursor.ptr, + expected_token_cursor.len); + + s_aws_ecs_tester_cleanup(); + aws_string_destroy(auth_token); + return 0; +} +AWS_TEST_CASE( + credentials_provider_ecs_basic_success_token_env_with_parameter_token, + s_credentials_provider_ecs_basic_success_token_env_with_parameter_token); + static int s_credentials_provider_ecs_no_auth_token_success(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -543,6 +704,7 @@ static int s_credentials_provider_ecs_no_auth_token_success(struct aws_allocator ASSERT_SUCCESS(s_do_ecs_success_test(allocator, &options)); s_aws_ecs_tester_cleanup(); + ASSERT_TRUE(s_tester.request_authorization_header.len == 0); return 0; } @@ -611,7 +773,6 @@ static int s_credentials_provider_ecs_success_multi_part_doc(struct aws_allocato /* Because we mock the http connection manager, we never get a callback back from it */ aws_mem_release(provider->allocator, provider); - s_aws_ecs_tester_cleanup(); return 0;