Skip to content

Commit 523de48

Browse files
committed
fixes sts creds provider env var resolution
1 parent 2768d01 commit 523de48

File tree

3 files changed

+132
-44
lines changed

3 files changed

+132
-44
lines changed

src/aws-cpp-sdk-core/include/aws/core/client/ClientConfiguration.h

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@
66
#pragma once
77

88
#include <aws/core/Core_EXPORTS.h>
9-
#include <aws/core/http/Scheme.h>
10-
#include <aws/core/http/Version.h>
119
#include <aws/core/Region.h>
12-
#include <aws/core/utils/memory/stl/AWSString.h>
1310
#include <aws/core/http/HttpTypes.h>
11+
#include <aws/core/http/Scheme.h>
12+
#include <aws/core/http/Version.h>
1413
#include <aws/core/utils/Array.h>
14+
#include <aws/core/utils/StringUtils.h>
15+
#include <aws/core/utils/memory/stl/AWSString.h>
1516
#include <aws/crt/Optional.h>
1617
#include <smithy/tracing/TelemetryProvider.h>
18+
1719
#include <memory>
1820

1921
namespace Aws
@@ -447,6 +449,16 @@ namespace Aws
447449
static Aws::String LoadConfigFromEnvOrProfile(const Aws::String& envKey, const Aws::String& profile,
448450
const Aws::String& profileProperty, const Aws::Vector<Aws::String>& allowedValues,
449451
const Aws::String& defaultValue);
452+
/**
453+
* A helper function to read config value from env variable or aws profile config. Addresses a problem in
454+
* LoadConfigFromEnvOrProfile where env variables values are always mapped to their lower case equivalent.
455+
* This fails for cases where ENV vars need to be case sensitive in instances like AWS_ROLE_ARN can have
456+
* camel case values.
457+
*/
458+
static Aws::String LoadConfigFromEnvOrProfileCaseSensitive(
459+
const Aws::String& envKey, const Aws::String& profile, const Aws::String& profileProperty,
460+
const Aws::Vector<Aws::String>& allowedValues, const Aws::String& defaultValue,
461+
const std::function<Aws::String(const char*)>& envValueMapping = Utils::StringUtils::ToLower);
450462

