Skip to content

Commit 6b686e2

Browse files
committed
fixes sts creds provider env var resolution
1 parent 1122413 commit 6b686e2

File tree

3 files changed

+140
-44
lines changed

3 files changed

+140
-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: 58 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,33 @@ 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 = ClientConfiguration::LoadConfigFromEnvOrProfile(
357+
AWS_WEB_IDENTITY_TOKEN_FILE_ENV_VAR, config.profileName, AWS_WEB_IDENTITY_TOKEN_FILE_CONFIG_FILE_OPTION, {}, /* allowed values */
358+
"" /* default value */);
347359
}
348360

349361
ClientConfiguration::ClientConfiguration()
@@ -558,29 +570,35 @@ Aws::String ClientConfiguration::LoadConfigFromEnvOrProfile(const Aws::String& e
558570
const Aws::Vector<Aws::String>& allowedValues,
559571
const Aws::String& defaultValue)
560572
{
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 << "]";
573+
return LoadConfigFromEnvOrProfileCaseSensitive(envKey, profile, profileProperty, allowedValues, defaultValue);
574+
}
575+
Aws::String ClientConfiguration::LoadConfigFromEnvOrProfileCaseSensitive(const Aws::String& envKey, const Aws::String& profile,
576+
const Aws::String& profileProperty,
577+
const Aws::Vector<Aws::String>& allowedValues,
578+
const Aws::String& defaultValue,
579+
const std::function<Aws::String(const char*)>& envValueMapping) {
580+
Aws::String option = Aws::Environment::GetEnv(envKey.c_str());
581+
if (option.empty()) {
582+
option = Aws::Config::GetCachedConfigValue(profile, profileProperty);
583+
}
584+
option = envValueMapping(option.c_str());
585+
if (option.empty()) {
586+
return defaultValue;
587+
}
577588

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;
589+
if (!allowedValues.empty() && std::find(allowedValues.cbegin(), allowedValues.cend(), option) == allowedValues.cend()) {
590+
Aws::OStringStream expectedStr;
591+
expectedStr << "[";
592+
for (const auto& allowed : allowedValues) {
593+
expectedStr << allowed << ";";
582594
}
583-
return option;
595+
expectedStr << "]";
596+
597+
AWS_LOGSTREAM_WARN(CLIENT_CONFIG_TAG, "Unrecognised value for " << envKey << ": " << option << ". Using default instead: "
598+
<< defaultValue << ". Expected empty or one of: " << expectedStr.str());
599+
option = defaultValue;
600+
}
601+
return option;
584602
}
585603

586604
} // namespace Client

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

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
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>
109
#include <aws/core/platform/Environment.h>
10+
#include <aws/core/utils/FileSystemUtils.h>
1111
#include <aws/iam/IAMClient.h>
1212
#include <aws/iam/model/CreateRoleRequest.h>
1313
#include <aws/iam/model/DeleteRolePolicyRequest.h>
@@ -16,10 +16,12 @@
1616
#include <aws/sts/STSClient.h>
1717
#include <aws/sts/model/AssumeRoleRequest.h>
1818
#include <aws/testing/AwsTestHelpers.h>
19+
#include <aws/testing/platform/PlatformTesting.h>
1920
#include <gtest/gtest.h>
2021

2122
using namespace Aws;
2223
using namespace Aws::Client;
24+
using namespace Aws::Environment;
2325
using namespace Aws::Auth;
2426
using namespace Aws::Utils;
2527
using namespace Aws::IAM;
@@ -29,13 +31,30 @@ using namespace Aws::STS::Model;
2931
using namespace Aws::CognitoIdentity;
3032
using namespace Aws::CognitoIdentity::Model;
3133

