From 513861dc5be28a64030f0efb357760e830a7b26d Mon Sep 17 00:00:00 2001 From: sbiscigl Date: Wed, 29 Oct 2025 10:46:38 -0400 Subject: [PATCH] fixes sts creds provider env var resolution --- .../aws/core/client/ClientConfiguration.h | 18 +++- .../source/client/ClientConfiguration.cpp | 99 +++++++++++-------- .../STSWebIdentityProviderIntegrationTest.cpp | 45 ++++++++- 3 files changed, 118 insertions(+), 44 deletions(-) diff --git a/src/aws-cpp-sdk-core/include/aws/core/client/ClientConfiguration.h b/src/aws-cpp-sdk-core/include/aws/core/client/ClientConfiguration.h index e9980a99de0..a72809b240e 100644 --- a/src/aws-cpp-sdk-core/include/aws/core/client/ClientConfiguration.h +++ b/src/aws-cpp-sdk-core/include/aws/core/client/ClientConfiguration.h @@ -6,14 +6,16 @@ #pragma once #include -#include -#include #include -#include #include +#include +#include #include +#include +#include #include #include + #include namespace Aws @@ -447,6 +449,16 @@ namespace Aws static Aws::String LoadConfigFromEnvOrProfile(const Aws::String& envKey, const Aws::String& profile, const Aws::String& profileProperty, const Aws::Vector& allowedValues, const Aws::String& defaultValue); + /** + * A helper function to read config value from env variable or aws profile config. Addresses a problem in + * LoadConfigFromEnvOrProfile where env variables values are always mapped to their lower case equivalent. + * This fails for cases where ENV vars need to be case sensitive in instances like AWS_ROLE_ARN can have + * camel case values. + */ + static Aws::String LoadConfigFromEnvOrProfileCaseSensitive( + const Aws::String& envKey, const Aws::String& profile, const Aws::String& profileProperty, + const Aws::Vector& allowedValues, const Aws::String& defaultValue, + const std::function& envValueMapping = Utils::StringUtils::ToLower); /** * A wrapper for interfacing with telemetry functionality. Defaults to Noop provider. diff --git a/src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp b/src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp index 5513c03b2cc..3b427fb7ae4 100644 --- a/src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp +++ b/src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp @@ -45,9 +45,11 @@ static const char* AWS_METADATA_SERVICE_TIMEOUT_ENV_VAR = "AWS_METADATA_SERVICE_ static const char* AWS_METADATA_SERVICE_TIMEOUT_CONFIG_VAR = "metadata_service_timeout"; static const char* AWS_METADATA_SERVICE_NUM_ATTEMPTS_ENV_VAR = "AWS_METADATA_SERVICE_NUM_ATTEMPTS"; static const char* AWS_METADATA_SERVICE_NUM_ATTEMPTS_CONFIG_VAR = "metadata_service_num_attempts"; -static const char* AWS_IAM_ROLE_ARN_ENV_VAR = "AWS_IAM_ROLE_ARN"; +static const char* AWS_IAM_ROLE_ARN_ENV_VAR = "AWS_ROLE_ARN"; +static const char* AWS_IAM_ROLE_ARN_ENV_VAR_COMPAT = "AWS_IAM_ROLE_ARN"; static const char* AWS_IAM_ROLE_ARN_CONFIG_FILE_OPTION = "role_arn"; -static const char* AWS_IAM_ROLE_SESSION_NAME_ENV_VAR = "AWS_IAM_ROLE_SESSION_NAME"; +static const char* AWS_IAM_ROLE_SESSION_NAME_ENV_VAR = "AWS_ROLE_SESSION_NAME"; +static const char* AWS_IAM_ROLE_SESSION_NAME_ENV_VAR_COMPAT = "AWS_IAM_ROLE_SESSION_NAME"; static const char* AWS_IAM_ROLE_SESSION_NAME_CONFIG_FILE_OPTION = "role_session_name"; static const char* AWS_WEB_IDENTITY_TOKEN_FILE_ENV_VAR = "AWS_WEB_IDENTITY_TOKEN_FILE"; static const char* AWS_WEB_IDENTITY_TOKEN_FILE_CONFIG_FILE_OPTION = "web_identity_token_file"; @@ -327,23 +329,34 @@ void setConfigFromEnvOrProfile(ClientConfiguration &config) // Uses default retry mode with the specified max attempts from metadata_service_num_attempts config.credentialProviderConfig.imdsConfig.imdsRetryStrategy = InitRetryStrategy(attempts, ""); - config.credentialProviderConfig.stsCredentialsProviderConfig.roleArn = ClientConfiguration::LoadConfigFromEnvOrProfile(AWS_IAM_ROLE_ARN_ENV_VAR, - config.profileName, - AWS_IAM_ROLE_ARN_CONFIG_FILE_OPTION, - {}, /* allowed values */ - "" /* default value */); - - config.credentialProviderConfig.stsCredentialsProviderConfig.sessionName = ClientConfiguration::LoadConfigFromEnvOrProfile(AWS_IAM_ROLE_SESSION_NAME_ENV_VAR, - config.profileName, - AWS_IAM_ROLE_SESSION_NAME_CONFIG_FILE_OPTION, - {}, /* allowed values */ - "" /* default value */); - - config.credentialProviderConfig.stsCredentialsProviderConfig.tokenFilePath = ClientConfiguration::LoadConfigFromEnvOrProfile(AWS_WEB_IDENTITY_TOKEN_FILE_ENV_VAR, - config.profileName, - AWS_WEB_IDENTITY_TOKEN_FILE_CONFIG_FILE_OPTION, - {}, /* allowed values */ - "" /* default value */); + config.credentialProviderConfig.stsCredentialsProviderConfig.roleArn = ClientConfiguration::LoadConfigFromEnvOrProfileCaseSensitive( + AWS_IAM_ROLE_ARN_ENV_VAR_COMPAT, config.profileName, AWS_IAM_ROLE_ARN_CONFIG_FILE_OPTION, {}, /* allowed values */ + "" /* default value */, [](const Aws::String& envValue) -> Aws::String { return envValue; }); + + // there was a typo in the original environment variable, this exists for backwards compatibility + if (config.credentialProviderConfig.stsCredentialsProviderConfig.roleArn.empty()) { + config.credentialProviderConfig.stsCredentialsProviderConfig.roleArn = ClientConfiguration::LoadConfigFromEnvOrProfileCaseSensitive( + AWS_IAM_ROLE_ARN_ENV_VAR, config.profileName, AWS_IAM_ROLE_ARN_CONFIG_FILE_OPTION, {}, /* allowed values */ + "" /* default value */, [](const Aws::String& envValue) -> Aws::String { return envValue; }); + } + + config.credentialProviderConfig.stsCredentialsProviderConfig.sessionName = ClientConfiguration::LoadConfigFromEnvOrProfileCaseSensitive( + AWS_IAM_ROLE_SESSION_NAME_ENV_VAR_COMPAT, config.profileName, AWS_IAM_ROLE_SESSION_NAME_CONFIG_FILE_OPTION, {}, /* allowed values */ + "" /* default value */, [](const Aws::String& envValue) -> Aws::String { return envValue; }); + + // there was a typo in the original environment variable, this exists for backwards compatibility + if (config.credentialProviderConfig.stsCredentialsProviderConfig.sessionName.empty()) { + config.credentialProviderConfig.stsCredentialsProviderConfig.sessionName = + ClientConfiguration::LoadConfigFromEnvOrProfileCaseSensitive( + AWS_IAM_ROLE_SESSION_NAME_ENV_VAR, config.profileName, AWS_IAM_ROLE_SESSION_NAME_CONFIG_FILE_OPTION, {}, /* allowed values */ + "" /* default value */, [](const Aws::String& envValue) -> Aws::String { return envValue; }); + } + + config.credentialProviderConfig.stsCredentialsProviderConfig.tokenFilePath = + ClientConfiguration::LoadConfigFromEnvOrProfileCaseSensitive( + AWS_WEB_IDENTITY_TOKEN_FILE_ENV_VAR, config.profileName, AWS_WEB_IDENTITY_TOKEN_FILE_CONFIG_FILE_OPTION, + {}, /* allowed values */ + "" /* default value */, [](const Aws::String& envValue) -> Aws::String { return envValue; }); } ClientConfiguration::ClientConfiguration() @@ -558,29 +571,35 @@ Aws::String ClientConfiguration::LoadConfigFromEnvOrProfile(const Aws::String& e const Aws::Vector& allowedValues, const Aws::String& defaultValue) { - Aws::String option = Aws::Environment::GetEnv(envKey.c_str()); - if (option.empty()) { - option = Aws::Config::GetCachedConfigValue(profile, profileProperty); - } - option = Aws::Utils::StringUtils::ToLower(option.c_str()); - if (option.empty()) { - return defaultValue; - } - - if (!allowedValues.empty() && std::find(allowedValues.cbegin(), allowedValues.cend(), option) == allowedValues.cend()) { - Aws::OStringStream expectedStr; - expectedStr << "["; - for(const auto& allowed : allowedValues) { - expectedStr << allowed << ";"; - } - expectedStr << "]"; + return LoadConfigFromEnvOrProfileCaseSensitive(envKey, profile, profileProperty, allowedValues, defaultValue); +} +Aws::String ClientConfiguration::LoadConfigFromEnvOrProfileCaseSensitive(const Aws::String& envKey, const Aws::String& profile, + const Aws::String& profileProperty, + const Aws::Vector& allowedValues, + const Aws::String& defaultValue, + const std::function& envValueMapping) { + Aws::String option = Aws::Environment::GetEnv(envKey.c_str()); + if (option.empty()) { + option = Aws::Config::GetCachedConfigValue(profile, profileProperty); + } + option = envValueMapping(option.c_str()); + if (option.empty()) { + return defaultValue; + } - AWS_LOGSTREAM_WARN(CLIENT_CONFIG_TAG, "Unrecognised value for " << envKey << ": " << option << - ". Using default instead: " << defaultValue << - ". Expected empty or one of: " << expectedStr.str()); - option = defaultValue; + if (!allowedValues.empty() && std::find(allowedValues.cbegin(), allowedValues.cend(), option) == allowedValues.cend()) { + Aws::OStringStream expectedStr; + expectedStr << "["; + for (const auto& allowed : allowedValues) { + expectedStr << allowed << ";"; } - return option; + expectedStr << "]"; + + AWS_LOGSTREAM_WARN(CLIENT_CONFIG_TAG, "Unrecognised value for " << envKey << ": " << option << ". Using default instead: " + << defaultValue << ". Expected empty or one of: " << expectedStr.str()); + option = defaultValue; + } + return option; } } // namespace Client diff --git a/tests/aws-cpp-sdk-core-integration-tests/STSWebIdentityProviderIntegrationTest.cpp b/tests/aws-cpp-sdk-core-integration-tests/STSWebIdentityProviderIntegrationTest.cpp index 72ba701cb1b..9d7843bb305 100644 --- a/tests/aws-cpp-sdk-core-integration-tests/STSWebIdentityProviderIntegrationTest.cpp +++ b/tests/aws-cpp-sdk-core-integration-tests/STSWebIdentityProviderIntegrationTest.cpp @@ -6,8 +6,9 @@ #include #include #include -#include +#include #include +#include #include #include #include @@ -16,10 +17,12 @@ #include #include #include +#include #include using namespace Aws; using namespace Aws::Client; +using namespace Aws::Environment; using namespace Aws::Auth; using namespace Aws::Utils; using namespace Aws::IAM; @@ -170,3 +173,43 @@ TEST_F(STSWebIdentityProviderIntegrationTest, ShouldWork) { } while (credentials.IsEmpty() && attempts < MAX_IAM_CONSISTENCY_RETRIES); EXPECT_FALSE(credentials.IsEmpty()); } + +TEST_F(STSWebIdentityProviderIntegrationTest, ShouldWorkWithEnvVar) { + CognitoIdentitySetup testResourcesRAII{UUID::RandomUUID()}; + const EnvironmentRAII environmentRAII{ + {{"AWS_ROLE_ARN", testResourcesRAII.GetRoleArn()}, {"AWS_WEB_IDENTITY_TOKEN_FILE", testResourcesRAII.GetTokenFileName()}}}; + const ClientConfiguration config{}; + STSAssumeRoleWebIdentityCredentialsProvider provider{config.credentialProviderConfig}; + AWSCredentials credentials{}; + size_t attempts = 0; + bool shouldSleep = false; + do { + if (shouldSleep) { + std::this_thread::sleep_for(IAM_CONSISTENCY_SLEEP); + } + credentials = provider.GetAWSCredentials(); + shouldSleep = true; + attempts++; + } while (credentials.IsEmpty() && attempts < MAX_IAM_CONSISTENCY_RETRIES); + EXPECT_FALSE(credentials.IsEmpty()); +} + +TEST_F(STSWebIdentityProviderIntegrationTest, ShouldWorkWithEnvVarBackwardsCompat) { + CognitoIdentitySetup testResourcesRAII{UUID::RandomUUID()}; + const EnvironmentRAII environmentRAII{ + {{"AWS_IAM_ROLE_ARN", testResourcesRAII.GetRoleArn()}, {"AWS_WEB_IDENTITY_TOKEN_FILE", testResourcesRAII.GetTokenFileName()}}}; + const ClientConfiguration config{}; + STSAssumeRoleWebIdentityCredentialsProvider provider{config.credentialProviderConfig}; + AWSCredentials credentials{}; + size_t attempts = 0; + bool shouldSleep = false; + do { + if (shouldSleep) { + std::this_thread::sleep_for(IAM_CONSISTENCY_SLEEP); + } + credentials = provider.GetAWSCredentials(); + shouldSleep = true; + attempts++; + } while (credentials.IsEmpty() && attempts < MAX_IAM_CONSISTENCY_RETRIES); + EXPECT_FALSE(credentials.IsEmpty()); +}