451463
/**
452464
* A wrapper for interfacing with telemetry functionality. Defaults to Noop provider.

src/aws-cpp-sdk-core/source/client/ClientConfiguration.cpp

Lines changed: 60 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,11 @@ static const char* AWS_METADATA_SERVICE_TIMEOUT_ENV_VAR = "AWS_METADATA_SERVICE_
4545
static const char* AWS_METADATA_SERVICE_TIMEOUT_CONFIG_VAR = "metadata_service_timeout";
4646
static const char* AWS_METADATA_SERVICE_NUM_ATTEMPTS_ENV_VAR = "AWS_METADATA_SERVICE_NUM_ATTEMPTS";
4747
static const char* AWS_METADATA_SERVICE_NUM_ATTEMPTS_CONFIG_VAR = "metadata_service_num_attempts";
48-
static const char* AWS_IAM_ROLE_ARN_ENV_VAR = "AWS_IAM_ROLE_ARN";
48+
static const char* AWS_IAM_ROLE_ARN_ENV_VAR = "AWS_ROLE_ARN";
49+
static const char* AWS_IAM_ROLE_ARN_ENV_VAR_COMPAT = "AWS_IAM_ROLE_ARN";
4950
static const char* AWS_IAM_ROLE_ARN_CONFIG_FILE_OPTION = "role_arn";
50-
static const char* AWS_IAM_ROLE_SESSION_NAME_ENV_VAR = "AWS_IAM_ROLE_SESSION_NAME";
51+
static const char* AWS_IAM_ROLE_SESSION_NAME_ENV_VAR = "AWS_ROLE_SESSION_NAME";
52+
static const char* AWS_IAM_ROLE_SESSION_NAME_ENV_VAR_COMPAT = "AWS_IAM_ROLE_SESSION_NAME";
5153
static const char* AWS_IAM_ROLE_SESSION_NAME_CONFIG_FILE_OPTION = "role_session_name";
5254
static const char* AWS_WEB_IDENTITY_TOKEN_FILE_ENV_VAR = "AWS_WEB_IDENTITY_TOKEN_FILE";
5355
static const char* AWS_WEB_IDENTITY_TOKEN_FILE_CONFIG_FILE_OPTION = "web_identity_token_file";
@@ -327,23 +329,35 @@ void setConfigFromEnvOrProfile(ClientConfiguration &config)
327329
// Uses default retry mode with the specified max attempts from metadata_service_num_attempts
328330
config.credentialProviderConfig.imdsConfig.imdsRetryStrategy = InitRetryStrategy(attempts, "");
329331

330-
config.credentialProviderConfig.stsCredentialsProviderConfig.roleArn = ClientConfiguration::LoadConfigFromEnvOrProfile(AWS_IAM_ROLE_ARN_ENV_VAR,
331-
config.profileName,
332-
AWS_IAM_ROLE_ARN_CONFIG_FILE_OPTION,
333-
{}, /* allowed values */
334-
"" /* default value */);
335-
336-
config.credentialProviderConfig.stsCredentialsProviderConfig.sessionName = ClientConfiguration::LoadConfigFromEnvOrProfile(AWS_IAM_ROLE_SESSION_NAME_ENV_VAR,
337-
config.profileName,
338-
AWS_IAM_ROLE_SESSION_NAME_CONFIG_FILE_OPTION,
339-
{}, /* allowed values */
340-
"" /* default value */);
341-
342-
config.credentialProviderConfig.stsCredentialsProviderConfig.tokenFilePath = ClientConfiguration::LoadConfigFromEnvOrProfile(AWS_WEB_IDENTITY_TOKEN_FILE_ENV_VAR,
343-
config.profileName,
344-
AWS_WEB_IDENTITY_TOKEN_FILE_CONFIG_FILE_OPTION,
345-
{}, /* allowed values */
346-
"" /* default value */);
332+
config.credentialProviderConfig.stsCredentialsProviderConfig.roleArn = ClientConfiguration::LoadConfigFromEnvOrProfileCaseSensitive(
333+
AWS_IAM_ROLE_ARN_ENV_VAR, config.profileName, AWS_IAM_ROLE_ARN_CONFIG_FILE_OPTION, {}, /* allowed values */
334+
"" /* default value */, [](const Aws::String& envValue) -> Aws::String { return envValue; });
335+
336+
// there was a typo in the original environment variable, this exists for backwards compatibility
337+
if (config.credentialProviderConfig.stsCredentialsProviderConfig.roleArn.empty()) {
338+
config.credentialProviderConfig.stsCredentialsProviderConfig.roleArn = ClientConfiguration::LoadConfigFromEnvOrProfileCaseSensitive(
339+
AWS_IAM_ROLE_ARN_ENV_VAR_COMPAT, config.profileName, AWS_IAM_ROLE_ARN_CONFIG_FILE_OPTION, {}, /* allowed values */
340+
"" /* default value */, [](const Aws::String& envValue) -> Aws::String { return envValue; });
341+
}
342+
343+
config.credentialProviderConfig.stsCredentialsProviderConfig.sessionName = ClientConfiguration::LoadConfigFromEnvOrProfileCaseSensitive(
344+
AWS_IAM_ROLE_SESSION_NAME_ENV_VAR, config.profileName, AWS_IAM_ROLE_SESSION_NAME_CONFIG_FILE_OPTION, {}, /* allowed values */
345+
"" /* default value */, [](const Aws::String& envValue) -> Aws::String { return envValue; });
346+
347+
// there was a typo in the original environment variable, this exists for backwards compatibility
348+
if (config.credentialProviderConfig.stsCredentialsProviderConfig.sessionName.empty()) {
349+
config.credentialProviderConfig.stsCredentialsProviderConfig.sessionName =
350+
ClientConfiguration::LoadConfigFromEnvOrProfileCaseSensitive(
351+
AWS_IAM_ROLE_SESSION_NAME_ENV_VAR_COMPAT, config.profileName, AWS_IAM_ROLE_SESSION_NAME_CONFIG_FILE_OPTION,
352+
{}, /* allowed values */
353+
"" /* default value */, [](const Aws::String& envValue) -> Aws::String { return envValue; });
354+
}
355+
356+
config.credentialProviderConfig.stsCredentialsProviderConfig.tokenFilePath =
357+
ClientConfiguration::LoadConfigFromEnvOrProfileCaseSensitive(
358+
AWS_WEB_IDENTITY_TOKEN_FILE_ENV_VAR, config.profileName, AWS_WEB_IDENTITY_TOKEN_FILE_CONFIG_FILE_OPTION,
359+
{}, /* allowed values */
360+
"" /* default value */, [](const Aws::String& envValue) -> Aws::String { return envValue; });
347361
}
348362

