Skip to content

Commit

Permalink
Credentials expiration (#56)
Browse files Browse the repository at this point in the history
* Add optional expiration timestamp to credentials.
* Update credentials providers to use expiration timestamp when appropriate.

Co-authored-by: Jonathan M. Henson <jonathan.michael.henson@gmail.com>
  • Loading branch information
bretambrose and JonathanHenson committed Feb 24, 2020
1 parent 85dd9e3 commit 32721c6
Show file tree
Hide file tree
Showing 10 changed files with 335 additions and 202 deletions.
90 changes: 90 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -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
23 changes: 0 additions & 23 deletions .github/workflows/mac-osx-ci.yml

This file was deleted.

3 changes: 3 additions & 0 deletions builder.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@
{ "name": "aws-c-cal" }
],
"downstream": [
],
"cmake_args": [
"-DS2N_NO_PQ_ASM=ON"
]
}
41 changes: 32 additions & 9 deletions include/aws/auth/credentials.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down
42 changes: 10 additions & 32 deletions source/credentials.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -107,6 +111,8 @@ struct aws_credentials *aws_credentials_new_from_cursors(
}
}

credentials->expiration_timepoint_seconds = UINT64_MAX;

return credentials;

error:
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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;
Expand Down
27 changes: 22 additions & 5 deletions source/credentials_provider_cached.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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.",
Expand Down
6 changes: 3 additions & 3 deletions source/credentials_provider_profile.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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);

Expand All @@ -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 {
Expand Down
17 changes: 17 additions & 0 deletions source/credentials_provider_sts.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <aws/auth/signable.h>
#include <aws/auth/signing.h>
#include <aws/auth/signing_config.h>
#include <aws/common/clock.h>
#include <aws/common/mutex.h>
#include <aws/common/string.h>
#include <aws/http/connection.h>
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 32721c6

Please sign in to comment.