From 140652a7fd30b1ed0b926257e8f8d0ea4f060953 Mon Sep 17 00:00:00 2001 From: Waqar Ahmed Khan Date: Mon, 6 May 2024 09:34:15 -0700 Subject: [PATCH] Refactor ECSCredentialsProvider Part 1 (#236) --- include/aws/auth/credentials.h | 80 +++- source/auth.c | 2 +- source/credentials_provider_cognito.c | 2 +- source/credentials_provider_default_chain.c | 101 +--- source/credentials_provider_ecs.c | 130 +++++- source/credentials_provider_profile.c | 17 + source/credentials_provider_sts.c | 2 +- tests/CMakeLists.txt | 4 +- tests/credentials_provider_ecs_tests.c | 487 ++++++++++++++------ tests/credentials_provider_sts_tests.c | 292 +++++++++--- 10 files changed, 794 insertions(+), 323 deletions(-) diff --git a/include/aws/auth/credentials.h b/include/aws/auth/credentials.h index 0c0ca12e..55d48570 100644 --- a/include/aws/auth/credentials.h +++ b/include/aws/auth/credentials.h @@ -226,31 +226,41 @@ struct aws_credentials_provider_imds_options { }; /* - * Configuration options for the provider that sources credentials from ECS container metadata - * - * ECS creds provider can be used to access creds via either - * relative uri to a fixed endpoint http://169.254.170.2, - * or via a full uri specified by environment variables: + * Configuration options for the provider that sources credentials from ECS container metadata. + * The ECS creds provider can be used to access creds via either a relative URI to a fixed endpoint + * (http://169.254.170.2) or via a full URI specified by environment variables (in order of priority): * AWS_CONTAINER_CREDENTIALS_RELATIVE_URI * AWS_CONTAINER_CREDENTIALS_FULL_URI * - * If both relative uri and absolute uri are set, relative 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) - * from endpoint are needed. + * For the Authorization token, there are two ways (in order of priority): + * 1. AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE (an env var which contains the absolute path to the token file. The file + * will be re-read for each call to get credentials.) + * 2. AWS_CONTAINER_AUTHORIZATION_TOKEN (an env var that contains a static auth token) + */ +struct aws_credentials_provider_ecs_environment_options { + struct aws_credentials_provider_shutdown_options shutdown_options; + + /* + * (Required) + * Connection bootstrap to use for any network connections made while sourcing credentials + */ + struct aws_client_bootstrap *bootstrap; + + /* + * (Required) + * Client TLS context to use when making a query. This will only be used if the AWS_CONTAINER_CREDENTIALS_FULL_URI + * is set and starts with https + */ + struct aws_tls_ctx *tls_ctx; + + /* For mocking the http layer in tests, leave NULL otherwise */ + struct aws_auth_http_system_vtable *function_table; +}; + +/* + * Configuration options for the provider that sources credentials from ECS container metadata. + * This options struct doesn't read anything from the environment and requires everything to be explicitly passed in. If + * you need to read properties from the environment, use the `aws_credentials_provider_ecs_environment_options`. */ struct aws_credentials_provider_ecs_options { struct aws_credentials_provider_shutdown_options shutdown_options; @@ -271,7 +281,15 @@ struct aws_credentials_provider_ecs_options { struct aws_byte_cursor path_and_query; /* - * Authorization token to include in the credentials query + * Authorization token file path to include in the credentials query. The file will be re-read for each call to + * get_credentials. + * This has higher priority than `auth_token`. + */ + struct aws_byte_cursor auth_token_file_path; + + /* + * Authorization token to include in the credentials query. + * No effect if `auth_token_file_path` is set. */ struct aws_byte_cursor auth_token; @@ -996,8 +1014,24 @@ struct aws_credentials_provider *aws_credentials_provider_new_imds( struct aws_allocator *allocator, const struct aws_credentials_provider_imds_options *options); +/** + * Creates a provider that sources credentials from the ecs role credentials service and reads the required params from + * environment variables + * + * @param allocator memory allocator to use for all memory allocation + * @param options provider-specific configuration options + * + * @return the newly-constructed credentials provider, or NULL if an error occurred. + */ +AWS_AUTH_API +struct aws_credentials_provider *aws_credentials_provider_new_ecs_from_environment( + struct aws_allocator *allocator, + const struct aws_credentials_provider_ecs_environment_options *options); + /** * Creates a provider that sources credentials from the ecs role credentials service + * This function doesn't read anything from the environment and requires everything to be explicitly passed in. + * If you need to read properties from the environment, use the `aws_credentials_provider_new_ecs_from_environment`. * * @param allocator memory allocator to use for all memory allocation * @param options provider-specific configuration options diff --git a/source/auth.c b/source/auth.c index 1e8be09e..da93f49c 100644 --- a/source/auth.c +++ b/source/auth.c @@ -39,7 +39,7 @@ static struct aws_error_info s_errors[] = { "Attempt to sign an http request with an invalid signing configuration"), AWS_DEFINE_ERROR_INFO_AUTH( AWS_AUTH_CREDENTIALS_PROVIDER_INVALID_ENVIRONMENT, - "Valid credentials could not be sourced from process environment"), + "Required environment variables could not be sourced from process environment"), AWS_DEFINE_ERROR_INFO_AUTH( AWS_AUTH_CREDENTIALS_PROVIDER_INVALID_DELEGATE, "Valid credentials could not be sourced from the provided vtable"), diff --git a/source/credentials_provider_cognito.c b/source/credentials_provider_cognito.c index ece91b8a..f918e998 100644 --- a/source/credentials_provider_cognito.c +++ b/source/credentials_provider_cognito.c @@ -22,7 +22,7 @@ #include #define COGNITO_CONNECT_TIMEOUT_DEFAULT_IN_SECONDS 5 -#define COGNITO_MAX_RETRIES 8 +#define COGNITO_MAX_RETRIES 3 #define HTTP_REQUEST_BODY_INITIAL_SIZE 1024 #define HTTP_RESPONSE_BODY_INITIAL_SIZE 4096 diff --git a/source/credentials_provider_default_chain.c b/source/credentials_provider_default_chain.c index ccd2bfbb..321a16d8 100644 --- a/source/credentials_provider_default_chain.c +++ b/source/credentials_provider_default_chain.c @@ -25,9 +25,6 @@ # pragma warning(disable : 4221) #endif /* _MSC_VER */ -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_host, "169.254.170.2"); AWS_STATIC_STRING_FROM_LITERAL(s_ec2_creds_env_disable, "AWS_EC2_METADATA_DISABLED"); /** @@ -40,72 +37,27 @@ 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_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; - - 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_LOGF_ERROR( - AWS_LS_AUTH_CREDENTIALS_PROVIDER, - "Failed reading environment variables during default credentials provider chain initialization."); - goto clean_up; - } - - /* - * 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. - */ - if (ecs_relative_uri && ecs_relative_uri->len) { - AWS_LOGF_INFO( - AWS_LS_AUTH_CREDENTIALS_PROVIDER, - "default chain: ECS credentials provider with relative URI %s will be used to retrieve credentials", - aws_string_c_str(ecs_relative_uri)); - struct aws_credentials_provider_ecs_options ecs_options = { - .shutdown_options = *shutdown_options, - .bootstrap = bootstrap, - .host = aws_byte_cursor_from_string(s_ecs_host), - .path_and_query = aws_byte_cursor_from_string(ecs_relative_uri), - .tls_ctx = NULL, - }; - ecs_or_imds_provider = aws_credentials_provider_new_ecs(allocator, &ecs_options); - - } else if (ecs_full_uri && ecs_full_uri->len) { - struct aws_uri uri; - struct aws_byte_cursor uri_cstr = aws_byte_cursor_from_string(ecs_full_uri); - if (AWS_OP_ERR == aws_uri_init_parse(&uri, allocator, &uri_cstr)) { - AWS_LOGF_ERROR( - AWS_LS_AUTH_CREDENTIALS_PROVIDER, - "default chain: failed to parse URI %s during default credentials provider chain initialization: %s", - aws_string_c_str(ecs_full_uri), - aws_error_str(aws_last_error())); - goto clean_up; - } - + /* Try to create the ECS provider. This will fail if its environment variables aren't set */ + struct aws_credentials_provider_ecs_environment_options ecs_options = { + .shutdown_options = *shutdown_options, + .bootstrap = bootstrap, + .tls_ctx = tls_ctx, + }; + struct aws_credentials_provider *ecs_provider = + aws_credentials_provider_new_ecs_from_environment(allocator, &ecs_options); + if (ecs_provider != NULL) { AWS_LOGF_INFO( AWS_LS_AUTH_CREDENTIALS_PROVIDER, - "default chain: ECS credentials provider with full URI %s will be used to retrieve credentials", - aws_string_c_str(ecs_full_uri)); - - struct aws_byte_cursor path_and_query = uri.path_and_query; - if (path_and_query.len == 0) { - path_and_query = aws_byte_cursor_from_c_str("/"); - } - - struct aws_credentials_provider_ecs_options ecs_options = { - .shutdown_options = *shutdown_options, - .bootstrap = bootstrap, - .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, - .port = uri.port, - }; + "default chain: ECS credentials provider will be used to retrieve credentials"); + return ecs_provider; + } - ecs_or_imds_provider = aws_credentials_provider_new_ecs(allocator, &ecs_options); - aws_uri_clean_up(&uri); - } else if (ec2_imds_disable == NULL || aws_string_eq_c_str_ignore_case(ec2_imds_disable, "false")) { + /* Can we do IMDS? */ + struct aws_string *ec2_imds_disable = NULL; + aws_get_environment_value(allocator, s_ec2_creds_env_disable, &ec2_imds_disable); + bool imds_enabled = ec2_imds_disable == NULL || aws_string_eq_c_str_ignore_case(ec2_imds_disable, "false"); + aws_string_destroy(ec2_imds_disable); + if (imds_enabled) { AWS_LOGF_INFO( AWS_LS_AUTH_CREDENTIALS_PROVIDER, "default chain: IMDS credentials provider will be used to retrieve credentials"); @@ -113,21 +65,12 @@ static struct aws_credentials_provider *s_aws_credentials_provider_new_ecs_or_im .shutdown_options = *shutdown_options, .bootstrap = bootstrap, }; - ecs_or_imds_provider = aws_credentials_provider_new_imds(allocator, &imds_options); + return aws_credentials_provider_new_imds(allocator, &imds_options); } -clean_up: - if (ecs_or_imds_provider == NULL) { - AWS_LOGF_INFO( - AWS_LS_AUTH_CREDENTIALS_PROVIDER, - "default chain: neither ECS nor IMDS will be used to retrieve credentials"); - } - - aws_string_destroy(ecs_relative_uri); - aws_string_destroy(ecs_full_uri); - aws_string_destroy(ec2_imds_disable); - - return ecs_or_imds_provider; + AWS_LOGF_INFO( + AWS_LS_AUTH_CREDENTIALS_PROVIDER, "default chain: neither ECS nor IMDS will be used to retrieve credentials"); + return NULL; } struct default_chain_callback_data { diff --git a/source/credentials_provider_ecs.c b/source/credentials_provider_ecs.c index 4f7497cb..08fd5785 100644 --- a/source/credentials_provider_ecs.c +++ b/source/credentials_provider_ecs.c @@ -32,6 +32,9 @@ 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"); +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_host, "169.254.170.2"); static void s_on_connection_manager_shutdown(void *user_data); @@ -397,7 +400,10 @@ static int s_make_ecs_http_query( .user_data = ecs_user_data, .request = request, }; - + /* for test with mocking http stack where make request finishes + immediately and releases client before stream activate call */ + struct aws_credentials_provider *provider = ecs_user_data->ecs_provider; + aws_credentials_provider_acquire(provider); stream = impl->function_table->aws_http_connection_make_request(ecs_user_data->connection, &request_options); if (!stream) { @@ -407,6 +413,7 @@ static int s_make_ecs_http_query( if (impl->function_table->aws_http_stream_activate(stream)) { goto on_error; } + aws_credentials_provider_release(provider); return AWS_OP_SUCCESS; @@ -522,6 +529,12 @@ struct aws_credentials_provider *aws_credentials_provider_new_ecs( struct aws_allocator *allocator, const struct aws_credentials_provider_ecs_options *options) { + if (!options->bootstrap) { + AWS_LOGF_ERROR(AWS_LS_AUTH_CREDENTIALS_PROVIDER, "ECS provider: bootstrap must be specified"); + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + struct aws_credentials_provider *provider = NULL; struct aws_credentials_provider_ecs_impl *impl = NULL; @@ -594,16 +607,12 @@ 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)) { + } + if (options->auth_token_file_path.len != 0) { + impl->auth_token_file_path = aws_string_new_from_cursor(allocator, &options->auth_token_file_path); + if (impl->auth_token_file_path == NULL) { 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); @@ -629,3 +638,106 @@ struct aws_credentials_provider *aws_credentials_provider_new_ecs( return NULL; } + +struct aws_credentials_provider *aws_credentials_provider_new_ecs_from_environment( + struct aws_allocator *allocator, + const struct aws_credentials_provider_ecs_environment_options *options) { + + if (!options->tls_ctx) { + AWS_LOGF_ERROR(AWS_LS_AUTH_CREDENTIALS_PROVIDER, "ECS provider: tls_ctx must be specified"); + aws_raise_error(AWS_ERROR_INVALID_ARGUMENT); + return NULL; + } + + struct aws_credentials_provider_ecs_options explicit_options = { + .shutdown_options = options->shutdown_options, + .bootstrap = options->bootstrap, + .function_table = options->function_table, + }; + + struct aws_string *ecs_env_token_file_path = NULL; + struct aws_string *ecs_env_token = NULL; + struct aws_string *relative_uri_str = NULL; + struct aws_string *full_uri_str = NULL; + struct aws_uri full_uri; + AWS_ZERO_STRUCT(full_uri); + struct aws_credentials_provider *provider = NULL; + + /* read the environment variables */ + 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); + aws_get_environment_value(allocator, s_ecs_creds_env_relative_uri, &relative_uri_str); + aws_get_environment_value(allocator, s_ecs_creds_env_full_uri, &full_uri_str); + + if (ecs_env_token_file_path != NULL && ecs_env_token_file_path->len > 0) { + explicit_options.auth_token_file_path = aws_byte_cursor_from_string(ecs_env_token_file_path); + } + if (ecs_env_token != NULL && ecs_env_token->len > 0) { + explicit_options.auth_token = aws_byte_cursor_from_string(ecs_env_token); + } + + if (relative_uri_str != NULL && relative_uri_str->len != 0) { + + /* Using RELATIVE_URI */ + AWS_LOGF_INFO( + AWS_LS_AUTH_CREDENTIALS_PROVIDER, + "ECS provider: using relative uri %s", + aws_string_c_str(relative_uri_str)); + + explicit_options.path_and_query = aws_byte_cursor_from_string(relative_uri_str); + explicit_options.host = aws_byte_cursor_from_string(s_ecs_host); + explicit_options.port = 80; + + provider = aws_credentials_provider_new_ecs(allocator, &explicit_options); + + } else if (full_uri_str != NULL && full_uri_str->len != 0) { + + /* Using FULL_URI */ + AWS_LOGF_INFO( + AWS_LS_AUTH_CREDENTIALS_PROVIDER, "ECS provider: using full uri %s", aws_string_c_str(full_uri_str)); + struct aws_byte_cursor full_uri_cursor = aws_byte_cursor_from_string(full_uri_str); + if (aws_uri_init_parse(&full_uri, allocator, &full_uri_cursor)) { + AWS_LOGF_ERROR( + AWS_LS_AUTH_CREDENTIALS_PROVIDER, + "ECS provider: Failed because %s environment variable is invalid uri %s.", + aws_string_c_str(s_ecs_creds_env_full_uri), + aws_string_c_str(full_uri_str)); + goto cleanup; + } + + explicit_options.host = *aws_uri_host_name(&full_uri); + explicit_options.path_and_query = *aws_uri_path_and_query(&full_uri); + if (explicit_options.path_and_query.len == 0) { + explicit_options.path_and_query = aws_byte_cursor_from_c_str("/"); + } + + if (aws_byte_cursor_eq_c_str_ignore_case(aws_uri_scheme(&full_uri), "https")) { + explicit_options.tls_ctx = options->tls_ctx; + } + + explicit_options.port = aws_uri_port(&full_uri); + if (explicit_options.port == 0) { + explicit_options.port = explicit_options.tls_ctx ? 443 : 80; + } + + provider = aws_credentials_provider_new_ecs(allocator, &explicit_options); + + } else { + /* Neither environment variable is set */ + AWS_LOGF_INFO( + AWS_LS_AUTH_CREDENTIALS_PROVIDER, + "ECS provider: Unable to initialize from environment because AWS_CONTAINER_CREDENTIALS_FULL_URI and " + "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI are not set."); + aws_raise_error(AWS_AUTH_CREDENTIALS_PROVIDER_INVALID_ENVIRONMENT); + goto cleanup; + } + +cleanup: + aws_string_destroy(relative_uri_str); + aws_string_destroy(full_uri_str); + aws_string_destroy(ecs_env_token_file_path); + aws_string_destroy(ecs_env_token); + + aws_uri_clean_up(&full_uri); + return provider; +} diff --git a/source/credentials_provider_profile.c b/source/credentials_provider_profile.c index 038322c9..6f2fe770 100644 --- a/source/credentials_provider_profile.c +++ b/source/credentials_provider_profile.c @@ -32,6 +32,7 @@ static struct aws_byte_cursor s_default_session_name_pfx = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("aws-common-runtime-profile-config"); static struct aws_byte_cursor s_ec2_imds_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Ec2InstanceMetadata"); static struct aws_byte_cursor s_environment_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Environment"); +static struct aws_byte_cursor s_ecs_credentials_provider_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("EcsContainer"); #define MAX_SESSION_NAME_LEN ((size_t)64) @@ -372,6 +373,22 @@ static struct aws_credentials_provider *s_create_sts_based_provider( provider = aws_credentials_provider_new_sts(allocator, &sts_options); aws_credentials_provider_release(env_provider); + } else if (aws_string_eq_byte_cursor_ignore_case( + aws_profile_property_get_value(credential_source_property), &s_ecs_credentials_provider_name)) { + struct aws_credentials_provider_ecs_environment_options ecs_options = { + .bootstrap = options->bootstrap, + .function_table = options->function_table, + .tls_ctx = tls_ctx, + }; + struct aws_credentials_provider *ecs_provider = + aws_credentials_provider_new_ecs_from_environment(allocator, &ecs_options); + if (!ecs_provider) { + goto done; + } + sts_options.creds_provider = ecs_provider; + provider = aws_credentials_provider_new_sts(allocator, &sts_options); + + aws_credentials_provider_release(ecs_provider); } else { AWS_LOGF_ERROR( AWS_LS_AUTH_CREDENTIALS_PROVIDER, diff --git a/source/credentials_provider_sts.c b/source/credentials_provider_sts.c index 0832cdee..5d568885 100644 --- a/source/credentials_provider_sts.c +++ b/source/credentials_provider_sts.c @@ -54,7 +54,7 @@ static struct aws_byte_cursor s_content_length = AWS_BYTE_CUR_INIT_FROM_STRING_L static struct aws_byte_cursor s_path = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("/"); static struct aws_byte_cursor s_signing_region = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("us-east-1"); static struct aws_byte_cursor s_service_name = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("sts"); -static const int s_max_retries = 8; +static const int s_max_retries = 3; const uint16_t aws_sts_assume_role_default_duration_secs = 900; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c65161bd..c0aa6a48 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -51,8 +51,7 @@ 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_basic_success_uri_env) 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) @@ -91,6 +90,7 @@ add_net_test_case(credentials_provider_sts_direct_config_connection_failed) add_net_test_case(credentials_provider_sts_direct_config_service_fails) add_net_test_case(credentials_provider_sts_from_profile_config_succeeds) add_net_test_case(credentials_provider_sts_from_profile_config_with_chain) +add_net_test_case(credentials_provider_sts_from_profile_config_with_ecs_credentials_source) add_net_test_case(credentials_provider_sts_from_profile_config_with_chain_and_profile_creds) add_net_test_case(credentials_provider_sts_from_profile_config_with_chain_and_partial_profile_creds) add_net_test_case(credentials_provider_sts_from_self_referencing_profile) diff --git a/tests/credentials_provider_ecs_tests.c b/tests/credentials_provider_ecs_tests.c index fa6d384a..d698c399 100644 --- a/tests/credentials_provider_ecs_tests.c +++ b/tests/credentials_provider_ecs_tests.c @@ -15,16 +15,26 @@ #include #include #include +#include #include #include #include #include #include #include +#include + +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_file, "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE"); +AWS_STATIC_STRING_FROM_LITERAL(s_ecs_creds_env_token, "AWS_CONTAINER_AUTHORIZATION_TOKEN"); struct aws_mock_ecs_tester { - struct aws_byte_buf request_uri; - struct aws_byte_buf request_authorization_header; + struct aws_allocator *allocator; + + struct aws_string *request_path_and_query; + struct aws_string *request_authorization_header; + struct aws_string *selected_host; struct aws_array_list response_data_callbacks; bool is_connection_acquire_successful; @@ -36,9 +46,14 @@ struct aws_mock_ecs_tester { struct aws_credentials *credentials; bool has_received_credentials_callback; bool has_received_shutdown_callback; + bool selected_tls; uint32_t selected_port; int error_code; + + struct aws_event_loop_group *el_group; + struct aws_host_resolver *host_resolver; + struct aws_client_bootstrap *bootstrap; }; static struct aws_mock_ecs_tester s_tester; @@ -73,7 +88,9 @@ static struct aws_http_connection_manager *s_aws_http_connection_manager_new_moc (void)options; aws_mutex_lock(&s_tester.lock); + s_tester.selected_host = aws_string_new_from_cursor(allocator, &options->host); s_tester.selected_port = options->port; + s_tester.selected_tls = options->tls_connection_options != NULL; aws_mutex_unlock(&s_tester.lock); return (struct aws_http_connection_manager *)1; @@ -159,15 +176,16 @@ static struct aws_http_stream *s_aws_http_connection_make_request_mock( AWS_ZERO_STRUCT(path); aws_http_message_get_request_path(options->request, &path); - aws_byte_buf_append_dynamic(&s_tester.request_uri, &path); + s_tester.request_path_and_query = aws_string_new_from_cursor(s_tester.allocator, &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); + if (aws_http_headers_get( + aws_http_message_get_headers(options->request), + aws_byte_cursor_from_c_str("Authorization"), + &authorization_header_value) == AWS_OP_SUCCESS) { + s_tester.request_authorization_header = + aws_string_new_from_cursor(s_tester.allocator, &authorization_header_value); + } s_invoke_mock_request_callbacks(options, &s_tester.response_data_callbacks, s_tester.is_request_successful); @@ -209,16 +227,12 @@ static struct aws_auth_http_system_vtable s_mock_function_table = { .aws_http_connection_close = s_aws_http_connection_close_mock}; static int s_aws_ecs_tester_init(struct aws_allocator *allocator) { + aws_auth_library_init(allocator); + s_tester.allocator = allocator; if (aws_array_list_init_dynamic(&s_tester.response_data_callbacks, allocator, 10, sizeof(struct aws_byte_cursor))) { return AWS_OP_ERR; } - if (aws_byte_buf_init(&s_tester.request_uri, allocator, 100)) { - 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; } @@ -227,22 +241,49 @@ static int s_aws_ecs_tester_init(struct aws_allocator *allocator) { return AWS_OP_ERR; } - aws_auth_library_init(allocator); - /* default to everything successful */ s_tester.is_connection_acquire_successful = true; s_tester.is_request_successful = true; + s_tester.el_group = aws_event_loop_group_new_default(allocator, 1, NULL); + + struct aws_host_resolver_default_options resolver_options = { + .el_group = s_tester.el_group, + .max_entries = 8, + }; + s_tester.host_resolver = aws_host_resolver_new_default(allocator, &resolver_options); + + struct aws_client_bootstrap_options bootstrap_options = { + .event_loop_group = s_tester.el_group, + .host_resolver = s_tester.host_resolver, + }; + s_tester.bootstrap = aws_client_bootstrap_new(allocator, &bootstrap_options); + + /* ensure pre-existing environment doesn't interfere with tests */ + aws_unset_environment_value(s_ecs_creds_env_relative_uri); + aws_unset_environment_value(s_ecs_creds_env_full_uri); + aws_unset_environment_value(s_ecs_creds_env_token_file); + aws_unset_environment_value(s_ecs_creds_env_token); + return AWS_OP_SUCCESS; } -static void s_aws_ecs_tester_cleanup(void) { +static void s_aws_ecs_tester_reset(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_string_destroy(s_tester.request_path_and_query); + aws_string_destroy(s_tester.request_authorization_header); + aws_string_destroy(s_tester.selected_host); aws_condition_variable_clean_up(&s_tester.signal); aws_mutex_clean_up(&s_tester.lock); aws_credentials_release(s_tester.credentials); + aws_client_bootstrap_release(s_tester.bootstrap); + aws_host_resolver_release(s_tester.host_resolver); + aws_event_loop_group_release(s_tester.el_group); + AWS_ZERO_STRUCT(s_tester); +} + +static void s_aws_ecs_tester_cleanup(void) { + s_aws_ecs_tester_reset(); aws_auth_library_clean_up(); } @@ -277,7 +318,7 @@ static int s_credentials_provider_ecs_new_destroy(struct aws_allocator *allocato s_aws_ecs_tester_init(allocator); struct aws_credentials_provider_ecs_options options = { - .bootstrap = NULL, + .bootstrap = s_tester.bootstrap, .function_table = &s_mock_function_table, .shutdown_options = { @@ -311,7 +352,7 @@ static int s_credentials_provider_ecs_connect_failure(struct aws_allocator *allo s_tester.is_connection_acquire_successful = false; struct aws_credentials_provider_ecs_options options = { - .bootstrap = NULL, + .bootstrap = s_tester.bootstrap, .function_table = &s_mock_function_table, .shutdown_options = { @@ -349,8 +390,6 @@ static int s_credentials_provider_ecs_connect_failure(struct aws_allocator *allo AWS_TEST_CASE(credentials_provider_ecs_connect_failure, s_credentials_provider_ecs_connect_failure); -AWS_STATIC_STRING_FROM_LITERAL(s_expected_ecs_relative_uri, "/path/to/resource/?a=b&c=d"); - static int s_credentials_provider_ecs_request_failure(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -358,7 +397,7 @@ static int s_credentials_provider_ecs_request_failure(struct aws_allocator *allo s_tester.is_request_successful = false; struct aws_credentials_provider_ecs_options options = { - .bootstrap = NULL, + .bootstrap = s_tester.bootstrap, .function_table = &s_mock_function_table, .shutdown_options = { @@ -377,11 +416,7 @@ static int s_credentials_provider_ecs_request_failure(struct aws_allocator *allo s_aws_wait_for_credentials_result(); aws_mutex_lock(&s_tester.lock); - ASSERT_BIN_ARRAYS_EQUALS( - s_tester.request_uri.buffer, - s_tester.request_uri.len, - s_expected_ecs_relative_uri->bytes, - s_expected_ecs_relative_uri->len); + ASSERT_STR_EQUALS("/path/to/resource/?a=b&c=d", aws_string_c_str(s_tester.request_path_and_query)); ASSERT_TRUE(s_tester.has_received_credentials_callback == true); ASSERT_TRUE(s_tester.credentials == NULL); ASSERT_UINT_EQUALS(80, s_tester.selected_port); @@ -412,7 +447,7 @@ static int s_credentials_provider_ecs_bad_document_failure(struct aws_allocator aws_array_list_push_back(&s_tester.response_data_callbacks, &bad_document_cursor); struct aws_credentials_provider_ecs_options options = { - .bootstrap = NULL, + .bootstrap = s_tester.bootstrap, .function_table = &s_mock_function_table, .shutdown_options = { @@ -432,11 +467,7 @@ static int s_credentials_provider_ecs_bad_document_failure(struct aws_allocator s_aws_wait_for_credentials_result(); aws_mutex_lock(&s_tester.lock); - ASSERT_BIN_ARRAYS_EQUALS( - s_tester.request_uri.buffer, - s_tester.request_uri.len, - s_expected_ecs_relative_uri->bytes, - s_expected_ecs_relative_uri->len); + ASSERT_STR_EQUALS("/path/to/resource/?a=b&c=d", aws_string_c_str(s_tester.request_path_and_query)); ASSERT_TRUE(s_tester.has_received_credentials_callback == true); ASSERT_TRUE(s_tester.credentials == NULL); @@ -466,9 +497,36 @@ AWS_STATIC_STRING_FROM_LITERAL(s_good_secret_access_key, "SuccessfulSecret"); AWS_STATIC_STRING_FROM_LITERAL(s_good_session_token, "TokenSuccess"); AWS_STATIC_STRING_FROM_LITERAL(s_good_response_expiration, "2020-02-25T06:03:31Z"); +/* Check that expected URI and Authorization token were used to make request. + * URI must be super explicit, specifying scheme and port. */ +static int s_check_ecs_tester_request_uri_and_authorization(const char *expected_uri_cstr, const char *expected_token) { + struct aws_byte_cursor expected_uri_cursor = aws_byte_cursor_from_c_str(expected_uri_cstr); + struct aws_uri expected_uri; + ASSERT_SUCCESS(aws_uri_init_parse(&expected_uri, s_tester.allocator, &expected_uri_cursor)); + ASSERT_TRUE(aws_uri_scheme(&expected_uri)->len != 0); + ASSERT_TRUE(aws_uri_port(&expected_uri) != 0); + + ASSERT_CURSOR_VALUE_STRING_EQUALS(*aws_uri_host_name(&expected_uri), s_tester.selected_host); + ASSERT_CURSOR_VALUE_STRING_EQUALS(*aws_uri_path_and_query(&expected_uri), s_tester.request_path_and_query); + ASSERT_INT_EQUALS( + aws_byte_cursor_eq_c_str_ignore_case(aws_uri_scheme(&expected_uri), "https"), s_tester.selected_tls); + ASSERT_UINT_EQUALS(aws_uri_port(&expected_uri), s_tester.selected_port); + + if (expected_token != NULL) { + ASSERT_STR_EQUALS(expected_token, aws_string_c_str(s_tester.request_authorization_header)); + } else { + ASSERT_NULL(s_tester.request_authorization_header); + } + + aws_uri_clean_up(&expected_uri); + return 0; +} + static int s_do_ecs_success_test( struct aws_allocator *allocator, - struct aws_credentials_provider_ecs_options *options) { + struct aws_credentials_provider_ecs_options *options, + const char *expected_uri, + const char *expected_token) { struct aws_credentials_provider *provider = aws_credentials_provider_new_ecs(allocator, options); aws_credentials_provider_get_credentials(provider, s_get_credentials_callback, NULL); @@ -476,11 +534,75 @@ static int s_do_ecs_success_test( s_aws_wait_for_credentials_result(); aws_mutex_lock(&s_tester.lock); - ASSERT_BIN_ARRAYS_EQUALS( - s_tester.request_uri.buffer, - s_tester.request_uri.len, - s_expected_ecs_relative_uri->bytes, - s_expected_ecs_relative_uri->len); + ASSERT_SUCCESS(s_check_ecs_tester_request_uri_and_authorization(expected_uri, expected_token)); + ASSERT_TRUE(s_tester.has_received_credentials_callback == true); + ASSERT_TRUE(s_tester.credentials != NULL); + ASSERT_CURSOR_VALUE_STRING_EQUALS(aws_credentials_get_access_key_id(s_tester.credentials), s_good_access_key_id); + ASSERT_CURSOR_VALUE_STRING_EQUALS( + aws_credentials_get_secret_access_key(s_tester.credentials), s_good_secret_access_key); + ASSERT_CURSOR_VALUE_STRING_EQUALS(aws_credentials_get_session_token(s_tester.credentials), s_good_session_token); + + struct aws_date_time expiration; + struct aws_byte_cursor date_cursor = aws_byte_cursor_from_string(s_good_response_expiration); + aws_date_time_init_from_str_cursor(&expiration, &date_cursor, AWS_DATE_FORMAT_ISO_8601); + ASSERT_TRUE( + aws_credentials_get_expiration_timepoint_seconds(s_tester.credentials) == (uint64_t)expiration.timestamp); + aws_mutex_unlock(&s_tester.lock); + + aws_credentials_provider_release(provider); + + s_aws_wait_for_provider_shutdown_callback(); + + /* Because we mock the http connection manager, we never get a callback back from it */ + aws_mem_release(provider->allocator, provider); + + return AWS_OP_SUCCESS; +} + +static int s_do_ecs_env_success_test( + struct aws_allocator *allocator, + struct aws_credentials_provider_ecs_environment_options *options, + const char *relative_uri, + const char *full_uri, + const char *auth_token, + const char *auth_token_file_content, + const char *expected_uri, + const char *expected_token) { + + struct aws_string *relative_uri_str = NULL; + if (relative_uri != NULL) { + relative_uri_str = aws_string_new_from_c_str(allocator, relative_uri); + ASSERT_SUCCESS(aws_set_environment_value(s_ecs_creds_env_relative_uri, relative_uri_str)); + } + struct aws_string *full_uri_str = NULL; + if (full_uri != NULL) { + full_uri_str = aws_string_new_from_c_str(allocator, full_uri); + ASSERT_SUCCESS(aws_set_environment_value(s_ecs_creds_env_full_uri, full_uri_str)); + } + struct aws_string *auth_token_str = NULL; + if (auth_token != NULL) { + auth_token_str = aws_string_new_from_c_str(allocator, auth_token); + ASSERT_SUCCESS(aws_set_environment_value(s_ecs_creds_env_token, auth_token_str)); + } + struct aws_string *auth_token_file_contents_str = NULL; + struct aws_string *auth_token_file_path = NULL; + if (auth_token_file_content != NULL) { + auth_token_file_contents_str = aws_string_new_from_c_str(allocator, auth_token_file_content); + + auth_token_file_path = aws_create_process_unique_file_name(allocator); + ASSERT_NOT_NULL(auth_token_file_path); + ASSERT_TRUE(aws_create_profile_file(auth_token_file_path, auth_token_file_contents_str) == AWS_OP_SUCCESS); + ASSERT_SUCCESS(aws_set_environment_value(s_ecs_creds_env_token_file, auth_token_file_path)); + } + + struct aws_credentials_provider *provider = aws_credentials_provider_new_ecs_from_environment(allocator, options); + + aws_credentials_provider_get_credentials(provider, s_get_credentials_callback, NULL); + + s_aws_wait_for_credentials_result(); + + aws_mutex_lock(&s_tester.lock); + ASSERT_SUCCESS(s_check_ecs_tester_request_uri_and_authorization(expected_uri, expected_token)); ASSERT_TRUE(s_tester.has_received_credentials_callback == true); ASSERT_TRUE(s_tester.credentials != NULL); @@ -502,6 +624,14 @@ static int s_do_ecs_success_test( /* Because we mock the http connection manager, we never get a callback back from it */ aws_mem_release(provider->allocator, provider); + if (auth_token_file_path != NULL) { + aws_file_delete(auth_token_file_path); + } + aws_string_destroy(relative_uri_str); + aws_string_destroy(full_uri_str); + aws_string_destroy(auth_token_str); + aws_string_destroy(auth_token_file_contents_str); + aws_string_destroy(auth_token_file_path); return AWS_OP_SUCCESS; } @@ -515,7 +645,7 @@ static int s_credentials_provider_ecs_basic_success(struct aws_allocator *alloca aws_array_list_push_back(&s_tester.response_data_callbacks, &good_response_cursor); struct aws_credentials_provider_ecs_options options = { - .bootstrap = NULL, + .bootstrap = s_tester.bootstrap, .function_table = &s_mock_function_table, .shutdown_options = { @@ -527,7 +657,11 @@ static int s_credentials_provider_ecs_basic_success(struct aws_allocator *alloca .auth_token = aws_byte_cursor_from_c_str("test-token-1234-abcd"), }; - ASSERT_SUCCESS(s_do_ecs_success_test(allocator, &options)); + ASSERT_SUCCESS(s_do_ecs_success_test( + allocator, + &options, + "http://www.xxx123321testmocknonexsitingawsservice.com:80/path/to/resource/?a=b&c=d" /*expected_uri*/, + "test-token-1234-abcd" /*expected_token*/)); s_aws_ecs_tester_cleanup(); @@ -536,29 +670,23 @@ 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, + .bootstrap = s_tester.bootstrap, .function_table = &s_mock_function_table, .shutdown_options = { @@ -567,119 +695,185 @@ static int s_credentials_provider_ecs_basic_success_token_file(struct aws_alloca }, .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 = aws_byte_cursor_from_string(auth_token), + .auth_token_file_path = aws_byte_cursor_from_string(token_file_path), }; - 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); + struct aws_credentials_provider *provider = aws_credentials_provider_new_ecs(allocator, &options); + + aws_credentials_provider_get_credentials(provider, s_get_credentials_callback, NULL); + + s_aws_wait_for_credentials_result(); + + aws_mutex_lock(&s_tester.lock); + ASSERT_SUCCESS(s_check_ecs_tester_request_uri_and_authorization( + "http://www.xxx123321testmocknonexsitingawsservice.com:80/path/to/resource/?a=b&c=d", + aws_string_c_str(auth_token))); + + aws_string_destroy(s_tester.request_path_and_query); + aws_string_destroy(s_tester.request_authorization_header); + aws_credentials_release(s_tester.credentials); + + aws_mutex_unlock(&s_tester.lock); /* 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); + aws_credentials_provider_get_credentials(provider, s_get_credentials_callback, NULL); - 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_wait_for_credentials_result(); - 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); + aws_mutex_lock(&s_tester.lock); + ASSERT_SUCCESS(s_check_ecs_tester_request_uri_and_authorization( + "http://www.xxx123321testmocknonexsitingawsservice.com:80/path/to/resource/?a=b&c=d", + aws_string_c_str(auth_token2))); -static int s_credentials_provider_ecs_basic_success_token_env(struct aws_allocator *allocator, void *ctx) { - (void)ctx; + aws_mutex_unlock(&s_tester.lock); - s_aws_ecs_tester_init(allocator); + aws_credentials_provider_release(provider); - 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"), - }; + s_aws_wait_for_provider_shutdown_callback(); - 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); + /* 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(); + 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_env, s_credentials_provider_ecs_basic_success_token_env); +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_with_parameter_token( - struct aws_allocator *allocator, - void *ctx) { +static int s_credentials_provider_ecs_basic_success_uri_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"); - 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, + aws_auth_library_init(allocator); + const struct test_case { + const char *relative_uri; + const char *full_uri; + const char *expected_uri; + const char *auth_token; + const char *auth_token_file_content; + const char *expected_auth_token; + } test_cases[] = { + /* simple full uri*/ + { + .full_uri = "http://127.0.0.1/credentials", + .expected_uri = "http://127.0.0.1:80/credentials", + }, + /* explicit port */ + { + .full_uri = "http://127.0.0.1:8080/credentials", + .expected_uri = "http://127.0.0.1:8080/credentials", + }, + /* https */ + { + .full_uri = "https://www.xxx123321testmocknonexsitingawsservice.com/credentials", + .expected_uri = "https://www.xxx123321testmocknonexsitingawsservice.com:443/credentials", + }, + /* path and query */ + { + .full_uri = "http://127.0.0.1/path/to/resource/?a=b&c=d", + .expected_uri = "http://127.0.0.1:80/path/to/resource/?a=b&c=d", + }, + /* relative URI */ + { + .relative_uri = "/path/to/resource/?a=b&c=d", + .expected_uri = "http://169.254.170.2:80/path/to/resource/?a=b&c=d", + }, + /* relative URI takes priority, when both RELATIVE and FULL are set */ + { + .relative_uri = "/from-relative-uri", + .full_uri = "http://127.0.0.1/from-full-uri", + .expected_uri = "http://169.254.170.2:80/from-relative-uri", + }, + /* auth token is properly set */ + { + .full_uri = "http://127.0.0.1:8080/credentials", + .expected_uri = "http://127.0.0.1:8080/credentials", + .auth_token = "testToken", + .expected_auth_token = "testToken", + }, + /* auth_token is respected */ + { + .full_uri = "http://127.0.0.1:8080/credentials", + .expected_uri = "http://127.0.0.1:8080/credentials", + .auth_token = "testToken", + .expected_auth_token = "testToken", + }, + /* auth_token_file_path is respected */ + { + .full_uri = "http://127.0.0.1:8080/credentials", + .expected_uri = "http://127.0.0.1:8080/credentials", + .auth_token_file_content = "testToken", + .expected_auth_token = "testToken", + }, + /* auth_token_file_path is preferred */ + { + .full_uri = "http://127.0.0.1:8080/credentials", + .expected_uri = "http://127.0.0.1:8080/credentials", + .auth_token = "BadToken", + .auth_token_file_content = "testToken", + .expected_auth_token = "testToken", + }, }; - 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); + /* Provide tls_ctx, in case FULL_URI scheme is "https://" */ + struct aws_tls_ctx_options tls_options; + aws_tls_ctx_options_init_default_client(&tls_options, allocator); + struct aws_tls_ctx *tls_ctx = aws_tls_client_ctx_new(allocator, &tls_options); + ASSERT_NOT_NULL(tls_ctx); + + for (size_t case_idx = 0; case_idx < AWS_ARRAY_SIZE(test_cases); ++case_idx) { + struct test_case case_i = test_cases[case_idx]; + printf( + "CASE[%zu]: AWS_CONTAINER_CREDENTIALS_RELATIVE_URI=%s AWS_CONTAINER_CREDENTIALS_FULL_URI=%s\n, " + "AWS_CONTAINER_AUTHORIZATION_TOKEN=%s\n, auth_token_file_content=%s\n", + case_idx, + case_i.relative_uri ? case_i.relative_uri : "", + case_i.full_uri ? case_i.full_uri : "", + case_i.auth_token ? case_i.auth_token : "", + case_i.auth_token_file_content ? case_i.auth_token_file_content : ""); + + /* This unsets previous env vars */ + ASSERT_SUCCESS(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_credentials_provider_ecs_environment_options options = { + .bootstrap = s_tester.bootstrap, + .function_table = &s_mock_function_table, + .shutdown_options = + { + .shutdown_callback = s_on_shutdown_complete, + .shutdown_user_data = NULL, + }, + .tls_ctx = tls_ctx, + }; + + ASSERT_SUCCESS(s_do_ecs_env_success_test( + allocator, + &options, + case_i.relative_uri, + case_i.full_uri, + case_i.auth_token, + case_i.auth_token_file_content, + case_i.expected_uri, + case_i.expected_auth_token)); + + s_aws_ecs_tester_reset(); + } + aws_tls_ctx_release(tls_ctx); + aws_tls_ctx_options_clean_up(&tls_options); 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); +AWS_TEST_CASE(credentials_provider_ecs_basic_success_uri_env, s_credentials_provider_ecs_basic_success_uri_env); static int s_credentials_provider_ecs_no_auth_token_success(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -690,7 +884,7 @@ static int s_credentials_provider_ecs_no_auth_token_success(struct aws_allocator aws_array_list_push_back(&s_tester.response_data_callbacks, &good_response_cursor); struct aws_credentials_provider_ecs_options options = { - .bootstrap = NULL, + .bootstrap = s_tester.bootstrap, .function_table = &s_mock_function_table, .shutdown_options = { @@ -701,10 +895,13 @@ static int s_credentials_provider_ecs_no_auth_token_success(struct aws_allocator .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_SUCCESS(s_do_ecs_success_test( + allocator, + &options, + "http://www.xxx123321testmocknonexsitingawsservice.com:80/path/to/resource/?a=b&c=d" /*expected_uri*/, + NULL /*expected_token*/)); s_aws_ecs_tester_cleanup(); - ASSERT_TRUE(s_tester.request_authorization_header.len == 0); return 0; } @@ -728,7 +925,7 @@ static int s_credentials_provider_ecs_success_multi_part_doc(struct aws_allocato aws_array_list_push_back(&s_tester.response_data_callbacks, &good_response_cursor3); struct aws_credentials_provider_ecs_options options = { - .bootstrap = NULL, + .bootstrap = s_tester.bootstrap, .function_table = &s_mock_function_table, .shutdown_options = { @@ -747,11 +944,7 @@ static int s_credentials_provider_ecs_success_multi_part_doc(struct aws_allocato s_aws_wait_for_credentials_result(); aws_mutex_lock(&s_tester.lock); - ASSERT_BIN_ARRAYS_EQUALS( - s_tester.request_uri.buffer, - s_tester.request_uri.len, - s_expected_ecs_relative_uri->bytes, - s_expected_ecs_relative_uri->len); + ASSERT_STR_EQUALS("/path/to/resource/?a=b&c=d", aws_string_c_str(s_tester.request_path_and_query)); ASSERT_TRUE(s_tester.has_received_credentials_callback == true); ASSERT_TRUE(s_tester.credentials != NULL); diff --git a/tests/credentials_provider_sts_tests.c b/tests/credentials_provider_sts_tests.c index 719e2e82..64542426 100644 --- a/tests/credentials_provider_sts_tests.c +++ b/tests/credentials_provider_sts_tests.c @@ -43,13 +43,15 @@ struct aws_mock_sts_tester { int mock_failure_code; size_t fail_operations; - struct aws_byte_buf mock_body; + struct aws_array_list response_data_callbacks; struct aws_mutex lock; struct aws_condition_variable signal; struct aws_credentials *credentials; bool has_received_credentials_callback; + int mocked_connection_manager_shutdown_callback_count; + int expected_connection_manager_shutdown_callback_count; int error_code; bool fail_connection; @@ -65,18 +67,49 @@ struct aws_mock_sts_tester { static struct aws_mock_sts_tester s_tester; +static void s_on_connection_manager_shutdown_complete(void *user_data) { + (void)user_data; + + aws_mutex_lock(&s_tester.lock); + s_tester.mocked_connection_manager_shutdown_callback_count++; + aws_mutex_unlock(&s_tester.lock); + + aws_condition_variable_notify_one(&s_tester.signal); +} + +static bool s_has_tester_received_shutdown_callback(void *user_data) { + (void)user_data; + + return s_tester.mocked_connection_manager_shutdown_callback_count == + s_tester.expected_connection_manager_shutdown_callback_count; +} + +static void s_aws_wait_for_provider_shutdown_callback(void) { + aws_mutex_lock(&s_tester.lock); + aws_condition_variable_wait_pred(&s_tester.signal, &s_tester.lock, s_has_tester_received_shutdown_callback, NULL); + aws_mutex_unlock(&s_tester.lock); +} + static struct aws_http_connection_manager *s_aws_http_connection_manager_new_mock( struct aws_allocator *allocator, const struct aws_http_connection_manager_options *options) { - - (void)allocator; - (void)options; - - return (struct aws_http_connection_manager *)1; + /* copy the shutdown callback */ + struct aws_shutdown_callback_options *shutdown_callback = + aws_mem_calloc(allocator, 1, sizeof(struct aws_shutdown_callback_options)); + shutdown_callback->shutdown_callback_fn = options->shutdown_complete_callback; + shutdown_callback->shutdown_callback_user_data = options->shutdown_complete_user_data; + return (struct aws_http_connection_manager *)shutdown_callback; } static void s_aws_http_connection_manager_release_mock(struct aws_http_connection_manager *manager) { (void)manager; + /* invoke the shutdown callback */ + struct aws_shutdown_callback_options *shutdown_callback = (struct aws_shutdown_callback_options *)manager; + if (shutdown_callback->shutdown_callback_fn != NULL) { + shutdown_callback->shutdown_callback_fn(shutdown_callback->shutdown_callback_user_data); + } + aws_mem_release(s_tester.allocator, shutdown_callback); + s_on_connection_manager_shutdown_complete(NULL); } static void s_aws_http_connection_manager_acquire_connection_mock( @@ -125,7 +158,8 @@ static void s_invoke_mock_request_callbacks( options->on_response_header_block_done((struct aws_http_stream *)1, true, options->user_data); } - struct aws_byte_cursor data_callback_cur = aws_byte_cursor_from_buf(&s_tester.mock_body); + struct aws_byte_cursor data_callback_cur; + aws_array_list_get_at(&s_tester.response_data_callbacks, &data_callback_cur, s_tester.num_request - 1); options->on_response_body((struct aws_http_stream *)1, &data_callback_cur, options->user_data); options->on_complete( @@ -174,12 +208,12 @@ static struct aws_http_stream *s_aws_http_connection_make_request_mock( struct aws_input_stream *input_stream = aws_http_message_get_body_stream(options->request); int64_t body_len = 0; - - aws_input_stream_get_length(input_stream, &body_len); - aws_byte_buf_clean_up(&mocked_request->body); - aws_byte_buf_init(&mocked_request->body, s_tester.allocator, (size_t)body_len); - aws_input_stream_read(input_stream, &mocked_request->body); - + if (input_stream != NULL) { + aws_input_stream_get_length(input_stream, &body_len); + aws_byte_buf_clean_up(&mocked_request->body); + aws_byte_buf_init(&mocked_request->body, s_tester.allocator, (size_t)body_len); + aws_input_stream_read(input_stream, &mocked_request->body); + } bool fail_request = false; if (s_tester.fail_operations) { @@ -231,7 +265,7 @@ static struct aws_auth_http_system_vtable s_mock_function_table = { static int s_aws_sts_tester_init(struct aws_allocator *allocator) { AWS_ZERO_STRUCT(s_tester); s_tester.allocator = allocator; - + s_tester.expected_connection_manager_shutdown_callback_count = 1; aws_auth_library_init(allocator); if (aws_mutex_init(&s_tester.lock)) { @@ -262,6 +296,10 @@ static int s_aws_sts_tester_init(struct aws_allocator *allocator) { ASSERT_NOT_NULL(s_tester.tls_ctx); aws_tls_ctx_options_clean_up(&tls_options); + if (aws_array_list_init_dynamic(&s_tester.response_data_callbacks, allocator, 10, sizeof(struct aws_byte_cursor))) { + return AWS_OP_ERR; + } + return AWS_OP_SUCCESS; } @@ -291,7 +329,7 @@ static int s_aws_sts_tester_cleanup(void) { aws_condition_variable_clean_up(&s_tester.signal); aws_mutex_clean_up(&s_tester.lock); - aws_byte_buf_clean_up(&s_tester.mock_body); + aws_array_list_clean_up(&s_tester.response_data_callbacks); aws_client_bootstrap_release(s_tester.bootstrap); aws_host_resolver_release(s_tester.resolver); @@ -335,21 +373,22 @@ static struct aws_byte_cursor s_role_arn_cur = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("arn:aws:iam::67895:role/test_role"); static struct aws_byte_cursor s_session_name_cur = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("test_session"); -static const char *success_creds_doc = "\n" - " \n" - " \n" - " \n" - " \n" - " accessKeyIdResp\n" - " secretKeyResp\n" - " sessionTokenResp\n" - " \n" - " \n" - " ... a bunch of other stuff we don't care about\n" - " \n" - " ... more stuff we don't care about\n" - " \n" - ""; +static struct aws_byte_cursor s_success_creds_doc = + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\n" + " \n" + " \n" + " \n" + " \n" + " accessKeyIdResp\n" + " secretKeyResp\n" + " sessionTokenResp\n" + " \n" + " \n" + " ... a bunch of other stuff we don't care about\n" + " \n" + " ... more stuff we don't care about\n" + " \n" + ""); static struct aws_byte_cursor s_expected_payload = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Version=2011-06-15&Action=AssumeRole&RoleArn=arn%3Aaws%3Aiam%3A%3A67895%" @@ -392,7 +431,8 @@ static int s_credentials_provider_sts_direct_config_succeeds_fn(struct aws_alloc }; mock_aws_set_system_time(0); - s_tester.mock_body = aws_byte_buf_from_c_str(success_creds_doc); + + aws_array_list_push_back(&s_tester.response_data_callbacks, &s_success_creds_doc); s_tester.mock_response_code = 200; struct aws_credentials_provider *sts_provider = aws_credentials_provider_new_sts(allocator, &options); @@ -434,6 +474,7 @@ static int s_credentials_provider_sts_direct_config_succeeds_fn(struct aws_alloc s_tester.mocked_requests[0].body.len); aws_credentials_provider_release(sts_provider); + s_aws_wait_for_provider_shutdown_callback(); aws_credentials_provider_release(static_provider); ASSERT_SUCCESS(s_aws_sts_tester_cleanup()); @@ -468,10 +509,13 @@ static int s_credentials_provider_sts_direct_config_succeeds_after_retry_fn( }; mock_aws_set_system_time(0); - s_tester.mock_body = aws_byte_buf_from_c_str(success_creds_doc); s_tester.mock_response_code = 200; s_tester.mock_failure_code = 429; s_tester.fail_operations = 2; + int expected_num_requests = 3; + for (int i = 0; i < expected_num_requests; i++) { + aws_array_list_push_back(&s_tester.response_data_callbacks, &s_success_creds_doc); + } struct aws_credentials_provider *sts_provider = aws_credentials_provider_new_sts(allocator, &options); @@ -512,6 +556,7 @@ static int s_credentials_provider_sts_direct_config_succeeds_after_retry_fn( s_tester.mocked_requests[0].body.len); aws_credentials_provider_release(sts_provider); + s_aws_wait_for_provider_shutdown_callback(); aws_credentials_provider_release(static_provider); ASSERT_SUCCESS(s_aws_sts_tester_cleanup()); @@ -522,12 +567,13 @@ AWS_TEST_CASE( credentials_provider_sts_direct_config_succeeds_after_retry, s_credentials_provider_sts_direct_config_succeeds_after_retry_fn) -static const char *malformed_creds_doc = "\n" - " \n" - " \n" - " \n" - " accessKeyIdResp\n" - " "; +static struct aws_byte_cursor s_malformed_creds_doc = + AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("\n" + " \n" + " \n" + " \n" + " accessKeyIdResp\n" + " "); static int s_credentials_provider_sts_direct_config_invalid_doc_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; @@ -553,7 +599,7 @@ static int s_credentials_provider_sts_direct_config_invalid_doc_fn(struct aws_al }; mock_aws_set_system_time(0); - s_tester.mock_body = aws_byte_buf_from_c_str(malformed_creds_doc); + aws_array_list_push_back(&s_tester.response_data_callbacks, &s_malformed_creds_doc); s_tester.mock_response_code = 200; struct aws_credentials_provider *sts_provider = aws_credentials_provider_new_sts(allocator, &options); @@ -594,6 +640,7 @@ static int s_credentials_provider_sts_direct_config_invalid_doc_fn(struct aws_al s_tester.mocked_requests[0].body.len); aws_credentials_provider_release(sts_provider); + s_aws_wait_for_provider_shutdown_callback(); aws_credentials_provider_release(static_provider); ASSERT_SUCCESS(s_aws_sts_tester_cleanup()); @@ -640,6 +687,7 @@ static int s_credentials_provider_sts_direct_config_connection_failed_fn(struct ASSERT_NULL(s_tester.credentials); aws_credentials_provider_release(sts_provider); + s_aws_wait_for_provider_shutdown_callback(); aws_credentials_provider_release(static_provider); ASSERT_SUCCESS(s_aws_sts_tester_cleanup()); @@ -675,6 +723,10 @@ static int s_credentials_provider_sts_direct_config_service_fails_fn(struct aws_ mock_aws_set_system_time(0); s_tester.mock_response_code = 529; + int expected_num_requests = 4; + for (int i = 0; i < expected_num_requests; i++) { + aws_array_list_push_back(&s_tester.response_data_callbacks, &s_malformed_creds_doc); + } struct aws_credentials_provider *sts_provider = aws_credentials_provider_new_sts(allocator, &options); @@ -685,6 +737,7 @@ static int s_credentials_provider_sts_direct_config_service_fails_fn(struct aws_ ASSERT_NULL(s_tester.credentials); aws_credentials_provider_release(sts_provider); + s_aws_wait_for_provider_shutdown_callback(); aws_credentials_provider_release(static_provider); ASSERT_SUCCESS(s_aws_sts_tester_cleanup()); @@ -729,7 +782,7 @@ static int s_credentials_provider_sts_from_profile_config_with_chain_fn(struct a aws_unset_environment_value(s_default_credentials_path_env_variable_name); s_aws_sts_tester_init(allocator); - + s_tester.expected_connection_manager_shutdown_callback_count = 3; struct aws_string *config_contents = aws_string_new_from_c_str(allocator, s_soure_profile_chain_config_file); struct aws_string *config_file_str = aws_create_process_unique_file_name(allocator); @@ -745,8 +798,10 @@ static int s_credentials_provider_sts_from_profile_config_with_chain_fn(struct a .bootstrap = s_tester.bootstrap, .function_table = &s_mock_function_table, }; - - s_tester.mock_body = aws_byte_buf_from_c_str(success_creds_doc); + int expected_num_requests = 3; + for (int i = 0; i < expected_num_requests; i++) { + aws_array_list_push_back(&s_tester.response_data_callbacks, &s_success_creds_doc); + } s_tester.mock_response_code = 200; struct aws_credentials_provider *provider = aws_credentials_provider_new_profile(allocator, &options); @@ -761,7 +816,7 @@ static int s_credentials_provider_sts_from_profile_config_with_chain_fn(struct a ASSERT_SUCCESS(s_verify_credentials(s_tester.credentials)); - ASSERT_INT_EQUALS(3, s_tester.num_request); + ASSERT_INT_EQUALS(expected_num_requests, s_tester.num_request); static struct aws_byte_cursor s_expected_request_body[] = { AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Version=2011-06-15&Action=AssumeRole&RoleArn=arn%3Aaws%3Aiam%3A%3A67897%" "3Arole%2Ftest_role&RoleSessionName=test_session3&DurationSeconds=900"), @@ -799,7 +854,7 @@ static int s_credentials_provider_sts_from_profile_config_with_chain_fn(struct a } aws_credentials_provider_release(provider); - + s_aws_wait_for_provider_shutdown_callback(); ASSERT_SUCCESS(s_aws_sts_tester_cleanup()); return AWS_OP_SUCCESS; @@ -808,6 +863,117 @@ AWS_TEST_CASE( credentials_provider_sts_from_profile_config_with_chain, s_credentials_provider_sts_from_profile_config_with_chain_fn) +AWS_STATIC_STRING_FROM_LITERAL(s_ecs_creds_env_relative_uri, "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"); +static const char *s_soure_credentials_ecs_config_file = "[default]\n" + "role_arn=arn:aws:iam::67895:role/test_role\n" + "credential_source=EcsContainer\n" + "role_session_name=test_session\n"; +static struct aws_byte_cursor s_ecs_good_response = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL( + "{\"AccessKeyId\":\"SuccessfulAccessKey\", \n \"SecretAccessKey\":\"SuccessfulSecret\", \n " + "\"Token\":\"TokenSuccess\", \n \"Expiration\":\"2020-02-25T06:03:31Z\"}"); +static int s_credentials_provider_sts_from_profile_config_with_ecs_credentials_source_fn( + struct aws_allocator *allocator, + void *ctx) { + (void)ctx; + + aws_unset_environment_value(s_default_profile_env_variable_name); + aws_unset_environment_value(s_default_config_path_env_variable_name); + aws_unset_environment_value(s_default_credentials_path_env_variable_name); + + /* set the relative uri environment variable*/ + char *relative_uri = "/test"; + struct aws_string *relative_uri_str = aws_string_new_from_c_str(allocator, relative_uri); + ASSERT_SUCCESS(aws_set_environment_value(s_ecs_creds_env_relative_uri, relative_uri_str)); + + s_aws_sts_tester_init(allocator); + /* one for ecs provdier and one for sts provider */ + s_tester.expected_connection_manager_shutdown_callback_count = 2; + struct aws_string *config_contents = aws_string_new_from_c_str(allocator, s_soure_credentials_ecs_config_file); + + struct aws_string *config_file_str = aws_create_process_unique_file_name(allocator); + struct aws_string *creds_file_str = aws_create_process_unique_file_name(allocator); + + ASSERT_SUCCESS(aws_create_profile_file(config_file_str, config_contents)); + aws_string_destroy(config_contents); + + struct aws_credentials_provider_profile_options options = { + .config_file_name_override = aws_byte_cursor_from_string(config_file_str), + .credentials_file_name_override = aws_byte_cursor_from_string(creds_file_str), + .bootstrap = s_tester.bootstrap, + .function_table = &s_mock_function_table, + }; + int expected_num_requests = 2; + aws_array_list_push_back(&s_tester.response_data_callbacks, &s_ecs_good_response); + aws_array_list_push_back(&s_tester.response_data_callbacks, &s_success_creds_doc); + + s_tester.mock_response_code = 200; + + struct aws_credentials_provider *provider = aws_credentials_provider_new_profile(allocator, &options); + ASSERT_NOT_NULL(provider); + + aws_credentials_provider_get_credentials(provider, s_get_credentials_callback, NULL); + + s_aws_wait_for_credentials_result(); + + ASSERT_SUCCESS(s_verify_credentials(s_tester.credentials)); + + ASSERT_INT_EQUALS(expected_num_requests, s_tester.num_request); + struct aws_mock_http_request expected_mocked_request[2]; + /* expected ecs request*/ + expected_mocked_request[0] = (struct aws_mock_http_request){ + .method = aws_byte_buf_from_c_str("GET"), + .host_header = aws_byte_buf_from_c_str("169.254.170.2"), + .path = aws_byte_buf_from_c_str(relative_uri), + .body = aws_byte_buf_from_c_str(""), + .had_auth_header = false, + }; + /* expected sts request */ + expected_mocked_request[1] = (struct aws_mock_http_request){ + .method = aws_byte_buf_from_c_str("POST"), + .host_header = aws_byte_buf_from_c_str("sts.amazonaws.com"), + .path = aws_byte_buf_from_c_str("/"), + .body = aws_byte_buf_from_c_str("Version=2011-06-15&Action=AssumeRole&RoleArn=arn%3Aaws%3Aiam%3A%3A67895%" + "3Arole%2Ftest_role&RoleSessionName=test_session&DurationSeconds=900"), + .had_auth_header = true, + }; + + for (int i = 0; i < expected_num_requests; i++) { + ASSERT_BIN_ARRAYS_EQUALS( + expected_mocked_request[i].method.buffer, + expected_mocked_request[i].method.len, + s_tester.mocked_requests[i].method.buffer, + s_tester.mocked_requests[i].method.len); + ASSERT_BIN_ARRAYS_EQUALS( + expected_mocked_request[i].path.buffer, + expected_mocked_request[i].path.len, + s_tester.mocked_requests[i].path.buffer, + s_tester.mocked_requests[i].path.len); + ASSERT_TRUE(s_tester.mocked_requests[i].had_auth_header == expected_mocked_request[i].had_auth_header); + ASSERT_BIN_ARRAYS_EQUALS( + expected_mocked_request[i].host_header.buffer, + expected_mocked_request[i].host_header.len, + s_tester.mocked_requests[i].host_header.buffer, + s_tester.mocked_requests[i].host_header.len); + ASSERT_BIN_ARRAYS_EQUALS( + expected_mocked_request[i].body.buffer, + expected_mocked_request[i].body.len, + s_tester.mocked_requests[i].body.buffer, + s_tester.mocked_requests[i].body.len); + } + + aws_credentials_provider_release(provider); + s_aws_wait_for_provider_shutdown_callback(); + aws_string_destroy(relative_uri_str); + aws_string_destroy(config_file_str); + aws_string_destroy(creds_file_str); + ASSERT_SUCCESS(s_aws_sts_tester_cleanup()); + + return AWS_OP_SUCCESS; +} +AWS_TEST_CASE( + credentials_provider_sts_from_profile_config_with_ecs_credentials_source, + s_credentials_provider_sts_from_profile_config_with_ecs_credentials_source_fn) + static const char *s_soure_profile_chain_and_profile_config_file = "[default]\n" "aws_access_key_id=BLAHBLAH\n" "aws_secret_access_key=BLAHBLAHBLAH\n" @@ -836,7 +1002,7 @@ static int s_credentials_provider_sts_from_profile_config_with_chain_and_profile aws_unset_environment_value(s_default_credentials_path_env_variable_name); s_aws_sts_tester_init(allocator); - + s_tester.expected_connection_manager_shutdown_callback_count = 2; struct aws_string *config_contents = aws_string_new_from_c_str(allocator, s_soure_profile_chain_and_profile_config_file); @@ -854,7 +1020,10 @@ static int s_credentials_provider_sts_from_profile_config_with_chain_and_profile .function_table = &s_mock_function_table, }; - s_tester.mock_body = aws_byte_buf_from_c_str(success_creds_doc); + int expected_num_requests = 2; + for (int i = 0; i < expected_num_requests; i++) { + aws_array_list_push_back(&s_tester.response_data_callbacks, &s_success_creds_doc); + } s_tester.mock_response_code = 200; struct aws_credentials_provider *provider = aws_credentials_provider_new_profile(allocator, &options); @@ -869,7 +1038,7 @@ static int s_credentials_provider_sts_from_profile_config_with_chain_and_profile ASSERT_SUCCESS(s_verify_credentials(s_tester.credentials)); - ASSERT_INT_EQUALS(2, s_tester.num_request); + ASSERT_INT_EQUALS(expected_num_requests, s_tester.num_request); static struct aws_byte_cursor s_expected_request_body[] = { AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("Version=2011-06-15&Action=AssumeRole&RoleArn=arn%3Aaws%3Aiam%3A%3A67896%" "3Arole%2Ftest_role&RoleSessionName=test_session2&DurationSeconds=900"), @@ -905,7 +1074,7 @@ static int s_credentials_provider_sts_from_profile_config_with_chain_and_profile } aws_credentials_provider_release(provider); - + s_aws_wait_for_provider_shutdown_callback(); ASSERT_SUCCESS(s_aws_sts_tester_cleanup()); return AWS_OP_SUCCESS; @@ -942,7 +1111,7 @@ static int s_credentials_provider_sts_from_profile_config_with_chain_and_partial aws_unset_environment_value(s_default_credentials_path_env_variable_name); s_aws_sts_tester_init(allocator); - + s_tester.expected_connection_manager_shutdown_callback_count = 2; struct aws_string *config_contents = aws_string_new_from_c_str(allocator, s_soure_profile_chain_and_partial_profile_config_file); @@ -960,7 +1129,7 @@ static int s_credentials_provider_sts_from_profile_config_with_chain_and_partial .function_table = &s_mock_function_table, }; - s_tester.mock_body = aws_byte_buf_from_c_str(success_creds_doc); + aws_array_list_push_back(&s_tester.response_data_callbacks, &s_success_creds_doc); s_tester.mock_response_code = 200; struct aws_credentials_provider *provider = aws_credentials_provider_new_profile(allocator, &options); @@ -976,7 +1145,7 @@ static int s_credentials_provider_sts_from_profile_config_with_chain_and_partial ASSERT_NULL(s_tester.credentials); ASSERT_INT_EQUALS(s_tester.error_code, AWS_AUTH_SIGNING_NO_CREDENTIALS); aws_credentials_provider_release(provider); - + s_aws_wait_for_provider_shutdown_callback(); ASSERT_SUCCESS(s_aws_sts_tester_cleanup()); return AWS_OP_SUCCESS; @@ -1003,7 +1172,6 @@ static int s_credentials_provider_sts_from_self_referencing_profile_fn(struct aw aws_unset_environment_value(s_default_credentials_path_env_variable_name); s_aws_sts_tester_init(allocator); - struct aws_string *config_contents = aws_string_new_from_c_str(allocator, s_soure_profile_self_assume_role_config_file); @@ -1021,7 +1189,7 @@ static int s_credentials_provider_sts_from_self_referencing_profile_fn(struct aw .function_table = &s_mock_function_table, }; - s_tester.mock_body = aws_byte_buf_from_c_str(success_creds_doc); + aws_array_list_push_back(&s_tester.response_data_callbacks, &s_success_creds_doc); s_tester.mock_response_code = 200; struct aws_credentials_provider *provider = aws_credentials_provider_new_profile(allocator, &options); @@ -1070,7 +1238,7 @@ static int s_credentials_provider_sts_from_self_referencing_profile_fn(struct aw } aws_credentials_provider_release(provider); - + s_aws_wait_for_provider_shutdown_callback(); ASSERT_SUCCESS(s_aws_sts_tester_cleanup()); return AWS_OP_SUCCESS; @@ -1123,7 +1291,7 @@ static int s_credentials_provider_sts_from_profile_config_with_chain_cycle_fn( .function_table = &s_mock_function_table, }; - s_tester.mock_body = aws_byte_buf_from_c_str(success_creds_doc); + aws_array_list_push_back(&s_tester.response_data_callbacks, &s_success_creds_doc); s_tester.mock_response_code = 200; struct aws_credentials_provider *provider = aws_credentials_provider_new_profile(allocator, &options); @@ -1184,7 +1352,7 @@ static int s_credentials_provider_sts_from_profile_config_with_chain_cycle_and_p .function_table = &s_mock_function_table, }; - s_tester.mock_body = aws_byte_buf_from_c_str(success_creds_doc); + aws_array_list_push_back(&s_tester.response_data_callbacks, &s_success_creds_doc); s_tester.mock_response_code = 200; struct aws_credentials_provider *provider = aws_credentials_provider_new_profile(allocator, &options); @@ -1231,7 +1399,7 @@ static int s_credentials_provider_sts_from_profile_config_succeeds( .function_table = &s_mock_function_table, }; - s_tester.mock_body = aws_byte_buf_from_c_str(success_creds_doc); + aws_array_list_push_back(&s_tester.response_data_callbacks, &s_success_creds_doc); s_tester.mock_response_code = 200; struct aws_credentials_provider *provider = aws_credentials_provider_new_profile(allocator, &options); @@ -1276,7 +1444,7 @@ static int s_credentials_provider_sts_from_profile_config_succeeds( s_tester.mocked_requests[0].body.len); aws_credentials_provider_release(provider); - + s_aws_wait_for_provider_shutdown_callback(); ASSERT_SUCCESS(s_aws_sts_tester_cleanup()); return AWS_OP_SUCCESS; @@ -1343,7 +1511,7 @@ static int s_credentials_provider_sts_from_profile_config_environment_succeeds_f .function_table = &s_mock_function_table, }; - s_tester.mock_body = aws_byte_buf_from_c_str(success_creds_doc); + aws_array_list_push_back(&s_tester.response_data_callbacks, &s_success_creds_doc); s_tester.mock_response_code = 200; struct aws_credentials_provider *provider = aws_credentials_provider_new_profile(allocator, &options); @@ -1388,6 +1556,7 @@ static int s_credentials_provider_sts_from_profile_config_environment_succeeds_f s_tester.mocked_requests[0].body.len); aws_credentials_provider_release(provider); + s_aws_wait_for_provider_shutdown_callback(); ASSERT_SUCCESS(s_aws_sts_tester_cleanup()); @@ -1433,7 +1602,7 @@ static int s_credentials_provider_sts_cache_expiration_conflict(struct aws_alloc mock_aws_set_system_time(0); mock_aws_set_high_res_time(HIGH_RES_BASE_TIME_NS); - s_tester.mock_body = aws_byte_buf_from_c_str(success_creds_doc); + aws_array_list_push_back(&s_tester.response_data_callbacks, &s_success_creds_doc); s_tester.mock_response_code = 200; struct aws_credentials_provider *sts_provider = aws_credentials_provider_new_sts(allocator, &options); @@ -1489,6 +1658,7 @@ static int s_credentials_provider_sts_cache_expiration_conflict(struct aws_alloc s_cleanup_creds_callback_data(); + aws_array_list_push_back(&s_tester.response_data_callbacks, &s_success_creds_doc); aws_credentials_provider_get_credentials(cached_provider, s_get_credentials_callback, NULL); s_aws_wait_for_credentials_result(); @@ -1502,6 +1672,7 @@ static int s_credentials_provider_sts_cache_expiration_conflict(struct aws_alloc s_cleanup_creds_callback_data(); + aws_array_list_push_back(&s_tester.response_data_callbacks, &s_success_creds_doc); aws_credentials_provider_get_credentials(cached_provider, s_get_credentials_callback, NULL); s_aws_wait_for_credentials_result(); @@ -1509,6 +1680,7 @@ static int s_credentials_provider_sts_cache_expiration_conflict(struct aws_alloc aws_credentials_provider_release(cached_provider); aws_credentials_provider_release(sts_provider); + s_aws_wait_for_provider_shutdown_callback(); aws_credentials_provider_release(static_provider); ASSERT_SUCCESS(s_aws_sts_tester_cleanup());