349363
ClientConfiguration::ClientConfiguration()
@@ -558,29 +572,35 @@ Aws::String ClientConfiguration::LoadConfigFromEnvOrProfile(const Aws::String& e
558572
const Aws::Vector<Aws::String>& allowedValues,
559573
const Aws::String& defaultValue)
560574
{
561-
Aws::String option = Aws::Environment::GetEnv(envKey.c_str());
562-
if (option.empty()) {
563-
option = Aws::Config::GetCachedConfigValue(profile, profileProperty);
564-
}
565-
option = Aws::Utils::StringUtils::ToLower(option.c_str());
566-
if (option.empty()) {
567-
return defaultValue;
568-
}
569-
570-
if (!allowedValues.empty() && std::find(allowedValues.cbegin(), allowedValues.cend(), option) == allowedValues.cend()) {
571-
Aws::OStringStream expectedStr;
572-
expectedStr << "[";
573-
for(const auto& allowed : allowedValues) {
574-
expectedStr << allowed << ";";
575-
}
576-
expectedStr << "]";
575+
return LoadConfigFromEnvOrProfileCaseSensitive(envKey, profile, profileProperty, allowedValues, defaultValue);
576+
}
577+
Aws::String ClientConfiguration::LoadConfigFromEnvOrProfileCaseSensitive(const Aws::String& envKey, const Aws::String& profile,
578+
const Aws::String& profileProperty,
579+
const Aws::Vector<Aws::String>& allowedValues,
580+
const Aws::String& defaultValue,
581+
const std::function<Aws::String(const char*)>& envValueMapping) {
582+
Aws::String option = Aws::Environment::GetEnv(envKey.c_str());
583+
if (option.empty()) {
584+
option = Aws::Config::GetCachedConfigValue(profile, profileProperty);
585+
}
586+
option = envValueMapping(option.c_str());
587+
if (option.empty()) {
588+
return defaultValue;
589+
}
577590

578-
AWS_LOGSTREAM_WARN(CLIENT_CONFIG_TAG, "Unrecognised value for " << envKey << ": " << option <<
579-
". Using default instead: " << defaultValue <<
580-
". Expected empty or one of: " << expectedStr.str());
581-
option = defaultValue;
591+
if (!allowedValues.empty() && std::find(allowedValues.cbegin(), allowedValues.cend(), option) == allowedValues.cend()) {
592+
Aws::OStringStream expectedStr;
593+
expectedStr << "[";
594+
for (const auto& allowed : allowedValues) {
595+
expectedStr << allowed << ";";
582596
}
583-
return option;
597+
expectedStr << "]";
598+
599+
AWS_LOGSTREAM_WARN(CLIENT_CONFIG_TAG, "Unrecognised value for " << envKey << ": " << option << ". Using default instead: "
600+
<< defaultValue << ". Expected empty or one of: " << expectedStr.str());
601+
option = defaultValue;
602+
}
603+
return option;
584604
}
585605

586606
} // namespace Client

tests/aws-cpp-sdk-core-integration-tests/STSWebIdentityProviderIntegrationTest.cpp

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
#include <aws/cognito-identity/model/SetIdentityPoolRolesRequest.h>
77
#include <aws/core/Aws.h>
88
#include <aws/core/auth/STSCredentialsProvider.h>
9-
#include <aws/core/utils/FileSystemUtils.h>
9+
#include <aws/core/client/ClientConfiguration.h>
1010
#include <aws/core/platform/Environment.h>
11+
#include <aws/core/utils/FileSystemUtils.h>
1112
#include <aws/iam/IAMClient.h>
1213
#include <aws/iam/model/CreateRoleRequest.h>
1314
#include <aws/iam/model/DeleteRolePolicyRequest.h>
@@ -16,10 +17,12 @@
1617
#include <aws/sts/STSClient.h>
1718
#include <aws/sts/model/AssumeRoleRequest.h>
1819
#include <aws/testing/AwsTestHelpers.h>
20+
#include <aws/testing/platform/PlatformTesting.h>
1921
#include <gtest/gtest.h>
2022

