diff --git a/src/aws-cpp-sdk-core/include/aws/core/auth/AWSCredentialsProvider.h b/src/aws-cpp-sdk-core/include/aws/core/auth/AWSCredentialsProvider.h index 8222cdc3361..8632fc3de70 100644 --- a/src/aws-cpp-sdk-core/include/aws/core/auth/AWSCredentialsProvider.h +++ b/src/aws-cpp-sdk-core/include/aws/core/auth/AWSCredentialsProvider.h @@ -153,7 +153,9 @@ namespace Aws * Optionally a user can specify the profile and it will override the environment variable * and defaults. To alter the file this pulls from, then the user should alter the AWS_SHARED_CREDENTIALS_FILE variable. */ - class AWS_CORE_API ProfileConfigFileAWSCredentialsProvider : public AWSCredentialsProvider + class + AWS_DEPRECATED("This class is in the maintenance mode, no new updates will be released, use S3EncryptionClientV3. Please see https://docs.aws.amazon.com/general/latest/gr/aws_sdk_cryptography.html for more information.") + AWS_CORE_API ProfileConfigFileAWSCredentialsProvider : public AWSCredentialsProvider { public: diff --git a/src/aws-cpp-sdk-core/include/aws/core/auth/ProfileCredentialsProvider.h b/src/aws-cpp-sdk-core/include/aws/core/auth/ProfileCredentialsProvider.h new file mode 100644 index 00000000000..45b8d751301 --- /dev/null +++ b/src/aws-cpp-sdk-core/include/aws/core/auth/ProfileCredentialsProvider.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace Aws { +namespace Auth { +/** + * CRT-based credentials provider that sources credentials from config files with full SEP compliance. + * Supports assume role, credential_source, role chaining, and all SEP scenarios. + */ +class AWS_CORE_API ProfileCredentialsProvider : public AWSCredentialsProvider { + public: + /** + * Initializes with refreshRateMs as the frequency at which the file is reparsed in milliseconds. Defaults to 5 minutes. + */ + ProfileCredentialsProvider(long refreshRateMs = REFRESH_THRESHOLD); + + /** + * Initializes with a profile override and + * refreshRateMs as the frequency at which the file is reparsed in milliseconds. Defaults to 5 minutes. + */ + ProfileCredentialsProvider(const char* profile, long refreshRateMs = REFRESH_THRESHOLD); + + /** + * Retrieves the credentials if found, otherwise returns empty credential set. + */ + AWSCredentials GetAWSCredentials() override; + + /** + * Returns the fullpath of the calculated credentials profile file + */ + static Aws::String GetCredentialsProfileFilename(); + + /** + * Returns the directory storing the profile file. + */ + static Aws::String GetProfileDirectory(); + + protected: + void Reload() override; + + private: + class ProfileCredentialsProviderImp; + std::shared_ptr m_impl; +}; +} // namespace Auth +} // namespace Aws diff --git a/src/aws-cpp-sdk-core/source/auth/AWSCredentialsProviderChain.cpp b/src/aws-cpp-sdk-core/source/auth/AWSCredentialsProviderChain.cpp index 9098602db24..203b856056d 100644 --- a/src/aws-cpp-sdk-core/source/auth/AWSCredentialsProviderChain.cpp +++ b/src/aws-cpp-sdk-core/source/auth/AWSCredentialsProviderChain.cpp @@ -12,6 +12,7 @@ #include #include #include +#include using namespace Aws::Auth; using namespace Aws::Utils::Threading; @@ -45,7 +46,7 @@ AWSCredentials AWSCredentialsProviderChain::GetAWSCredentials() DefaultAWSCredentialsProviderChain::DefaultAWSCredentialsProviderChain() : AWSCredentialsProviderChain() { AddProvider(Aws::MakeShared(DefaultCredentialsProviderChainTag)); - AddProvider(Aws::MakeShared(DefaultCredentialsProviderChainTag)); + AddProvider(Aws::MakeShared(DefaultCredentialsProviderChainTag)); AddProvider(Aws::MakeShared(DefaultCredentialsProviderChainTag)); AddProvider(Aws::MakeShared(DefaultCredentialsProviderChainTag)); AddProvider(Aws::MakeShared(DefaultCredentialsProviderChainTag)); @@ -90,7 +91,7 @@ DefaultAWSCredentialsProviderChain::DefaultAWSCredentialsProviderChain() : AWSCr DefaultAWSCredentialsProviderChain::DefaultAWSCredentialsProviderChain(const Aws::Client::ClientConfiguration::CredentialProviderConfiguration& config) : AWSCredentialsProviderChain() { AddProvider(Aws::MakeShared(DefaultCredentialsProviderChainTag)); - AddProvider(Aws::MakeShared(DefaultCredentialsProviderChainTag,config.profile.c_str())); + AddProvider(Aws::MakeShared(DefaultCredentialsProviderChainTag,config.profile.c_str())); AddProvider(Aws::MakeShared(DefaultCredentialsProviderChainTag,config.profile)); AddProvider(Aws::MakeShared(DefaultCredentialsProviderChainTag, config)); AddProvider(Aws::MakeShared(DefaultCredentialsProviderChainTag,config.profile)); diff --git a/src/aws-cpp-sdk-core/source/auth/ProfileCredentialsProvider.cpp b/src/aws-cpp-sdk-core/source/auth/ProfileCredentialsProvider.cpp new file mode 100644 index 00000000000..169df523d19 --- /dev/null +++ b/src/aws-cpp-sdk-core/source/auth/ProfileCredentialsProvider.cpp @@ -0,0 +1,109 @@ +#include + +#include +#include +#include +#include +#include +#include + +using namespace Aws::Auth; +using namespace Aws::Utils::Threading; + +class ProfileCredentialsProvider::ProfileCredentialsProviderImp : public AWSCredentialsProvider { + public: + ProfileCredentialsProviderImp(long refreshRateMs) + : m_profileToUse(Aws::Auth::GetConfigProfileName()), + m_credentialsFileLoader(GetCredentialsProfileFilename()), + m_loadFrequencyMs(refreshRateMs) { + AWS_LOGSTREAM_INFO(PROFILE_LOG_TAG, "Setting provider to read credentials from " + << GetCredentialsProfileFilename() << " for credentials file" + << " and " << GetConfigProfileFilename() << " for the config file " + << ", for use with profile " << m_profileToUse); + } + + ProfileCredentialsProviderImp(const char* profile, long refreshRateMs) + : m_profileToUse(profile), m_credentialsFileLoader(GetCredentialsProfileFilename()), m_loadFrequencyMs(refreshRateMs) { + AWS_LOGSTREAM_INFO(PROFILE_LOG_TAG, "Setting provider to read credentials from " + << GetCredentialsProfileFilename() << " for credentials file" + << " and " << GetConfigProfileFilename() << " for the config file " + << ", for use with profile " << m_profileToUse); + } + + static Aws::String GetCredentialsProfileFilename() { + auto credentialsFileNameFromVar = Aws::Environment::GetEnv(AWS_CREDENTIALS_FILE); + + if (credentialsFileNameFromVar.empty()) { + return Aws::FileSystem::GetHomeDirectory() + PROFILE_DIRECTORY + PATH_DELIM + DEFAULT_CREDENTIALS_FILE; + } + return credentialsFileNameFromVar; + } + + static Aws::String GetProfileDirectory() { + Aws::String credentialsFileName = GetCredentialsProfileFilename(); + auto lastSeparator = credentialsFileName.find_last_of(PATH_DELIM); + if (lastSeparator != std::string::npos) { + return credentialsFileName.substr(0, lastSeparator); + } else { + return {}; + } + } + + AWSCredentials GetAWSCredentials() override { + RefreshIfExpired(); + ReaderLockGuard guard(m_reloadLock); + const Aws::Map& profiles = m_credentialsFileLoader.GetProfiles(); + auto credsFileProfileIter = profiles.find(m_profileToUse); + + if (credsFileProfileIter != profiles.end()) { + AWSCredentials credentials = credsFileProfileIter->second.GetCredentials(); + if (!credentials.IsEmpty()) { + credentials.AddUserAgentFeature(UserAgentFeature::CREDENTIALS_PROFILE); + } + return credentials; + } + + return AWSCredentials(); + } + + void Reload() override { + m_credentialsFileLoader.Load(); + AWSCredentialsProvider::Reload(); + } + + private: + Aws::String m_profileToUse; + Aws::Config::AWSConfigFileProfileConfigLoader m_credentialsFileLoader; + long m_loadFrequencyMs; + + void RefreshIfExpired() { + ReaderLockGuard guard(m_reloadLock); + if (!IsTimeToRefresh(m_loadFrequencyMs)) { + return; + } + + guard.UpgradeToWriterLock(); + if (!IsTimeToRefresh(m_loadFrequencyMs)) // double-checked lock to avoid refreshing twice + { + return; + } + + Reload(); + } +}; + +ProfileCredentialsProvider::ProfileCredentialsProvider(long refreshRateMs) + : m_impl(std::make_shared(refreshRateMs)) {} + +ProfileCredentialsProvider::ProfileCredentialsProvider(const char* profile, long refreshRateMs) + : m_impl(std::make_shared(profile, refreshRateMs)) {} + +Aws::String ProfileCredentialsProvider::GetCredentialsProfileFilename() { + return ProfileCredentialsProviderImp::GetCredentialsProfileFilename(); +} + +Aws::String ProfileCredentialsProvider::GetProfileDirectory() { return ProfileCredentialsProviderImp::GetProfileDirectory(); } + +AWSCredentials ProfileCredentialsProvider::GetAWSCredentials() { return m_impl->GetAWSCredentials(); } + +void ProfileCredentialsProvider::Reload() { m_impl->Reload(); } \ No newline at end of file diff --git a/src/aws-cpp-sdk-core/source/auth/SSOCredentialsProvider.cpp b/src/aws-cpp-sdk-core/source/auth/SSOCredentialsProvider.cpp index fb437d291e8..c020c70f56f 100644 --- a/src/aws-cpp-sdk-core/source/auth/SSOCredentialsProvider.cpp +++ b/src/aws-cpp-sdk-core/source/auth/SSOCredentialsProvider.cpp @@ -72,7 +72,7 @@ void SSOCredentialsProvider::Reload() return token.GetToken(); } Aws::String hashedStartUrl = Aws::Utils::HashingUtils::HexEncode(Aws::Utils::HashingUtils::CalculateSHA1(profile.GetSsoStartUrl())); - auto profileDirectory = ProfileConfigFileAWSCredentialsProvider::GetProfileDirectory(); + auto profileDirectory = ProfileCredentialsProvider::GetProfileDirectory(); Aws::StringStream ssToken; ssToken << profileDirectory; ssToken << PATH_DELIM << "sso" << PATH_DELIM << "cache" << PATH_DELIM << hashedStartUrl << ".json"; diff --git a/src/aws-cpp-sdk-core/source/auth/bearer-token-provider/SSOBearerTokenProvider.cpp b/src/aws-cpp-sdk-core/source/auth/bearer-token-provider/SSOBearerTokenProvider.cpp index 05c90dac566..c33bf2ad19a 100644 --- a/src/aws-cpp-sdk-core/source/auth/bearer-token-provider/SSOBearerTokenProvider.cpp +++ b/src/aws-cpp-sdk-core/source/auth/bearer-token-provider/SSOBearerTokenProvider.cpp @@ -147,7 +147,7 @@ SSOBearerTokenProvider::CachedSsoToken SSOBearerTokenProvider::LoadAccessTokenFi } Aws::String hashedStartUrl = Aws::Utils::HashingUtils::HexEncode(Aws::Utils::HashingUtils::CalculateSHA1(profile.GetSsoSession().GetName())); - Aws::String profileDirectory = ProfileConfigFileAWSCredentialsProvider::GetProfileDirectory(); + Aws::String profileDirectory = ProfileCredentialsProvider::GetProfileDirectory(); Aws::StringStream ssToken; ssToken << profileDirectory; ssToken << Aws::FileSystem::PATH_DELIM << "sso" << Aws::FileSystem::PATH_DELIM << "cache" << Aws::FileSystem::PATH_DELIM << hashedStartUrl << ".json"; @@ -195,7 +195,7 @@ bool SSOBearerTokenProvider::WriteAccessTokenFile(const CachedSsoToken& token) c } Aws::String hashedStartUrl = Aws::Utils::HashingUtils::HexEncode(Aws::Utils::HashingUtils::CalculateSHA1(profile.GetSsoSession().GetName())); - Aws::String profileDirectory = ProfileConfigFileAWSCredentialsProvider::GetProfileDirectory(); + Aws::String profileDirectory = ProfileCredentialsProvider::GetProfileDirectory(); Aws::StringStream ssToken; ssToken << profileDirectory; ssToken << Aws::FileSystem::PATH_DELIM << "sso" << Aws::FileSystem::PATH_DELIM << "cache" << Aws::FileSystem::PATH_DELIM << hashedStartUrl << ".json"; diff --git a/tests/aws-cpp-sdk-core-tests/aws/auth/ProfileCredentialsProviderTests.cpp b/tests/aws-cpp-sdk-core-tests/aws/auth/ProfileCredentialsProviderTests.cpp new file mode 100644 index 00000000000..3d9092355b1 --- /dev/null +++ b/tests/aws-cpp-sdk-core-tests/aws/auth/ProfileCredentialsProviderTests.cpp @@ -0,0 +1,125 @@ +#include +#include +#include +#include +#include + +#include + +using namespace Aws::Auth; + +class ProfileCredentialsProviderTests : public Aws::Testing::AwsCppSdkGTestSuite { + protected: + void SetUp() override { + SaveEnv("AWS_CONFIG_FILE"); + SaveEnv("AWS_SHARED_CREDENTIALS_FILE"); + SaveEnv("AWS_PROFILE"); + + Aws::FileSystem::CreateDirectoryIfNotExists(GetTestDir().c_str()); + m_configFile = GetTestDir() + "/config"; + m_credsFile = GetTestDir() + "/credentials"; + + Aws::Environment::SetEnv("AWS_CONFIG_FILE", m_configFile.c_str(), 1); + Aws::Environment::SetEnv("AWS_SHARED_CREDENTIALS_FILE", m_credsFile.c_str(), 1); + } + + void TearDown() override { + Aws::FileSystem::RemoveFileIfExists(m_configFile.c_str()); + Aws::FileSystem::RemoveFileIfExists(m_credsFile.c_str()); + RestoreEnv(); + } + + void SaveEnv(const char* name) { m_savedEnv.emplace_back(name, Aws::Environment::GetEnv(name)); } + + void RestoreEnv() { + for (const auto& pair : m_savedEnv) { + if (pair.second.empty()) { + Aws::Environment::UnSetEnv(pair.first); + } else { + Aws::Environment::SetEnv(pair.first, pair.second.c_str(), 1); + } + } + } + + Aws::String GetTestDir() { + return Aws::FileSystem::GetHomeDirectory() + "/.aws_test_" + Aws::Utils::StringUtils::to_string(std::this_thread::get_id()); + } + + Aws::String m_configFile; + Aws::String m_credsFile; + Aws::Vector> m_savedEnv; +}; + +TEST_F(ProfileCredentialsProviderTests, LoadStaticCredentials) { + std::ofstream creds(m_credsFile.c_str()); + creds << "[default]\n"; + creds << "aws_access_key_id = AKIATEST123\n"; + creds << "aws_secret_access_key = SecretKey456\n"; + creds.close(); + + ProfileCredentialsProvider provider; + auto credentials = provider.GetAWSCredentials(); + + EXPECT_STREQ("AKIATEST123", credentials.GetAWSAccessKeyId().c_str()); + EXPECT_STREQ("SecretKey456", credentials.GetAWSSecretKey().c_str()); +} + +TEST_F(ProfileCredentialsProviderTests, LoadNamedProfile) { + std::ofstream creds(m_credsFile.c_str()); + creds << "[default]\n"; + creds << "aws_access_key_id = DefaultKey\n"; + creds << "aws_secret_access_key = DefaultSecret\n"; + creds << "\n"; + creds << "[test-profile]\n"; + creds << "aws_access_key_id = TestKey\n"; + creds << "aws_secret_access_key = TestSecret\n"; + creds.close(); + + ProfileCredentialsProvider provider("test-profile"); + auto credentials = provider.GetAWSCredentials(); + + EXPECT_STREQ("TestKey", credentials.GetAWSAccessKeyId().c_str()); + EXPECT_STREQ("TestSecret", credentials.GetAWSSecretKey().c_str()); +} + +TEST_F(ProfileCredentialsProviderTests, LoadWithSessionToken) { + std::ofstream creds(m_credsFile.c_str()); + creds << "[default]\n"; + creds << "aws_access_key_id = AKIATEST\n"; + creds << "aws_secret_access_key = SecretKey\n"; + creds << "aws_session_token = SessionToken123\n"; + creds.close(); + + ProfileCredentialsProvider provider; + auto credentials = provider.GetAWSCredentials(); + + EXPECT_STREQ("AKIATEST", credentials.GetAWSAccessKeyId().c_str()); + EXPECT_STREQ("SecretKey", credentials.GetAWSSecretKey().c_str()); + EXPECT_STREQ("SessionToken123", credentials.GetSessionToken().c_str()); +} + +TEST_F(ProfileCredentialsProviderTests, MissingProfileReturnsEmpty) { + std::ofstream creds(m_credsFile.c_str()); + creds << "[default]\n"; + creds << "aws_access_key_id = DefaultKey\n"; + creds << "aws_secret_access_key = DefaultSecret\n"; + creds.close(); + + ProfileCredentialsProvider provider("nonexistent"); + auto credentials = provider.GetAWSCredentials(); + + EXPECT_TRUE(credentials.IsEmpty()); +} + +TEST_F(ProfileCredentialsProviderTests, DISABLED_ProcessCredentials) { + std::ofstream config(m_configFile.c_str()); + config << "[default]\n"; + config << "credential_process = echo '{\"Version\": 1, \"AccessKeyId\": \"ProcessKey\", \"SecretAccessKey\": \"ProcessSecret\"}'\n"; + config.close(); + + ProfileCredentialsProvider provider; + auto credentials = provider.GetAWSCredentials(); + + EXPECT_STREQ("ProcessKey", credentials.GetAWSAccessKeyId().c_str()); + EXPECT_STREQ("ProcessSecret", credentials.GetAWSSecretKey().c_str()); +}