34+
extern char **environ;
35+
3236
namespace {
3337
const char* TRUST_POLICY = R"({"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Federated":"cognito-identity.amazonaws.com"},"Action":"sts:AssumeRoleWithWebIdentity","Condition":{"StringEquals":{"cognito-identity.amazonaws.com:aud":"IDENTITY_POOL_ID_PLACEHOLDER"},"ForAnyValue:StringLike":{"cognito-identity.amazonaws.com:amr":"unauthenticated"}}},{"Effect":"Allow","Principal":{"AWS":"arn:aws:iam::TEST_ACCOUNT_ID:root"},"Action":"sts:AssumeRole"}]})";
3438
const char* ALLOW_S3_POLICY = R"({"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:GetObject","s3:ListBucket"],"Resource":["arn:aws:s3:::example-bucket/*","arn:aws:s3:::example-bucket"]}]})";
3539
// If it takes longer than 60s for a IAM role to be consistent -- thats a problem
3640
// and we likely want to move on and fail the test
3741
const std::chrono::milliseconds IAM_CONSISTENCY_SLEEP = std::chrono::seconds(1);
3842
const size_t MAX_IAM_CONSISTENCY_RETRIES = 60;
43+
44+
void dumpEnv() {
45+
std::cout << "DEBUG: Dumping environment" << '\n';
46+
for (char **env = environ; *env != nullptr; env++) {
47+
std::cout << *env << '\n';
48+
}
49+
}
50+
51+
void dumpCredsConfig(const ClientConfiguration::CredentialProviderConfiguration& config) {
52+
std::cout << "DEBUG: Dumping credentials config" << '\n';
53+
std::cout << "DEBUG: Region: " << config.region << '\n';
54+
std::cout << "DEBUG: Role ARN: " << config.stsCredentialsProviderConfig.roleArn << '\n';
55+
std::cout << "DEBUG: Session name: " << config.stsCredentialsProviderConfig.sessionName << '\n';
56+
std::cout << "DEBUG: Token file path: " << config.stsCredentialsProviderConfig.tokenFilePath << '\n';
57+
}
3958
}
4059

4160
class CognitoIdentitySetup {
@@ -156,6 +175,53 @@ TEST_F(STSWebIdentityProviderIntegrationTest, ShouldWork) {
156175
config.credentialProviderConfig.region = config.region;
157176
config.credentialProviderConfig.stsCredentialsProviderConfig.roleArn = testResourcesRAII.GetRoleArn();
158177
config.credentialProviderConfig.stsCredentialsProviderConfig.tokenFilePath = testResourcesRAII.GetTokenFileName();
178+
dumpCredsConfig(config.credentialProviderConfig);
179+
STSAssumeRoleWebIdentityCredentialsProvider provider{config.credentialProviderConfig};
180+
AWSCredentials credentials{};
181+
size_t attempts = 0;
182+
bool shouldSleep = false;
183+
do {
184+
if (shouldSleep) {
185+
std::this_thread::sleep_for(IAM_CONSISTENCY_SLEEP);
186+
}
187+
credentials = provider.GetAWSCredentials();
188+
shouldSleep = true;
189+
attempts++;
190+
} while (credentials.IsEmpty() && attempts < MAX_IAM_CONSISTENCY_RETRIES);
191+
EXPECT_FALSE(credentials.IsEmpty());
192+
}
193+
194+
TEST_F(STSWebIdentityProviderIntegrationTest, ShouldWorkWithEnvVar) {
195+
CognitoIdentitySetup testResourcesRAII{UUID::RandomUUID()};
196+
dumpEnv();
197+
const EnvironmentRAII environmentRAII{{{"AWS_ROLE_ARN", testResourcesRAII.GetRoleArn()},
198+
{"AWS_ROLE_SESSION_NAME", UUID::RandomUUID()},
199+
{"AWS_WEB_IDENTITY_TOKEN_FILE", testResourcesRAII.GetTokenFileName()}}};
200+
const ClientConfiguration config{};
201+
dumpCredsConfig(config.credentialProviderConfig);
202+
STSAssumeRoleWebIdentityCredentialsProvider provider{config.credentialProviderConfig};
203+
AWSCredentials credentials{};
204+
size_t attempts = 0;
205+
bool shouldSleep = false;
206+
do {
207+
if (shouldSleep) {
208+
std::this_thread::sleep_for(IAM_CONSISTENCY_SLEEP);
209+
}
210+
credentials = provider.GetAWSCredentials();
211+
shouldSleep = true;
212+
attempts++;
213+
} while (credentials.IsEmpty() && attempts < MAX_IAM_CONSISTENCY_RETRIES);
214+
EXPECT_FALSE(credentials.IsEmpty());
215+
}
216+
217+
TEST_F(STSWebIdentityProviderIntegrationTest, ShouldWorkWithEnvVarBackwardsCompat) {
218+
CognitoIdentitySetup testResourcesRAII{UUID::RandomUUID()};
219+
dumpEnv();
220+
const EnvironmentRAII environmentRAII{{{"AWS_IAM_ROLE_ARN", testResourcesRAII.GetRoleArn()},
221+
{"AWS_IAM_ROLE_SESSION_NAME", UUID::RandomUUID()},
222+
{"AWS_WEB_IDENTITY_TOKEN_FILE", testResourcesRAII.GetTokenFileName()}}};
223+
const ClientConfiguration config{};
224+
dumpCredsConfig(config.credentialProviderConfig);
159225
STSAssumeRoleWebIdentityCredentialsProvider provider{config.credentialProviderConfig};
160226
AWSCredentials credentials{};
161227
size_t attempts = 0;

0 commit comments

Comments
 (0)