2123
using namespace Aws;
2224
using namespace Aws::Client;
25+
using namespace Aws::Environment;
2326
using namespace Aws::Auth;
2427
using namespace Aws::Utils;
2528
using namespace Aws::IAM;
@@ -36,6 +39,16 @@ const char* ALLOW_S3_POLICY = R"({"Version":"2012-10-17","Statement":[{"Effect":
3639
// and we likely want to move on and fail the test
3740
const std::chrono::milliseconds IAM_CONSISTENCY_SLEEP = std::chrono::seconds(1);
3841
const size_t MAX_IAM_CONSISTENCY_RETRIES = 60;
42+
43+
void dumpConfig(const ClientConfiguration::CredentialProviderConfiguration& config) {
44+
std::cout << "DEBUG: CredentialProviderConfiguration:" << std::endl;
45+
std::cout << "DEBUG: region: " << config.region << std::endl;
46+
std::cout << "DEBUG: profile: " << config.profile << std::endl;
47+
std::cout << "DEBUG: stsCredentialsProviderConfig:" << std::endl;
48+
std::cout << "DEBUG: roleArn: " << config.stsCredentialsProviderConfig.roleArn << std::endl;
49+
std::cout << "DEBUG: tokenFilePath: " << config.stsCredentialsProviderConfig.tokenFilePath << std::endl;
50+
std::cout << "DEBUG: sessionName: " << config.stsCredentialsProviderConfig.sessionName << std::endl;
51+
}
3952
}
4053

4154
class CognitoIdentitySetup {
@@ -156,6 +169,49 @@ TEST_F(STSWebIdentityProviderIntegrationTest, ShouldWork) {
156169
config.credentialProviderConfig.region = config.region;
157170
config.credentialProviderConfig.stsCredentialsProviderConfig.roleArn = testResourcesRAII.GetRoleArn();
158171
config.credentialProviderConfig.stsCredentialsProviderConfig.tokenFilePath = testResourcesRAII.GetTokenFileName();
172+
dumpConfig(config.credentialProviderConfig);
173+
STSAssumeRoleWebIdentityCredentialsProvider provider{config.credentialProviderConfig};
174+
AWSCredentials credentials{};
175+
size_t attempts = 0;
176+
bool shouldSleep = false;
177+
do {
178+
if (shouldSleep) {
179+
std::this_thread::sleep_for(IAM_CONSISTENCY_SLEEP);
180+
}
181+
credentials = provider.GetAWSCredentials();
182+
shouldSleep = true;
183+
attempts++;
184+
} while (credentials.IsEmpty() && attempts < MAX_IAM_CONSISTENCY_RETRIES);
185+
EXPECT_FALSE(credentials.IsEmpty());
186+
}
187+
188+
TEST_F(STSWebIdentityProviderIntegrationTest, ShouldWorkWithEnvVar) {
189+
CognitoIdentitySetup testResourcesRAII{UUID::RandomUUID()};
190+
const EnvironmentRAII environmentRAII{
191+
{{"AWS_ROLE_ARN", testResourcesRAII.GetRoleArn()}, {"AWS_WEB_IDENTITY_TOKEN_FILE", testResourcesRAII.GetTokenFileName()}}};
192+
const ClientConfiguration config{};
193+
dumpConfig(config.credentialProviderConfig);
194+
STSAssumeRoleWebIdentityCredentialsProvider provider{config.credentialProviderConfig};
195+
AWSCredentials credentials{};
196+
size_t attempts = 0;
197+
bool shouldSleep = false;
198+
do {
199+
if (shouldSleep) {
200+
std::this_thread::sleep_for(IAM_CONSISTENCY_SLEEP);
201+
}
202+
credentials = provider.GetAWSCredentials();
203+
shouldSleep = true;
204+
attempts++;
205+
} while (credentials.IsEmpty() && attempts < MAX_IAM_CONSISTENCY_RETRIES);
206+
EXPECT_FALSE(credentials.IsEmpty());
207+
}
208+
209+
TEST_F(STSWebIdentityProviderIntegrationTest, ShouldWorkWithEnvVarBackwardsCompat) {
210+
CognitoIdentitySetup testResourcesRAII{UUID::RandomUUID()};
211+
const EnvironmentRAII environmentRAII{
212+
{{"AWS_IAM_ROLE_ARN", testResourcesRAII.GetRoleArn()}, {"AWS_WEB_IDENTITY_TOKEN_FILE", testResourcesRAII.GetTokenFileName()}}};
213+
const ClientConfiguration config{};
214+
dumpConfig(config.credentialProviderConfig);
159215
STSAssumeRoleWebIdentityCredentialsProvider provider{config.credentialProviderConfig};
160216
AWSCredentials credentials{};
161217
size_t attempts = 0;

0 commit comments

Comments
 (0)