diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..e9005d14 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,90 @@ +name: CI + +on: + push: + branches: + - '*' + - '!master' + +env: + BUILDER_VERSION: v0.3.1 + BUILDER_HOST: https://d19elf31gohf1l.cloudfront.net + PACKAGE_NAME: aws-c-auth + LINUX_BASE_IMAGE: ubuntu-16-x64 + +jobs: + linux-compat: + runs-on: ubuntu-latest + strategy: + matrix: + image: + - manylinux1-x64 + - manylinux1-x86 + - manylinux2014-x64 + - manylinux2014-x86 + steps: + # We can't use the `uses: docker://image` version yet, GitHub lacks authentication for actions -> packages + - name: Build ${{ env.PACKAGE_NAME }} + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u awslabs --password-stdin + export DOCKER_IMAGE=docker.pkg.github.com/awslabs/aws-crt-builder/aws-crt-${{ matrix.image }}:${{ env.BUILDER_VERSION }} + docker pull $DOCKER_IMAGE + docker run --env GITHUB_REF $DOCKER_IMAGE -p ${{ env.PACKAGE_NAME }} build manylinux-default-default-default-default + + al2: + runs-on: ubuntu-latest + steps: + # We can't use the `uses: docker://image` version yet, GitHub lacks authentication for actions -> packages + - name: Build ${{ env.PACKAGE_NAME }} + consumers + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u awslabs --password-stdin + export DOCKER_IMAGE=docker.pkg.github.com/awslabs/aws-crt-builder/aws-crt-al2-x64:${{ env.BUILDER_VERSION }} + docker pull $DOCKER_IMAGE + docker run --env GITHUB_REF $DOCKER_IMAGE -p ${{ env.PACKAGE_NAME }} build al2-default-default-default-default-downstream + + clang-compat: + runs-on: ubuntu-latest + strategy: + matrix: + version: [3, 6, 8, 9] + steps: + # We can't use the `uses: docker://image` version yet, GitHub lacks authentication for actions -> packages + - name: Build ${{ env.PACKAGE_NAME }} + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u awslabs --password-stdin + export DOCKER_IMAGE=docker.pkg.github.com/awslabs/aws-crt-builder/aws-crt-${{ env.LINUX_BASE_IMAGE }}:${{ env.BUILDER_VERSION }} + docker pull $DOCKER_IMAGE + docker run --env GITHUB_REF $DOCKER_IMAGE -p ${{ env.PACKAGE_NAME }} build linux-clang-${{ matrix.version }}-linux-x64 + + gcc-compat: + runs-on: ubuntu-latest + strategy: + matrix: + version: [4.8, 5, 6, 7, 8] + steps: + # We can't use the `uses: docker://image` version yet, GitHub lacks authentication for actions -> packages + - name: Build ${{ env.PACKAGE_NAME }} + run: | + echo "${{ secrets.GITHUB_TOKEN }}" | docker login docker.pkg.github.com -u awslabs --password-stdin + export DOCKER_IMAGE=docker.pkg.github.com/awslabs/aws-crt-builder/aws-crt-${{ env.LINUX_BASE_IMAGE }}:${{ env.BUILDER_VERSION }} + docker pull $DOCKER_IMAGE + docker run --env GITHUB_REF $DOCKER_IMAGE -p ${{ env.PACKAGE_NAME }} build linux-gcc-${{ matrix.version }}-linux-x64 + + windows: + runs-on: windows-latest + steps: + - name: Build ${{ env.PACKAGE_NAME }} + consumers + run: | + python -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_VERSION }}/builder', 'builder.pyz')" + python builder.pyz -p ${{ env.PACKAGE_NAME }} build default-downstream + + osx: + runs-on: macos-latest + steps: + - name: Build ${{ env.PACKAGE_NAME }} + consumers + run: | + python3 -c "from urllib.request import urlretrieve; urlretrieve('${{ env.BUILDER_HOST }}/${{ env.BUILDER_VERSION }}/builder', 'builder')" + chmod a+x builder + ./builder -p ${{ env.PACKAGE_NAME }} build default-downstream + + diff --git a/.github/workflows/mac-osx-ci.yml b/.github/workflows/mac-osx-ci.yml deleted file mode 100644 index 7f2fa754..00000000 --- a/.github/workflows/mac-osx-ci.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: CI - -on: [push] - -jobs: - OSX: - - runs-on: macOS-10.14 - - steps: - - name: Checkout Source - uses: actions/checkout@v1 - - - name: Install Dependencies - run: | - brew install llvm - export PATH=$(brew --prefix llvm)/bin:$PATH - - - name: Build and test - run: | - export CODEBUILD_SRC_DIR=$(pwd) - python3 -c "from urllib.request import urlretrieve; urlretrieve('https://raw.githubusercontent.com/awslabs/aws-c-common/master/codebuild/builder.py', 'builder.py')" - python3 builder.py build macos-default-default-macos-x64 diff --git a/builder.json b/builder.json index 35794828..f4fa5192 100644 --- a/builder.json +++ b/builder.json @@ -5,5 +5,8 @@ { "name": "aws-c-cal" } ], "downstream": [ + ], + "cmake_args": [ + "-DS2N_NO_PQ_ASM=ON" ] } diff --git a/include/aws/auth/credentials.h b/include/aws/auth/credentials.h index e27f8fde..1f6a8637 100644 --- a/include/aws/auth/credentials.h +++ b/include/aws/auth/credentials.h @@ -37,6 +37,36 @@ struct aws_credentials { struct aws_string *access_key_id; struct aws_string *secret_access_key; struct aws_string *session_token; + + /* + * A timepoint, in seconds since epoch, at which the credentials should no longer be used because they + * will have expired. + * + * + * The primary purpose of this value is to allow providers to communicate to the caching provider any + * additional constraints on how the sourced credentials should be used (STS). After refreshing the cached + * credentials, the caching provider uses the following calculation to determine the next requery time: + * + * next_requery_time = now + cached_expiration_config; + * if (cached_creds->expiration_timepoint_seconds < next_requery_time) { + * next_requery_time = cached_creds->expiration_timepoint_seconds; + * + * The cached provider may, at its discretion, use a smaller requery time to avoid edge-case scenarios where + * credential expiration becomes a race condition. + * + * The following leaf providers always set this value to UINT64_MAX (indefinite): + * static + * environment + * imds + * profile_config* + * + * * - profile_config may invoke sts which will use a non-max value + * + * The following leaf providers set this value to a sensible timepoint: + * sts - value is based on current time + options->duration_seconds + * + */ + uint64_t expiration_timepoint_seconds; }; struct aws_credentials_provider; @@ -136,8 +166,9 @@ struct aws_credentials_provider_sts_options { uint16_t duration_seconds; struct aws_credentials_provider_shutdown_options shutdown_options; - /* For mocking the http layer in tests, leave NULL otherwise */ + /* For mocking, leave NULL otherwise */ struct aws_credentials_provider_system_vtable *function_table; + aws_io_clock_fn *clock_fn; }; struct aws_credentials_provider_chain_default_options { @@ -244,14 +275,6 @@ struct aws_credentials_provider *aws_credentials_provider_new_profile( struct aws_allocator *allocator, const struct aws_credentials_provider_profile_options *options); -/* - * A provider assumes an IAM role via. STS AssumeRole() API. Caches the result until options.duration expires. - */ -AWS_AUTH_API -struct aws_credentials_provider *aws_credentials_provider_new_sts_cached( - struct aws_allocator *allocator, - struct aws_credentials_provider_sts_options *options); - /* * A provider assumes an IAM role via. STS AssumeRole() API. This provider will fetch new credentials * upon each call to aws_credentials_provider_get_credentials(). If you very likely don't want this behavior, diff --git a/source/credentials.c b/source/credentials.c index f1af7fb6..4260bf57 100644 --- a/source/credentials.c +++ b/source/credentials.c @@ -61,8 +61,12 @@ struct aws_credentials *aws_credentials_new( struct aws_credentials *aws_credentials_new_copy(struct aws_allocator *allocator, struct aws_credentials *credentials) { if (credentials != NULL) { - return aws_credentials_new( + struct aws_credentials *copy = aws_credentials_new( allocator, credentials->access_key_id, credentials->secret_access_key, credentials->session_token); + + copy->expiration_timepoint_seconds = credentials->expiration_timepoint_seconds; + + return copy; } return NULL; @@ -107,6 +111,8 @@ struct aws_credentials *aws_credentials_new_from_cursors( } } + credentials->expiration_timepoint_seconds = UINT64_MAX; + return credentials; error: @@ -171,37 +177,6 @@ int aws_credentials_provider_get_credentials( return provider->vtable->get_credentials(provider, callback, user_data); } -struct aws_credentials_provider *aws_credentials_provider_new_sts_cached( - struct aws_allocator *allocator, - struct aws_credentials_provider_sts_options *options) { - struct aws_credentials_provider *direct_provider = aws_credentials_provider_new_sts(allocator, options); - - if (!direct_provider) { - return NULL; - } - - struct aws_credentials_provider_cached_options cached_options; - AWS_ZERO_STRUCT(cached_options); - - /* minimum for STS is 900 seconds*/ - if (options->duration_seconds < aws_sts_assume_role_default_duration_secs) { - options->duration_seconds = aws_sts_assume_role_default_duration_secs; - } - - /* give a 30 second grace period to avoid latency problems at the caching layer*/ - uint64_t cache_timeout_seconds = options->duration_seconds - 30; - - cached_options.source = direct_provider; - cached_options.refresh_time_in_milliseconds = - aws_timestamp_convert(cache_timeout_seconds, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_MILLIS, NULL); - cached_options.source = direct_provider; - - struct aws_credentials_provider *cached_provider = aws_credentials_provider_new_cached(allocator, &cached_options); - aws_credentials_provider_release(direct_provider); - - return cached_provider; -} - /* * Default provider chain implementation */ @@ -247,6 +222,9 @@ struct aws_credentials_provider *aws_credentials_provider_new_chain_default( } providers[index] = imds_provider; + + AWS_FATAL_ASSERT(index < AWS_ARRAY_SIZE(providers)); + struct aws_credentials_provider_chain_options chain_options; AWS_ZERO_STRUCT(chain_options); chain_options.provider_count = index; diff --git a/source/credentials_provider_cached.c b/source/credentials_provider_cached.c index 83f9bac3..ca6f246b 100644 --- a/source/credentials_provider_cached.c +++ b/source/credentials_provider_cached.c @@ -30,6 +30,8 @@ AWS_STATIC_STRING_FROM_LITERAL(s_credential_expiration_env_var, "AWS_CREDENTIAL_ */ +#define REFRESH_CREDENTIALS_EARLY_DURATION_SECONDS 30 + struct aws_credentials_provider_cached { struct aws_credentials_provider *source; struct aws_credentials_provider_shutdown_options source_shutdown_options; @@ -72,15 +74,30 @@ static void s_cached_credentials_provider_get_credentials_async_callback( aws_linked_list_swap_contents(&pending_queries, &impl->pending_queries); + uint64_t next_refresh_time_in_ns = UINT64_MAX; + if (impl->refresh_interval_in_ns > 0) { uint64_t now = 0; if (!impl->clock_fn(&now)) { - impl->next_refresh_time = now + impl->refresh_interval_in_ns; + next_refresh_time_in_ns = now + impl->refresh_interval_in_ns; + } + } + + if (credentials && credentials->expiration_timepoint_seconds < UINT64_MAX) { + if (credentials->expiration_timepoint_seconds >= REFRESH_CREDENTIALS_EARLY_DURATION_SECONDS) { + uint64_t early_refresh_time_ns = aws_timestamp_convert( + credentials->expiration_timepoint_seconds - REFRESH_CREDENTIALS_EARLY_DURATION_SECONDS, + AWS_TIMESTAMP_SECS, + AWS_TIMESTAMP_NANOS, + NULL); + if (early_refresh_time_ns < next_refresh_time_in_ns) { + next_refresh_time_in_ns = early_refresh_time_ns; + } } - } else { - impl->next_refresh_time = UINT64_MAX; } + impl->next_refresh_time = next_refresh_time_in_ns; + AWS_LOGF_DEBUG( AWS_LS_AUTH_CREDENTIALS_PROVIDER, "(id=%p) Cached credentials provider next refresh time set to %" PRIu64, @@ -131,7 +148,7 @@ static int s_cached_credentials_provider_get_credentials_async( aws_mutex_lock(&impl->lock); - if (current_time < impl->next_refresh_time) { + if (impl->cached_credentials != NULL && current_time < impl->next_refresh_time) { perform_callback = true; credentials = aws_credentials_new_copy(provider->allocator, impl->cached_credentials); } else { @@ -157,7 +174,7 @@ static int s_cached_credentials_provider_get_credentials_async( aws_credentials_provider_get_credentials( impl->source, s_cached_credentials_provider_get_credentials_async_callback, provider); - } else { + } else if (!perform_callback) { AWS_LOGF_DEBUG( AWS_LS_AUTH_CREDENTIALS_PROVIDER, "(id=%p) Cached credentials provider has expired credentials. Waiting on existing query.", diff --git a/source/credentials_provider_profile.c b/source/credentials_provider_profile.c index 0a8a168a..540119c0 100644 --- a/source/credentials_provider_profile.c +++ b/source/credentials_provider_profile.c @@ -259,7 +259,7 @@ static struct aws_credentials_provider *s_create_sts_based_provider( return NULL; } - provider = aws_credentials_provider_new_sts_cached(allocator, &sts_options); + provider = aws_credentials_provider_new_sts(allocator, &sts_options); aws_credentials_provider_release(sts_options.creds_provider); @@ -286,7 +286,7 @@ static struct aws_credentials_provider *s_create_sts_based_provider( } sts_options.creds_provider = imds_provider; - provider = aws_credentials_provider_new_sts_cached(allocator, &sts_options); + provider = aws_credentials_provider_new_sts(allocator, &sts_options); aws_credentials_provider_release(imds_provider); @@ -302,7 +302,7 @@ static struct aws_credentials_provider *s_create_sts_based_provider( } sts_options.creds_provider = env_provider; - provider = aws_credentials_provider_new_sts_cached(allocator, &sts_options); + provider = aws_credentials_provider_new_sts(allocator, &sts_options); aws_credentials_provider_release(env_provider); } else { diff --git a/source/credentials_provider_sts.c b/source/credentials_provider_sts.c index 0c77efb4..4f08dc9a 100644 --- a/source/credentials_provider_sts.c +++ b/source/credentials_provider_sts.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -87,6 +88,7 @@ struct aws_credentials_provider_sts_impl { struct aws_credentials_provider_shutdown_options source_shutdown_options; struct aws_credentials_provider_system_vtable *function_table; bool owns_ctx; + aws_io_clock_fn *clock_fn; }; struct sts_creds_provider_user_data { @@ -260,6 +262,15 @@ static void s_on_stream_complete_fn(struct aws_http_stream *stream, int error_co provider_user_data->credentials = aws_mem_calloc(provider_user_data->allocator, 1, sizeof(struct aws_credentials)); + + uint64_t now = UINT64_MAX; + if (provider_impl->clock_fn(&now) != AWS_OP_SUCCESS) { + goto finish; + } + + uint64_t now_seconds = aws_timestamp_convert(now, AWS_TIMESTAMP_NANOS, AWS_TIMESTAMP_SECS, NULL); + provider_user_data->credentials->expiration_timepoint_seconds = now_seconds + provider_impl->duration_seconds; + AWS_LOGF_DEBUG( AWS_LS_AUTH_CREDENTIALS_PROVIDER, "(credentials=%p): parsing credentials", @@ -624,6 +635,12 @@ struct aws_credentials_provider *aws_credentials_provider_new_sts( impl->duration_seconds = options->duration_seconds; + if (options->clock_fn != NULL) { + impl->clock_fn = options->clock_fn; + } else { + impl->clock_fn = aws_sys_clock_get_ticks; + } + /* minimum for STS is 900 seconds*/ if (impl->duration_seconds < aws_sts_assume_role_default_duration_secs) { impl->duration_seconds = aws_sts_assume_role_default_duration_secs; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 625c91c1..8db9fbd2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -40,6 +40,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_environment_succeeds) +add_net_test_case(credentials_provider_sts_cache_expiration_conflict) add_test_case(aws_profile_early_property_parse_failure_test) add_test_case(aws_profile_missing_bracket_parse_failure_test) diff --git a/tests/credentials_provider_sts_tests.c b/tests/credentials_provider_sts_tests.c index 20842152..5926fe9e 100644 --- a/tests/credentials_provider_sts_tests.c +++ b/tests/credentials_provider_sts_tests.c @@ -14,6 +14,7 @@ */ #include +#include #include #include #include @@ -28,6 +29,7 @@ #include #include +#include "credentials_provider_utils.h" #include "shared_credentials_test_definitions.h" struct aws_mock_sts_tester { @@ -48,6 +50,12 @@ struct aws_mock_sts_tester { bool has_received_credentials_callback; bool fail_connection; + + struct aws_event_loop_group el_group; + + struct aws_host_resolver resolver; + + struct aws_client_bootstrap *bootstrap; }; static struct aws_mock_sts_tester s_tester; @@ -199,6 +207,8 @@ static int s_aws_sts_tester_init(struct aws_allocator *allocator) { AWS_ZERO_STRUCT(s_tester); s_tester.allocator = allocator; + aws_auth_library_init(allocator); + if (aws_mutex_init(&s_tester.lock)) { return AWS_OP_ERR; } @@ -207,23 +217,50 @@ static int s_aws_sts_tester_init(struct aws_allocator *allocator) { return AWS_OP_ERR; } + aws_event_loop_group_default_init(&s_tester.el_group, allocator, 0); + + aws_host_resolver_init_default(&s_tester.resolver, allocator, 10, &s_tester.el_group); + + struct aws_client_bootstrap_options bootstrap_options = { + .event_loop_group = &s_tester.el_group, + .host_resolver = &s_tester.resolver, + }; + s_tester.bootstrap = aws_client_bootstrap_new(allocator, &bootstrap_options); + return AWS_OP_SUCCESS; } -static void s_aws_sts_tester_cleanup(void) { - - aws_condition_variable_clean_up(&s_tester.signal); - aws_mutex_clean_up(&s_tester.lock); +static void s_cleanup_creds_callback_data(void) { + aws_mutex_lock(&s_tester.lock); + s_tester.has_received_credentials_callback = false; if (s_tester.credentials) { aws_credentials_destroy(s_tester.credentials); + s_tester.credentials = NULL; } - aws_byte_buf_clean_up(&s_tester.host_header); aws_byte_buf_clean_up(&s_tester.method); aws_byte_buf_clean_up(&s_tester.request_path); - aws_byte_buf_clean_up(&s_tester.mock_body); + aws_byte_buf_clean_up(&s_tester.host_header); aws_byte_buf_clean_up(&s_tester.request_body); + + aws_mutex_unlock(&s_tester.lock); +} + +static void s_aws_sts_tester_cleanup(void) { + + s_cleanup_creds_callback_data(); + + 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_client_bootstrap_release(s_tester.bootstrap); + aws_host_resolver_clean_up(&s_tester.resolver); + aws_event_loop_group_clean_up(&s_tester.el_group); + + aws_auth_library_clean_up(); } static bool s_has_tester_received_credentials_callback(void *user_data) { @@ -281,21 +318,8 @@ static struct aws_byte_cursor s_expected_payload = static int s_credentials_provider_sts_direct_config_succeeds_fn(struct aws_allocator *allocator, void *ctx) { (void)ctx; - aws_auth_library_init(allocator); s_aws_sts_tester_init(allocator); - struct aws_event_loop_group el_group; - aws_event_loop_group_default_init(&el_group, allocator, 0); - - struct aws_host_resolver resolver; - aws_host_resolver_init_default(&resolver, allocator, 10, &el_group); - - struct aws_client_bootstrap_options bootstrap_options = { - .event_loop_group = &el_group, - .host_resolver = &resolver, - }; - struct aws_client_bootstrap *bootstrap = aws_client_bootstrap_new(allocator, &bootstrap_options); - struct aws_credentials_provider_static_options static_options = { .access_key_id = s_access_key_cur, .secret_access_key = s_secret_key_cur, @@ -305,17 +329,19 @@ static int s_credentials_provider_sts_direct_config_succeeds_fn(struct aws_alloc struct aws_credentials_provider_sts_options options = { .creds_provider = static_provider, - .bootstrap = bootstrap, + .bootstrap = s_tester.bootstrap, .role_arn = s_role_arn_cur, .session_name = s_session_name_cur, .duration_seconds = 0, .function_table = &s_mock_function_table, + .clock_fn = mock_aws_get_time, }; + mock_aws_set_time(0); s_tester.mock_body = aws_byte_buf_from_c_str(success_creds_doc); s_tester.mock_response_code = 200; - struct aws_credentials_provider *sts_provider = aws_credentials_provider_new_sts_cached(allocator, &options); + struct aws_credentials_provider *sts_provider = aws_credentials_provider_new_sts(allocator, &options); aws_credentials_provider_get_credentials(sts_provider, s_get_credentials_callback, NULL); @@ -325,6 +351,7 @@ static int s_credentials_provider_sts_direct_config_succeeds_fn(struct aws_alloc ASSERT_STR_EQUALS("accessKeyIdResp", aws_string_c_str(s_tester.credentials->access_key_id)); ASSERT_STR_EQUALS("secretKeyResp", aws_string_c_str(s_tester.credentials->secret_access_key)); ASSERT_STR_EQUALS("sessionTokenResp", aws_string_c_str(s_tester.credentials->session_token)); + ASSERT_TRUE(s_tester.credentials->expiration_timepoint_seconds == 900); const char *expected_method = "POST"; ASSERT_BIN_ARRAYS_EQUALS(expected_method, strlen(expected_method), s_tester.method.buffer, s_tester.method.len); @@ -346,11 +373,6 @@ static int s_credentials_provider_sts_direct_config_succeeds_fn(struct aws_alloc aws_credentials_provider_release(static_provider); s_aws_sts_tester_cleanup(); - aws_client_bootstrap_release(bootstrap); - aws_host_resolver_clean_up(&resolver); - aws_event_loop_group_clean_up(&el_group); - - aws_auth_library_clean_up(); return AWS_OP_SUCCESS; } @@ -366,21 +388,8 @@ static const char *malformed_creds_doc = "access_key_id)); + ASSERT_STR_EQUALS("secretKeyResp", aws_string_c_str(s_tester.credentials->secret_access_key)); + ASSERT_STR_EQUALS("sessionTokenResp", aws_string_c_str(s_tester.credentials->session_token)); + ASSERT_TRUE(s_tester.credentials->expiration_timepoint_seconds == 900); + + const char *expected_method = "POST"; + ASSERT_BIN_ARRAYS_EQUALS(expected_method, strlen(expected_method), s_tester.method.buffer, s_tester.method.len); + + const char *expected_path = "/"; + ASSERT_BIN_ARRAYS_EQUALS( + expected_path, strlen(expected_path), s_tester.request_path.buffer, s_tester.request_path.len); + + ASSERT_TRUE(s_tester.had_auth_header); + + const char *expected_host_header = "sts.amazonaws.com"; + ASSERT_BIN_ARRAYS_EQUALS( + expected_host_header, strlen(expected_host_header), s_tester.host_header.buffer, s_tester.host_header.len); + + ASSERT_BIN_ARRAYS_EQUALS( + s_expected_payload.ptr, s_expected_payload.len, s_tester.request_body.buffer, s_tester.request_body.len); + + /* advance time to a little before expiration, verify we get creds with the same expiration */ + mock_aws_set_time(aws_timestamp_convert(800, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL)); + + s_cleanup_creds_callback_data(); + + aws_credentials_provider_get_credentials(cached_provider, s_get_credentials_callback, NULL); + + s_aws_wait_for_credentials_result(); + ASSERT_TRUE(s_tester.credentials->expiration_timepoint_seconds == 900); + + /* advance time to after expiration but before cached provider timeout, verify we get new creds */ + mock_aws_set_time(aws_timestamp_convert(901, AWS_TIMESTAMP_SECS, AWS_TIMESTAMP_NANOS, NULL)); + + s_cleanup_creds_callback_data(); + + aws_credentials_provider_get_credentials(cached_provider, s_get_credentials_callback, NULL); + + s_aws_wait_for_credentials_result(); + ASSERT_TRUE(s_tester.credentials->expiration_timepoint_seconds == 1801); + + aws_credentials_provider_release(cached_provider); + aws_credentials_provider_release(sts_provider); + aws_credentials_provider_release(static_provider); + + s_aws_sts_tester_cleanup(); + + return AWS_OP_SUCCESS; +} + +AWS_TEST_CASE(credentials_provider_sts_cache_expiration_conflict, s_credentials_provider_sts_cache_expiration_conflict)