From aa0481ae6299ac1daf446c04a617ae82422a1fec Mon Sep 17 00:00:00 2001 From: kai lin Date: Wed, 3 Sep 2025 14:10:37 -0400 Subject: [PATCH 01/12] removed SSO checking for now # Conflicts: # src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h # src/aws-cpp-sdk-core/source/auth/AWSCredentialsProvider.cpp # src/aws-cpp-sdk-core/source/client/UserAgent.cpp # tests/aws-cpp-sdk-core-tests/aws/auth/CredentialTrackingTest.cpp # Conflicts: # tests/aws-cpp-sdk-core-tests/aws/auth/CredentialTrackingTest.cpp --- src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h | 2 +- src/aws-cpp-sdk-core/source/auth/AWSCredentialsProvider.cpp | 2 +- src/aws-cpp-sdk-core/source/client/UserAgent.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h b/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h index 4184ef8889b..6a60bee2c4d 100644 --- a/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h +++ b/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h @@ -34,7 +34,7 @@ enum class UserAgentFeature { GZIP_REQUEST_COMPRESSION, CREDENTIALS_ENV_VARS, CREDENTIALS_PROFILE, - CREDENTIALS_PROFILE_PROCESS, + CREDENTIALS_PROCESS, CREDENTIALS_IMDS, CREDENTIALS_STS_ASSUME_ROLE, CREDENTIALS_HTTP, diff --git a/src/aws-cpp-sdk-core/source/auth/AWSCredentialsProvider.cpp b/src/aws-cpp-sdk-core/source/auth/AWSCredentialsProvider.cpp index e009b64c98d..0be1130ed8c 100644 --- a/src/aws-cpp-sdk-core/source/auth/AWSCredentialsProvider.cpp +++ b/src/aws-cpp-sdk-core/source/auth/AWSCredentialsProvider.cpp @@ -366,7 +366,7 @@ void ProcessCredentialsProvider::Reload() } m_credentials = GetCredentialsFromProcess(command); if (!m_credentials.IsEmpty()) { - m_credentials.AddUserAgentFeature(UserAgentFeature::CREDENTIALS_PROFILE_PROCESS); + m_credentials.AddUserAgentFeature(UserAgentFeature::CREDENTIALS_PROCESS); } } diff --git a/src/aws-cpp-sdk-core/source/client/UserAgent.cpp b/src/aws-cpp-sdk-core/source/client/UserAgent.cpp index 935de027487..d7d1118d33b 100644 --- a/src/aws-cpp-sdk-core/source/client/UserAgent.cpp +++ b/src/aws-cpp-sdk-core/source/client/UserAgent.cpp @@ -44,7 +44,7 @@ const std::pair BUSINESS_METRIC_MAPPING[] = { {UserAgentFeature::GZIP_REQUEST_COMPRESSION, "L"}, {UserAgentFeature::CREDENTIALS_ENV_VARS, "g"}, {UserAgentFeature::CREDENTIALS_PROFILE, "n"}, - {UserAgentFeature::CREDENTIALS_PROFILE_PROCESS, "v"}, + {UserAgentFeature::CREDENTIALS_PROCESS, "w"}, {UserAgentFeature::CREDENTIALS_IMDS, "0"}, {UserAgentFeature::CREDENTIALS_STS_ASSUME_ROLE, "i"}, {UserAgentFeature::CREDENTIALS_HTTP, "z"}, From f82e9ebdf0f7265ad3bdb75468655bf33a0fb73e Mon Sep 17 00:00:00 2001 From: kai lin Date: Thu, 4 Sep 2025 12:42:12 -0400 Subject: [PATCH 02/12] tracking for process profile --- src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h | 1 + src/aws-cpp-sdk-core/source/auth/AWSCredentialsProvider.cpp | 6 +++++- src/aws-cpp-sdk-core/source/client/UserAgent.cpp | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h b/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h index 6a60bee2c4d..ecfcda30c90 100644 --- a/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h +++ b/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h @@ -35,6 +35,7 @@ enum class UserAgentFeature { CREDENTIALS_ENV_VARS, CREDENTIALS_PROFILE, CREDENTIALS_PROCESS, + CREDENTIALS_PROFILE_PROCESS, CREDENTIALS_IMDS, CREDENTIALS_STS_ASSUME_ROLE, CREDENTIALS_HTTP, diff --git a/src/aws-cpp-sdk-core/source/auth/AWSCredentialsProvider.cpp b/src/aws-cpp-sdk-core/source/auth/AWSCredentialsProvider.cpp index 0be1130ed8c..2fca063e8b8 100644 --- a/src/aws-cpp-sdk-core/source/auth/AWSCredentialsProvider.cpp +++ b/src/aws-cpp-sdk-core/source/auth/AWSCredentialsProvider.cpp @@ -366,7 +366,7 @@ void ProcessCredentialsProvider::Reload() } m_credentials = GetCredentialsFromProcess(command); if (!m_credentials.IsEmpty()) { - m_credentials.AddUserAgentFeature(UserAgentFeature::CREDENTIALS_PROCESS); + m_credentials.AddUserAgentFeature(UserAgentFeature::CREDENTIALS_PROFILE_PROCESS); } } @@ -446,6 +446,10 @@ AWSCredentials Aws::Auth::GetCredentialsFromProcess(const Aws::String& process) credentials.SetAccountId(credentialsView.GetString("AccountId")); } + if (!credentials.IsEmpty()) { + credentials.AddUserAgentFeature(UserAgentFeature::CREDENTIALS_PROCESS); + } + AWS_LOGSTREAM_DEBUG(PROFILE_LOG_TAG, "Successfully pulled credentials from process credential with AccessKey: " << accessKey << ", Expiration:" << credentialsView.GetString("Expiration")); return credentials; } diff --git a/src/aws-cpp-sdk-core/source/client/UserAgent.cpp b/src/aws-cpp-sdk-core/source/client/UserAgent.cpp index d7d1118d33b..3480350f24f 100644 --- a/src/aws-cpp-sdk-core/source/client/UserAgent.cpp +++ b/src/aws-cpp-sdk-core/source/client/UserAgent.cpp @@ -45,6 +45,7 @@ const std::pair BUSINESS_METRIC_MAPPING[] = { {UserAgentFeature::CREDENTIALS_ENV_VARS, "g"}, {UserAgentFeature::CREDENTIALS_PROFILE, "n"}, {UserAgentFeature::CREDENTIALS_PROCESS, "w"}, + {UserAgentFeature::CREDENTIALS_PROFILE_PROCESS, "v"}, {UserAgentFeature::CREDENTIALS_IMDS, "0"}, {UserAgentFeature::CREDENTIALS_STS_ASSUME_ROLE, "i"}, {UserAgentFeature::CREDENTIALS_HTTP, "z"}, From f42b0fb740d1a226aef0a2edd6496a03e83630ce Mon Sep 17 00:00:00 2001 From: kai lin Date: Thu, 4 Sep 2025 14:33:09 -0400 Subject: [PATCH 03/12] added test for sso --- src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h | 1 + src/aws-cpp-sdk-core/source/auth/SSOCredentialsProvider.cpp | 3 +++ src/aws-cpp-sdk-core/source/client/UserAgent.cpp | 1 + 3 files changed, 5 insertions(+) diff --git a/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h b/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h index ecfcda30c90..f4b380b5675 100644 --- a/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h +++ b/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h @@ -39,6 +39,7 @@ enum class UserAgentFeature { CREDENTIALS_IMDS, CREDENTIALS_STS_ASSUME_ROLE, CREDENTIALS_HTTP, + CREDENTIALS_SSO, }; class AWS_CORE_API UserAgent { diff --git a/src/aws-cpp-sdk-core/source/auth/SSOCredentialsProvider.cpp b/src/aws-cpp-sdk-core/source/auth/SSOCredentialsProvider.cpp index 60b4134c4c1..90333df8c20 100644 --- a/src/aws-cpp-sdk-core/source/auth/SSOCredentialsProvider.cpp +++ b/src/aws-cpp-sdk-core/source/auth/SSOCredentialsProvider.cpp @@ -101,6 +101,9 @@ void SSOCredentialsProvider::Reload() AWS_LOGSTREAM_TRACE(SSO_CREDENTIALS_PROVIDER_LOG_TAG, "Successfully retrieved credentials with AWS_ACCESS_KEY: " << result.creds.GetAWSAccessKeyId()); m_credentials = result.creds; + if (!m_credentials.IsEmpty()) { + m_credentials.AddUserAgentFeature(Aws::Client::UserAgentFeature::CREDENTIALS_SSO); + } } void SSOCredentialsProvider::RefreshIfExpired() diff --git a/src/aws-cpp-sdk-core/source/client/UserAgent.cpp b/src/aws-cpp-sdk-core/source/client/UserAgent.cpp index 3480350f24f..2c83c19c95d 100644 --- a/src/aws-cpp-sdk-core/source/client/UserAgent.cpp +++ b/src/aws-cpp-sdk-core/source/client/UserAgent.cpp @@ -49,6 +49,7 @@ const std::pair BUSINESS_METRIC_MAPPING[] = { {UserAgentFeature::CREDENTIALS_IMDS, "0"}, {UserAgentFeature::CREDENTIALS_STS_ASSUME_ROLE, "i"}, {UserAgentFeature::CREDENTIALS_HTTP, "z"}, + {UserAgentFeature::CREDENTIALS_SSO, "s"}, }; const std::pair RETRY_FEATURE_MAPPING[] = { From 3ef553b07a3fc335a9ede86a2384cb713dbdb805 Mon Sep 17 00:00:00 2001 From: kai lin Date: Fri, 5 Sep 2025 14:17:54 -0400 Subject: [PATCH 04/12] new sso test --- .../aws/auth/SSOCredentialTrackingTest.cpp | 257 ++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp diff --git a/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp b/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp new file mode 100644 index 00000000000..89a656a486b --- /dev/null +++ b/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp @@ -0,0 +1,257 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Aws::Client; +using namespace Aws::Auth; +using namespace Aws::Http; +using namespace Aws::FileSystem; +using namespace Aws::Http::Standard; + +namespace { +const char* TEST_LOG_TAG = "CredentialTrackingTest"; +} + + +// Custom client that uses default credential provider for testing +class CredentialTestingClient : public Aws::Client::AWSClient +{ +public: + explicit CredentialTestingClient(const Aws::Client::ClientConfiguration& configuration) + : AWSClient(configuration, + Aws::MakeShared(TEST_LOG_TAG, + Aws::MakeShared(TEST_LOG_TAG), + "service", configuration.region), + Aws::MakeShared(TEST_LOG_TAG)) + { + } + + // Constructor with custom credential provider for IMDS test + explicit CredentialTestingClient(const Aws::Client::ClientConfiguration& configuration, + std::shared_ptr credentialsProvider) + : AWSClient(configuration, + Aws::MakeShared(TEST_LOG_TAG, + credentialsProvider, + "service", configuration.region), + Aws::MakeShared(TEST_LOG_TAG)) + { + } + + Aws::Client::HttpResponseOutcome MakeRequest(const Aws::AmazonWebServiceRequest& request) + { + auto uri = Aws::Http::URI("https://test.com"); + return AWSClient::AttemptExhaustively(uri, request, Aws::Http::HttpMethod::HTTP_POST, Aws::Auth::SIGV4_SIGNER); + } + + const char* GetServiceClientName() const override { return "CredentialTestingClient"; } + +protected: + Aws::Client::AWSError BuildAWSError(const std::shared_ptr& response) const override + { + AWS_UNREFERENCED_PARAM(response); + return Aws::Client::AWSError(Aws::Client::CoreErrors::UNKNOWN, false); + } +}; + +class SSOCredentialsProviderTest : public Aws::Testing::AwsCppSdkGTestSuite +{ +protected: + void SetUp() override + { + AwsCppSdkGTestSuite::SetUp(); + + // Create test directories + Aws::String uuid = Aws::Utils::UUID::RandomUUID(); + m_testDir = "/tmp/aws_sso_test_" + uuid; + m_configPath = m_testDir + "/config"; + m_ssoDir = m_testDir + "/sso/cache"; + + Aws::FileSystem::CreateDirectoryIfNotExists(m_testDir.c_str()); + Aws::FileSystem::CreateDirectoryIfNotExists((m_testDir + "/sso").c_str()); + Aws::FileSystem::CreateDirectoryIfNotExists(m_ssoDir.c_str()); + + // Save original AWS_CONFIG_FILE value + m_originalConfigFile = Aws::Environment::GetEnv("AWS_CONFIG_FILE"); + + // Set AWS_CONFIG_FILE to our test config + Aws::Environment::SetEnv("AWS_CONFIG_FILE", m_configPath.c_str(), 1); + + // Set up mock HTTP client + mockHttpClient = Aws::MakeShared("SSOTest"); + mockHttpClientFactory = Aws::MakeShared("SSOTest"); + mockHttpClientFactory->SetClient(mockHttpClient); + SetHttpClientFactory(mockHttpClientFactory); + } + + void TearDown() override + { + // Restore original AWS_CONFIG_FILE + if (!m_originalConfigFile.empty()) + { + Aws::Environment::SetEnv("AWS_CONFIG_FILE", m_originalConfigFile.c_str(), 1); + } + else + { + Aws::Environment::UnSetEnv("AWS_CONFIG_FILE"); + } + + // Reset HTTP clients + if (mockHttpClient) { + mockHttpClient->Reset(); + mockHttpClient = nullptr; + } + if (mockHttpClientFactory) { + mockHttpClientFactory = nullptr; + } + + // Cleanup test files + Aws::FileSystem::RemoveFileIfExists(m_configPath.c_str()); + + //AwsCppSdkGTestSuite::TearDown(); + } + + void CreateTestConfig(const Aws::String& startUrl = "https://test.awsapps.com/start") + { + std::ofstream configFile(m_configPath.c_str()); + configFile << "[default]\n" + << "sso_account_id = 123456789012\n" + << "sso_region = us-east-1\n" + << "sso_role_name = TestRole\n" + << "sso_start_url = " << startUrl << std::endl; + configFile.close(); + } + + void CreateSSOTokenFile(const Aws::String& startUrl) + { + // Use a simple hash for the test (SHA1 of the start URL) + Aws::String hashedStartUrl = "d033e22ae348aeb5660fc2140aec35850c4da997"; // Simple test hash + Aws::String tokenPath = m_ssoDir + "/" + hashedStartUrl + ".json"; + + // Create token file with future expiration + std::ofstream tokenFile(tokenPath.c_str()); + auto futureTime = Aws::Utils::DateTime::Now() + std::chrono::hours(1); + + tokenFile << "{\n" + << " \"accessToken\": \"test-token\",\n" + << " \"expiresAt\": \"" << futureTime.ToGmtString(Aws::Utils::DateFormat::ISO_8601) << "\",\n" + << " \"region\": \"us-east-1\",\n" + << " \"startUrl\": \"" << startUrl << "\"\n" + << "}" << std::endl; + tokenFile.close(); + } + + + void RunTestWithCredentialsProvider(const std::shared_ptr& credentialsProvider, const Aws::String& id) { + // Setup mock response + std::shared_ptr requestTmp = + CreateHttpRequest(Aws::Http::URI("dummy"), Aws::Http::HttpMethod::HTTP_POST, + Aws::Utils::Stream::DefaultResponseStreamFactoryMethod); + auto successResponse = Aws::MakeShared(TEST_LOG_TAG, requestTmp); + successResponse->SetResponseCode(HttpResponseCode::OK); + successResponse->GetResponseBody() << "{}"; + mockHttpClient->AddResponseToReturn(successResponse); + + // Create client configuration + Aws::Client::ClientConfigurationInitValues cfgInit; + cfgInit.shouldDisableIMDS = true; + Aws::Client::ClientConfiguration clientConfig(cfgInit); + clientConfig.region = Aws::Region::US_EAST_1; + + // Create credential testing client that uses default provider chain + CredentialTestingClient client(clientConfig, credentialsProvider); + + // Create mock request + AmazonWebServiceRequestMock mockRequest; + + // Make request + auto outcome = client.MakeRequest(mockRequest); + ASSERT_TRUE(outcome.IsSuccess()); + + // Verify User-Agent contains environment credentials tracking + auto lastRequest = mockHttpClient->GetMostRecentHttpRequest(); + EXPECT_TRUE(lastRequest.HasHeader(Aws::Http::USER_AGENT_HEADER)); + const auto& userAgent = lastRequest.GetHeaderValue(Aws::Http::USER_AGENT_HEADER); + EXPECT_FALSE(userAgent.empty()); + + const auto userAgentParsed = Aws::Utils::StringUtils::Split(userAgent, ' '); + + // Verify there's only one m/ section (no duplicate m/ sections) + int mSectionCount = 0; + for (const auto& part : userAgentParsed) { + if (part.find("m/") != Aws::String::npos) { + mSectionCount++; + } + } + EXPECT_EQ(1, mSectionCount); + + // Check for environment credentials business metric (g) in user agent + auto businessMetrics = std::find_if(userAgentParsed.begin(), userAgentParsed.end(), + [&id](const Aws::String& value) { return value.find("m/") != Aws::String::npos && value.find(id) != Aws::String::npos; }); + + EXPECT_TRUE(businessMetrics != userAgentParsed.end()); + } + + Aws::String m_testDir; + Aws::String m_configPath; + Aws::String m_ssoDir; + Aws::String m_originalConfigFile; + std::shared_ptr mockHttpClient; + std::shared_ptr mockHttpClientFactory; +}; + +TEST_F(SSOCredentialsProviderTest, TestSSOCredentialsTracking) +{ + const Aws::String startUrl = "https://test.awsapps.com/start"; + + // Create test configuration + CreateTestConfig(startUrl); + CreateSSOTokenFile(startUrl); + + // Mock SSO credentials API response + std::shared_ptr ssoRequest = CreateHttpRequest( + Aws::Http::URI("https://portal.sso.us-east-1.amazonaws.com/federation/credentials"), + Aws::Http::HttpMethod::HTTP_GET, + Aws::Utils::Stream::DefaultResponseStreamFactoryMethod); + + auto ssoResponse = Aws::MakeShared("SSOTest", ssoRequest); + ssoResponse->SetResponseCode(Aws::Http::HttpResponseCode::OK); + ssoResponse->GetResponseBody() << R"({ + "roleCredentials": { + "accessKeyId": "AKIAIOSFODNN7EXAMPLE", + "secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "sessionToken": "AQoDYXdzEJr...", + "expiration": )" << (Aws::Utils::DateTime::Now().Millis() + 3600000) << R"( + } + })"; + mockHttpClient->AddResponseToReturn(ssoResponse); + + // Create SSO credentials provider as shared_ptr + auto ssoProvider = Aws::MakeShared(TEST_LOG_TAG); + + // Get credentials using regular method + auto credentials = ssoProvider->GetAWSCredentials(); + + // Verify credentials were retrieved + EXPECT_FALSE(credentials.IsEmpty()); + EXPECT_EQ("AKIAIOSFODNN7EXAMPLE", credentials.GetAWSAccessKeyId()); + EXPECT_EQ("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", credentials.GetAWSSecretKey()); + + // Test credential tracking + RunTestWithCredentialsProvider(ssoProvider, "s"); +} \ No newline at end of file From a1ca75f6388cdcaf101100b3201eae59977fa005 Mon Sep 17 00:00:00 2001 From: kai lin Date: Fri, 5 Sep 2025 15:49:11 -0400 Subject: [PATCH 05/12] fixed sso test --- .../aws/auth/SSOCredentialTrackingTest.cpp | 397 ++++++++---------- 1 file changed, 174 insertions(+), 223 deletions(-) diff --git a/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp b/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp index 89a656a486b..66ce694325d 100644 --- a/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp @@ -1,257 +1,208 @@ +// SSOCredentialTrackingTest.cpp #include -#include #include #include -#include #include + #include #include #include -#include #include -#include -#include +#include +#include #include -#include +#include +#include +#include +#include + #include -#include #include -using namespace Aws::Client; +using namespace Aws; using namespace Aws::Auth; +using namespace Aws::Client; using namespace Aws::Http; -using namespace Aws::FileSystem; using namespace Aws::Http::Standard; +using namespace Aws::Utils; +using namespace Aws::FileSystem; namespace { -const char* TEST_LOG_TAG = "CredentialTrackingTest"; +const char* TEST_LOG_TAG = "CredentialTrackingTest"; } +Aws::String computeHashedStartUrl(const Aws::String& startUrl) { + auto sha1 = HashingUtils::CalculateSHA1(startUrl); + return HashingUtils::HexEncode(sha1); // lower-case hex the same as provider +} -// Custom client that uses default credential provider for testing -class CredentialTestingClient : public Aws::Client::AWSClient -{ +// Minimal AWSClient wrapper so we can make a signed call and inspect User-Agent +class CredentialTestingClient : public AWSClient { public: - explicit CredentialTestingClient(const Aws::Client::ClientConfiguration& configuration) - : AWSClient(configuration, - Aws::MakeShared(TEST_LOG_TAG, - Aws::MakeShared(TEST_LOG_TAG), - "service", configuration.region), - Aws::MakeShared(TEST_LOG_TAG)) - { + explicit CredentialTestingClient(const ClientConfiguration& config, + const std::shared_ptr& provider) + : AWSClient(config, + Aws::MakeShared(TEST_LOG_TAG, provider, "service", config.region), + Aws::MakeShared(TEST_LOG_TAG)) {} + + HttpResponseOutcome MakeRequest(const Aws::AmazonWebServiceRequest& request) { + URI uri("https://test.com"); + return AttemptExhaustively(uri, request, HttpMethod::HTTP_POST, Aws::Auth::SIGV4_SIGNER); } + const char* GetServiceClientName() const override { return "CredentialTestingClient"; } - // Constructor with custom credential provider for IMDS test - explicit CredentialTestingClient(const Aws::Client::ClientConfiguration& configuration, - std::shared_ptr credentialsProvider) - : AWSClient(configuration, - Aws::MakeShared(TEST_LOG_TAG, - credentialsProvider, - "service", configuration.region), - Aws::MakeShared(TEST_LOG_TAG)) - { +protected: + AWSError BuildAWSError(const std::shared_ptr&) const override { + return {CoreErrors::UNKNOWN, false}; } +}; - Aws::Client::HttpResponseOutcome MakeRequest(const Aws::AmazonWebServiceRequest& request) - { - auto uri = Aws::Http::URI("https://test.com"); - return AWSClient::AttemptExhaustively(uri, request, Aws::Http::HttpMethod::HTTP_POST, Aws::Auth::SIGV4_SIGNER); +class SSOCredentialsProviderTrackingTest : public Aws::Testing::AwsCppSdkGTestSuite { +protected: + void SetUp() override { + AwsCppSdkGTestSuite::SetUp(); + + // Build paths the same way the SDK does + const Aws::String profileDir = ProfileConfigFileAWSCredentialsProvider::GetProfileDirectory(); + const Aws::String ssoDir = profileDir + PATH_DELIM + "sso"; + const Aws::String cacheDir = ssoDir + PATH_DELIM + "cache"; + + CreateDirectoryIfNotExists(profileDir.c_str()); + CreateDirectoryIfNotExists(ssoDir.c_str()); + CreateDirectoryIfNotExists(cacheDir.c_str()); + + // Point AWS_CONFIG_FILE at a unique temp path the provider will read + StringStream ss; + ss << Aws::Auth::GetConfigProfileFilename() << "_blah" << std::this_thread::get_id(); + m_configPath = ss.str(); + Aws::Environment::SetEnv("AWS_CONFIG_FILE", m_configPath.c_str(), 1); + + m_profileDir = profileDir; + m_ssoCacheDir = cacheDir; + + // Mock HTTP client + mockHttpClient = Aws::MakeShared(TEST_LOG_TAG); + mockHttpClientFactory = Aws::MakeShared(TEST_LOG_TAG); + mockHttpClientFactory->SetClient(mockHttpClient); + SetHttpClientFactory(mockHttpClientFactory); } - const char* GetServiceClientName() const override { return "CredentialTestingClient"; } + void TearDown() override { + if (mockHttpClient) { mockHttpClient->Reset(); mockHttpClient = nullptr; } + mockHttpClientFactory = nullptr; + Aws::FileSystem::RemoveFileIfExists(m_configPath.c_str()); + AwsCppSdkGTestSuite::TearDown(); + } -protected: - Aws::Client::AWSError BuildAWSError(const std::shared_ptr& response) const override - { - AWS_UNREFERENCED_PARAM(response); - return Aws::Client::AWSError(Aws::Client::CoreErrors::UNKNOWN, false); + void CreateTestConfig(const Aws::String& startUrl) { + std::ofstream cfg(m_configPath.c_str()); + cfg << "[default]\n" + "sso_account_id = 123456789012\n" + "sso_region = us-east-1\n" + "sso_role_name = TestRole\n" + "sso_start_url = " << startUrl << "\n"; + cfg.close(); + + std::ifstream check(m_configPath.c_str()); + ASSERT_TRUE(check.good()) << "Config not created at: " << m_configPath; + check.close(); + + Aws::Config::ReloadCachedConfigFile(); } -}; -class SSOCredentialsProviderTest : public Aws::Testing::AwsCppSdkGTestSuite -{ -protected: - void SetUp() override - { - AwsCppSdkGTestSuite::SetUp(); - - // Create test directories - Aws::String uuid = Aws::Utils::UUID::RandomUUID(); - m_testDir = "/tmp/aws_sso_test_" + uuid; - m_configPath = m_testDir + "/config"; - m_ssoDir = m_testDir + "/sso/cache"; - - Aws::FileSystem::CreateDirectoryIfNotExists(m_testDir.c_str()); - Aws::FileSystem::CreateDirectoryIfNotExists((m_testDir + "/sso").c_str()); - Aws::FileSystem::CreateDirectoryIfNotExists(m_ssoDir.c_str()); - - // Save original AWS_CONFIG_FILE value - m_originalConfigFile = Aws::Environment::GetEnv("AWS_CONFIG_FILE"); - - // Set AWS_CONFIG_FILE to our test config - Aws::Environment::SetEnv("AWS_CONFIG_FILE", m_configPath.c_str(), 1); - - // Set up mock HTTP client - mockHttpClient = Aws::MakeShared("SSOTest"); - mockHttpClientFactory = Aws::MakeShared("SSOTest"); - mockHttpClientFactory->SetClient(mockHttpClient); - SetHttpClientFactory(mockHttpClientFactory); - } - - void TearDown() override - { - // Restore original AWS_CONFIG_FILE - if (!m_originalConfigFile.empty()) - { - Aws::Environment::SetEnv("AWS_CONFIG_FILE", m_originalConfigFile.c_str(), 1); - } - else - { - Aws::Environment::UnSetEnv("AWS_CONFIG_FILE"); - } - - // Reset HTTP clients - if (mockHttpClient) { - mockHttpClient->Reset(); - mockHttpClient = nullptr; - } - if (mockHttpClientFactory) { - mockHttpClientFactory = nullptr; - } - - // Cleanup test files - Aws::FileSystem::RemoveFileIfExists(m_configPath.c_str()); - - //AwsCppSdkGTestSuite::TearDown(); - } - - void CreateTestConfig(const Aws::String& startUrl = "https://test.awsapps.com/start") - { - std::ofstream configFile(m_configPath.c_str()); - configFile << "[default]\n" - << "sso_account_id = 123456789012\n" - << "sso_region = us-east-1\n" - << "sso_role_name = TestRole\n" - << "sso_start_url = " << startUrl << std::endl; - configFile.close(); - } - - void CreateSSOTokenFile(const Aws::String& startUrl) - { - // Use a simple hash for the test (SHA1 of the start URL) - Aws::String hashedStartUrl = "d033e22ae348aeb5660fc2140aec35850c4da997"; // Simple test hash - Aws::String tokenPath = m_ssoDir + "/" + hashedStartUrl + ".json"; - - // Create token file with future expiration - std::ofstream tokenFile(tokenPath.c_str()); - auto futureTime = Aws::Utils::DateTime::Now() + std::chrono::hours(1); - - tokenFile << "{\n" - << " \"accessToken\": \"test-token\",\n" - << " \"expiresAt\": \"" << futureTime.ToGmtString(Aws::Utils::DateFormat::ISO_8601) << "\",\n" - << " \"region\": \"us-east-1\",\n" - << " \"startUrl\": \"" << startUrl << "\"\n" - << "}" << std::endl; - tokenFile.close(); - } - - - void RunTestWithCredentialsProvider(const std::shared_ptr& credentialsProvider, const Aws::String& id) { - // Setup mock response - std::shared_ptr requestTmp = - CreateHttpRequest(Aws::Http::URI("dummy"), Aws::Http::HttpMethod::HTTP_POST, - Aws::Utils::Stream::DefaultResponseStreamFactoryMethod); - auto successResponse = Aws::MakeShared(TEST_LOG_TAG, requestTmp); - successResponse->SetResponseCode(HttpResponseCode::OK); - successResponse->GetResponseBody() << "{}"; - mockHttpClient->AddResponseToReturn(successResponse); - - // Create client configuration - Aws::Client::ClientConfigurationInitValues cfgInit; - cfgInit.shouldDisableIMDS = true; - Aws::Client::ClientConfiguration clientConfig(cfgInit); - clientConfig.region = Aws::Region::US_EAST_1; - - // Create credential testing client that uses default provider chain - CredentialTestingClient client(clientConfig, credentialsProvider); - - // Create mock request - AmazonWebServiceRequestMock mockRequest; - - // Make request - auto outcome = client.MakeRequest(mockRequest); - ASSERT_TRUE(outcome.IsSuccess()); - - // Verify User-Agent contains environment credentials tracking - auto lastRequest = mockHttpClient->GetMostRecentHttpRequest(); - EXPECT_TRUE(lastRequest.HasHeader(Aws::Http::USER_AGENT_HEADER)); - const auto& userAgent = lastRequest.GetHeaderValue(Aws::Http::USER_AGENT_HEADER); - EXPECT_FALSE(userAgent.empty()); - - const auto userAgentParsed = Aws::Utils::StringUtils::Split(userAgent, ' '); - - // Verify there's only one m/ section (no duplicate m/ sections) - int mSectionCount = 0; - for (const auto& part : userAgentParsed) { - if (part.find("m/") != Aws::String::npos) { - mSectionCount++; - } - } - EXPECT_EQ(1, mSectionCount); - - // Check for environment credentials business metric (g) in user agent - auto businessMetrics = std::find_if(userAgentParsed.begin(), userAgentParsed.end(), - [&id](const Aws::String& value) { return value.find("m/") != Aws::String::npos && value.find(id) != Aws::String::npos; }); - - EXPECT_TRUE(businessMetrics != userAgentParsed.end()); - } - - Aws::String m_testDir; - Aws::String m_configPath; - Aws::String m_ssoDir; - Aws::String m_originalConfigFile; - std::shared_ptr mockHttpClient; - std::shared_ptr mockHttpClientFactory; + void CreateSSOTokenFile(const Aws::String& startUrl) { + const Aws::String hash = computeHashedStartUrl(startUrl); + const Aws::String tokenPath = m_ssoCacheDir + PATH_DELIM + hash + ".json"; + + std::ofstream tokenFile(tokenPath.c_str()); + ASSERT_TRUE(tokenFile.good()) << "Failed to open " << tokenPath; + + const auto futureTime = DateTime::Now() + std::chrono::hours(1); + tokenFile << "{\n" + " \"accessToken\": \"test-token\",\n" + " \"expiresAt\": \"" << futureTime.ToGmtString(DateFormat::ISO_8601) << "\",\n" + " \"region\": \"us-east-1\",\n" + " \"startUrl\": \"" << startUrl << "\"\n" + "}\n"; + tokenFile.close(); + + std::ifstream check(tokenPath.c_str()); + ASSERT_TRUE(check.good()) << "Token not created at: " << tokenPath; + check.close(); + } + + void RunTrackingProbe(const std::shared_ptr& provider, const Aws::String& marker) { + // 200 OK dummy response for the signed call + auto req = CreateHttpRequest(URI("dummy"), HttpMethod::HTTP_POST, Aws::Utils::Stream::DefaultResponseStreamFactoryMethod); + auto ok = Aws::MakeShared(TEST_LOG_TAG, req); + ok->SetResponseCode(HttpResponseCode::OK); + ok->GetResponseBody() << "{}"; + mockHttpClient->AddResponseToReturn(ok); + + ClientConfigurationInitValues initVals; initVals.shouldDisableIMDS = true; + ClientConfiguration cfg(initVals); + cfg.region = Aws::Region::US_EAST_1; + + CredentialTestingClient client(cfg, provider); + AmazonWebServiceRequestMock mockReq; + auto outcome = client.MakeRequest(mockReq); + ASSERT_TRUE(outcome.IsSuccess()); + + auto last = mockHttpClient->GetMostRecentHttpRequest(); + ASSERT_TRUE(last.HasHeader(USER_AGENT_HEADER)); + const auto userAgent = last.GetHeaderValue(USER_AGENT_HEADER); + ASSERT_FALSE(userAgent.empty()); + + const auto parts = StringUtils::Split(userAgent, ' '); + int mCount = 0; + for (const auto& p : parts) if (p.find("m/") != Aws::String::npos) ++mCount; + EXPECT_EQ(1, mCount); // only one m/ section + + auto it = std::find_if(parts.begin(), parts.end(), + [&marker](const Aws::String& v){ return v.find("m/") != Aws::String::npos && v.find(marker) != Aws::String::npos; }); + EXPECT_TRUE(it != parts.end()); + } + + Aws::String m_profileDir; + Aws::String m_ssoCacheDir; + Aws::String m_configPath; + + std::shared_ptr mockHttpClient; + std::shared_ptr mockHttpClientFactory; }; -TEST_F(SSOCredentialsProviderTest, TestSSOCredentialsTracking) -{ - const Aws::String startUrl = "https://test.awsapps.com/start"; - - // Create test configuration - CreateTestConfig(startUrl); - CreateSSOTokenFile(startUrl); - - // Mock SSO credentials API response - std::shared_ptr ssoRequest = CreateHttpRequest( - Aws::Http::URI("https://portal.sso.us-east-1.amazonaws.com/federation/credentials"), - Aws::Http::HttpMethod::HTTP_GET, - Aws::Utils::Stream::DefaultResponseStreamFactoryMethod); - - auto ssoResponse = Aws::MakeShared("SSOTest", ssoRequest); - ssoResponse->SetResponseCode(Aws::Http::HttpResponseCode::OK); - ssoResponse->GetResponseBody() << R"({ - "roleCredentials": { - "accessKeyId": "AKIAIOSFODNN7EXAMPLE", - "secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", - "sessionToken": "AQoDYXdzEJr...", - "expiration": )" << (Aws::Utils::DateTime::Now().Millis() + 3600000) << R"( - } - })"; - mockHttpClient->AddResponseToReturn(ssoResponse); - - // Create SSO credentials provider as shared_ptr - auto ssoProvider = Aws::MakeShared(TEST_LOG_TAG); - - // Get credentials using regular method - auto credentials = ssoProvider->GetAWSCredentials(); - - // Verify credentials were retrieved - EXPECT_FALSE(credentials.IsEmpty()); - EXPECT_EQ("AKIAIOSFODNN7EXAMPLE", credentials.GetAWSAccessKeyId()); - EXPECT_EQ("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", credentials.GetAWSSecretKey()); - - // Test credential tracking - RunTestWithCredentialsProvider(ssoProvider, "s"); +TEST_F(SSOCredentialsProviderTrackingTest, TestSSOCredentialsTracking){ + const Aws::String startUrl = "https://test.awsapps.com/start"; + + CreateTestConfig(startUrl); + CreateSSOTokenFile(startUrl); + + // Prepare mock SSO GetRoleCredentials response + auto ssoReq = CreateHttpRequest( + URI("https://portal.sso.us-east-1.amazonaws.com/federation/credentials"), + HttpMethod::HTTP_GET, + Aws::Utils::Stream::DefaultResponseStreamFactoryMethod); + + auto ssoResp = Aws::MakeShared(TEST_LOG_TAG, ssoReq); + ssoResp->SetResponseCode(HttpResponseCode::OK); + ssoResp->GetResponseBody() + << R"({"roleCredentials":{ + "accessKeyId":"AKIAIOSFODNN7EXAMPLE", + "secretAccessKey":"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "sessionToken":"AQoDYXdzEJr...", + "expiration":)" + << (DateTime::Now().Millis() + 3600000) << "}}"; + mockHttpClient->AddResponseToReturn(ssoResp); + + // Provider should read config + token from the real cache dir and call mock + auto provider = Aws::MakeShared(TEST_LOG_TAG); + auto creds = provider->GetAWSCredentials(); + + ASSERT_FALSE(creds.IsEmpty()); + EXPECT_EQ("AKIAIOSFODNN7EXAMPLE", creds.GetAWSAccessKeyId()); + EXPECT_EQ("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", creds.GetAWSSecretKey()); + + // Fire a signed request and assert the business metric appears once + RunTrackingProbe(provider, "s"); } \ No newline at end of file From a3c7b805df61055522f24655b38fdc5d14690fd0 Mon Sep 17 00:00:00 2001 From: kai lin Date: Mon, 8 Sep 2025 10:35:43 -0400 Subject: [PATCH 06/12] cleaned up PR --- src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h | 1 - src/aws-cpp-sdk-core/source/auth/AWSCredentialsProvider.cpp | 4 ---- src/aws-cpp-sdk-core/source/client/UserAgent.cpp | 1 - 3 files changed, 6 deletions(-) diff --git a/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h b/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h index f4b380b5675..60b506671a3 100644 --- a/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h +++ b/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h @@ -34,7 +34,6 @@ enum class UserAgentFeature { GZIP_REQUEST_COMPRESSION, CREDENTIALS_ENV_VARS, CREDENTIALS_PROFILE, - CREDENTIALS_PROCESS, CREDENTIALS_PROFILE_PROCESS, CREDENTIALS_IMDS, CREDENTIALS_STS_ASSUME_ROLE, diff --git a/src/aws-cpp-sdk-core/source/auth/AWSCredentialsProvider.cpp b/src/aws-cpp-sdk-core/source/auth/AWSCredentialsProvider.cpp index 2fca063e8b8..e009b64c98d 100644 --- a/src/aws-cpp-sdk-core/source/auth/AWSCredentialsProvider.cpp +++ b/src/aws-cpp-sdk-core/source/auth/AWSCredentialsProvider.cpp @@ -446,10 +446,6 @@ AWSCredentials Aws::Auth::GetCredentialsFromProcess(const Aws::String& process) credentials.SetAccountId(credentialsView.GetString("AccountId")); } - if (!credentials.IsEmpty()) { - credentials.AddUserAgentFeature(UserAgentFeature::CREDENTIALS_PROCESS); - } - AWS_LOGSTREAM_DEBUG(PROFILE_LOG_TAG, "Successfully pulled credentials from process credential with AccessKey: " << accessKey << ", Expiration:" << credentialsView.GetString("Expiration")); return credentials; } diff --git a/src/aws-cpp-sdk-core/source/client/UserAgent.cpp b/src/aws-cpp-sdk-core/source/client/UserAgent.cpp index 2c83c19c95d..16e947ace48 100644 --- a/src/aws-cpp-sdk-core/source/client/UserAgent.cpp +++ b/src/aws-cpp-sdk-core/source/client/UserAgent.cpp @@ -44,7 +44,6 @@ const std::pair BUSINESS_METRIC_MAPPING[] = { {UserAgentFeature::GZIP_REQUEST_COMPRESSION, "L"}, {UserAgentFeature::CREDENTIALS_ENV_VARS, "g"}, {UserAgentFeature::CREDENTIALS_PROFILE, "n"}, - {UserAgentFeature::CREDENTIALS_PROCESS, "w"}, {UserAgentFeature::CREDENTIALS_PROFILE_PROCESS, "v"}, {UserAgentFeature::CREDENTIALS_IMDS, "0"}, {UserAgentFeature::CREDENTIALS_STS_ASSUME_ROLE, "i"}, From e30bcab35835c7562779cbb3ae7eda269d696c52 Mon Sep 17 00:00:00 2001 From: kai lin Date: Mon, 8 Sep 2025 13:02:33 -0400 Subject: [PATCH 07/12] updated test --- .../include/aws/core/client/UserAgent.h | 1 + .../source/auth/SSOCredentialsProvider.cpp | 6 ++- .../source/client/UserAgent.cpp | 1 + .../aws/auth/SSOCredentialTrackingTest.cpp | 42 +++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h b/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h index 60b506671a3..e0cd3acfc4b 100644 --- a/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h +++ b/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h @@ -39,6 +39,7 @@ enum class UserAgentFeature { CREDENTIALS_STS_ASSUME_ROLE, CREDENTIALS_HTTP, CREDENTIALS_SSO, + CREDENTIALS_SSO_LEGACY, }; class AWS_CORE_API UserAgent { diff --git a/src/aws-cpp-sdk-core/source/auth/SSOCredentialsProvider.cpp b/src/aws-cpp-sdk-core/source/auth/SSOCredentialsProvider.cpp index 90333df8c20..fb437d291e8 100644 --- a/src/aws-cpp-sdk-core/source/auth/SSOCredentialsProvider.cpp +++ b/src/aws-cpp-sdk-core/source/auth/SSOCredentialsProvider.cpp @@ -102,7 +102,11 @@ void SSOCredentialsProvider::Reload() m_credentials = result.creds; if (!m_credentials.IsEmpty()) { - m_credentials.AddUserAgentFeature(Aws::Client::UserAgentFeature::CREDENTIALS_SSO); + if (!profile.IsSsoSessionSet()) { + m_credentials.AddUserAgentFeature(Aws::Client::UserAgentFeature::CREDENTIALS_SSO_LEGACY); + } else { + m_credentials.AddUserAgentFeature(Aws::Client::UserAgentFeature::CREDENTIALS_SSO); + } } } diff --git a/src/aws-cpp-sdk-core/source/client/UserAgent.cpp b/src/aws-cpp-sdk-core/source/client/UserAgent.cpp index 16e947ace48..8c3368901f5 100644 --- a/src/aws-cpp-sdk-core/source/client/UserAgent.cpp +++ b/src/aws-cpp-sdk-core/source/client/UserAgent.cpp @@ -49,6 +49,7 @@ const std::pair BUSINESS_METRIC_MAPPING[] = { {UserAgentFeature::CREDENTIALS_STS_ASSUME_ROLE, "i"}, {UserAgentFeature::CREDENTIALS_HTTP, "z"}, {UserAgentFeature::CREDENTIALS_SSO, "s"}, + {UserAgentFeature::CREDENTIALS_SSO_LEGACY, "h"}, }; const std::pair RETRY_FEATURE_MAPPING[] = { diff --git a/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp b/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp index 66ce694325d..319e67ee88f 100644 --- a/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp @@ -205,4 +205,46 @@ TEST_F(SSOCredentialsProviderTrackingTest, TestSSOCredentialsTracking){ // Fire a signed request and assert the business metric appears once RunTrackingProbe(provider, "s"); +} + +TEST_F(SSOCredentialsProviderTrackingTest, TestSSOLegacyCredentialsTracking){ + const Aws::String startUrl = "https://test.awsapps.com/start"; + + // Create legacy SSO config (without sso_session) + std::ofstream cfg(m_configPath.c_str()); + cfg << "[default]\n" + "sso_account_id = 123456789012\n" + "sso_region = us-east-1\n" + "sso_role_name = TestRole\n" + "sso_start_url = " << startUrl << "\n"; + cfg.close(); + Aws::Config::ReloadCachedConfigFile(); + + CreateSSOTokenFile(startUrl); + + // Prepare mock SSO GetRoleCredentials response + auto ssoReq = CreateHttpRequest( + URI("https://portal.sso.us-east-1.amazonaws.com/federation/credentials"), + HttpMethod::HTTP_GET, + Aws::Utils::Stream::DefaultResponseStreamFactoryMethod); + + auto ssoResp = Aws::MakeShared(TEST_LOG_TAG, ssoReq); + ssoResp->SetResponseCode(HttpResponseCode::OK); + ssoResp->GetResponseBody() + << R"({"roleCredentials":{ + "accessKeyId":"AKIAIOSFODNN7EXAMPLE", + "secretAccessKey":"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "sessionToken":"AQoDYXdzEJr...", + "expiration":)" << (DateTime::Now().Millis() + 3600000) << "}}"; + mockHttpClient->AddResponseToReturn(ssoResp); + + auto provider = Aws::MakeShared(TEST_LOG_TAG); + auto creds = provider->GetAWSCredentials(); + + ASSERT_FALSE(creds.IsEmpty()); + EXPECT_EQ("AKIAIOSFODNN7EXAMPLE", creds.GetAWSAccessKeyId()); + EXPECT_EQ("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", creds.GetAWSSecretKey()); + + // Fire a signed request and assert the legacy SSO business metric appears + RunTrackingProbe(provider, "h"); } \ No newline at end of file From a5e79713fae15bc22e87f2a47cf8a8609a7e40f1 Mon Sep 17 00:00:00 2001 From: kai lin Date: Mon, 8 Sep 2025 13:57:29 -0400 Subject: [PATCH 08/12] updated test to support legacy SSO --- .../aws/auth/SSOCredentialTrackingTest.cpp | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp b/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp index 319e67ee88f..dfdc5749352 100644 --- a/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp @@ -98,9 +98,12 @@ class SSOCredentialsProviderTrackingTest : public Aws::Testing::AwsCppSdkGTestSu void CreateTestConfig(const Aws::String& startUrl) { std::ofstream cfg(m_configPath.c_str()); cfg << "[default]\n" + "sso_session = my-sso\n" "sso_account_id = 123456789012\n" - "sso_region = us-east-1\n" "sso_role_name = TestRole\n" + "\n" + "[sso-session my-sso]\n" + "sso_region = us-east-1\n" "sso_start_url = " << startUrl << "\n"; cfg.close(); @@ -132,6 +135,27 @@ class SSOCredentialsProviderTrackingTest : public Aws::Testing::AwsCppSdkGTestSu check.close(); } + void CreateSSOSessionTokenFile(const Aws::String& sessionName /* e.g., "my-sso" */) { + const Aws::String hash = Aws::Utils::HashingUtils::HexEncode( + Aws::Utils::HashingUtils::CalculateSHA1(sessionName)); + const Aws::String tokenPath = m_ssoCacheDir + PATH_DELIM + hash + ".json"; + + std::ofstream tokenFile(tokenPath.c_str()); + ASSERT_TRUE(tokenFile.good()) << "Failed to open " << tokenPath; + + const auto futureTime = Aws::Utils::DateTime::Now() + std::chrono::hours(1); + tokenFile << "{\n" + " \"accessToken\": \"test-token\",\n" + " \"expiresAt\": \"" << futureTime.ToGmtString(Aws::Utils::DateFormat::ISO_8601) << "\"\n" + // (region/startUrl fields are optional on this path) + "}\n"; + tokenFile.close(); + + std::ifstream check(tokenPath.c_str()); + ASSERT_TRUE(check.good()) << "Token not created at: " << tokenPath; + check.close(); + } + void RunTrackingProbe(const std::shared_ptr& provider, const Aws::String& marker) { // 200 OK dummy response for the signed call auto req = CreateHttpRequest(URI("dummy"), HttpMethod::HTTP_POST, Aws::Utils::Stream::DefaultResponseStreamFactoryMethod); @@ -154,14 +178,14 @@ class SSOCredentialsProviderTrackingTest : public Aws::Testing::AwsCppSdkGTestSu const auto userAgent = last.GetHeaderValue(USER_AGENT_HEADER); ASSERT_FALSE(userAgent.empty()); - const auto parts = StringUtils::Split(userAgent, ' '); + const auto userAgentParsed = StringUtils::Split(userAgent, ' '); int mCount = 0; - for (const auto& p : parts) if (p.find("m/") != Aws::String::npos) ++mCount; + for (const auto& p : userAgentParsed) if (p.find("m/") != Aws::String::npos) ++mCount; EXPECT_EQ(1, mCount); // only one m/ section - auto it = std::find_if(parts.begin(), parts.end(), + auto businessMetrics = std::find_if(userAgentParsed.begin(), userAgentParsed.end(), [&marker](const Aws::String& v){ return v.find("m/") != Aws::String::npos && v.find(marker) != Aws::String::npos; }); - EXPECT_TRUE(it != parts.end()); + EXPECT_TRUE(businessMetrics != userAgentParsed.end()); } Aws::String m_profileDir; @@ -176,7 +200,7 @@ TEST_F(SSOCredentialsProviderTrackingTest, TestSSOCredentialsTracking){ const Aws::String startUrl = "https://test.awsapps.com/start"; CreateTestConfig(startUrl); - CreateSSOTokenFile(startUrl); + CreateSSOSessionTokenFile("my-sso"); // Prepare mock SSO GetRoleCredentials response auto ssoReq = CreateHttpRequest( From 58335d10a2a36b7558b8a57bf6616392913af826 Mon Sep 17 00:00:00 2001 From: kai lin Date: Mon, 8 Sep 2025 16:57:39 -0400 Subject: [PATCH 09/12] updated ua --- src/aws-cpp-sdk-core/source/client/UserAgent.cpp | 2 +- .../aws/auth/SSOCredentialTrackingTest.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aws-cpp-sdk-core/source/client/UserAgent.cpp b/src/aws-cpp-sdk-core/source/client/UserAgent.cpp index 8c3368901f5..f0b02468c4e 100644 --- a/src/aws-cpp-sdk-core/source/client/UserAgent.cpp +++ b/src/aws-cpp-sdk-core/source/client/UserAgent.cpp @@ -49,7 +49,7 @@ const std::pair BUSINESS_METRIC_MAPPING[] = { {UserAgentFeature::CREDENTIALS_STS_ASSUME_ROLE, "i"}, {UserAgentFeature::CREDENTIALS_HTTP, "z"}, {UserAgentFeature::CREDENTIALS_SSO, "s"}, - {UserAgentFeature::CREDENTIALS_SSO_LEGACY, "h"}, + {UserAgentFeature::CREDENTIALS_SSO_LEGACY, "u"}, }; const std::pair RETRY_FEATURE_MAPPING[] = { diff --git a/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp b/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp index dfdc5749352..76d75fcdd5e 100644 --- a/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp @@ -270,5 +270,5 @@ TEST_F(SSOCredentialsProviderTrackingTest, TestSSOLegacyCredentialsTracking){ EXPECT_EQ("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", creds.GetAWSSecretKey()); // Fire a signed request and assert the legacy SSO business metric appears - RunTrackingProbe(provider, "h"); + RunTrackingProbe(provider, "u"); } \ No newline at end of file From 8fbf909c64de1c0fb7d6175399f7677ac7f7958d Mon Sep 17 00:00:00 2001 From: kai lin Date: Mon, 8 Sep 2025 17:02:44 -0400 Subject: [PATCH 10/12] changed std to aws --- .../aws/auth/SSOCredentialTrackingTest.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp b/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp index 76d75fcdd5e..b42cbd6d102 100644 --- a/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp @@ -135,7 +135,7 @@ class SSOCredentialsProviderTrackingTest : public Aws::Testing::AwsCppSdkGTestSu check.close(); } - void CreateSSOSessionTokenFile(const Aws::String& sessionName /* e.g., "my-sso" */) { + void CreateSSOSessionTokenFile(const Aws::String& sessionName) { const Aws::String hash = Aws::Utils::HashingUtils::HexEncode( Aws::Utils::HashingUtils::CalculateSHA1(sessionName)); const Aws::String tokenPath = m_ssoCacheDir + PATH_DELIM + hash + ".json"; @@ -143,10 +143,11 @@ class SSOCredentialsProviderTrackingTest : public Aws::Testing::AwsCppSdkGTestSu std::ofstream tokenFile(tokenPath.c_str()); ASSERT_TRUE(tokenFile.good()) << "Failed to open " << tokenPath; - const auto futureTime = Aws::Utils::DateTime::Now() + std::chrono::hours(1); + const auto futureTime = Aws::Utils::DateTime::Now().Millis() + 3600000; + const auto futureDateTime = Aws::Utils::DateTime(futureTime); tokenFile << "{\n" " \"accessToken\": \"test-token\",\n" - " \"expiresAt\": \"" << futureTime.ToGmtString(Aws::Utils::DateFormat::ISO_8601) << "\"\n" + " \"expiresAt\": \"" << futureDateTime.ToGmtString(Aws::Utils::DateFormat::ISO_8601) << "\"\n" // (region/startUrl fields are optional on this path) "}\n"; tokenFile.close(); From 704bde6d8febb9847e320aedcfd601ad0adecb37 Mon Sep 17 00:00:00 2001 From: kai lin Date: Mon, 8 Sep 2025 17:13:51 -0400 Subject: [PATCH 11/12] changed std to aws --- .../aws/auth/SSOCredentialTrackingTest.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp b/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp index b42cbd6d102..26676cd7dc5 100644 --- a/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp @@ -96,7 +96,7 @@ class SSOCredentialsProviderTrackingTest : public Aws::Testing::AwsCppSdkGTestSu } void CreateTestConfig(const Aws::String& startUrl) { - std::ofstream cfg(m_configPath.c_str()); + Aws::OFStream cfg(m_configPath.c_str()); cfg << "[default]\n" "sso_session = my-sso\n" "sso_account_id = 123456789012\n" @@ -107,7 +107,7 @@ class SSOCredentialsProviderTrackingTest : public Aws::Testing::AwsCppSdkGTestSu "sso_start_url = " << startUrl << "\n"; cfg.close(); - std::ifstream check(m_configPath.c_str()); + Aws::IFStream check(m_configPath.c_str()); ASSERT_TRUE(check.good()) << "Config not created at: " << m_configPath; check.close(); @@ -118,7 +118,7 @@ class SSOCredentialsProviderTrackingTest : public Aws::Testing::AwsCppSdkGTestSu const Aws::String hash = computeHashedStartUrl(startUrl); const Aws::String tokenPath = m_ssoCacheDir + PATH_DELIM + hash + ".json"; - std::ofstream tokenFile(tokenPath.c_str()); + Aws::OFStream tokenFile(tokenPath.c_str()); ASSERT_TRUE(tokenFile.good()) << "Failed to open " << tokenPath; const auto futureTime = DateTime::Now() + std::chrono::hours(1); @@ -130,7 +130,7 @@ class SSOCredentialsProviderTrackingTest : public Aws::Testing::AwsCppSdkGTestSu "}\n"; tokenFile.close(); - std::ifstream check(tokenPath.c_str()); + Aws::IFStream check(tokenPath.c_str()); ASSERT_TRUE(check.good()) << "Token not created at: " << tokenPath; check.close(); } @@ -140,7 +140,7 @@ class SSOCredentialsProviderTrackingTest : public Aws::Testing::AwsCppSdkGTestSu Aws::Utils::HashingUtils::CalculateSHA1(sessionName)); const Aws::String tokenPath = m_ssoCacheDir + PATH_DELIM + hash + ".json"; - std::ofstream tokenFile(tokenPath.c_str()); + Aws::OFStream tokenFile(tokenPath.c_str()); ASSERT_TRUE(tokenFile.good()) << "Failed to open " << tokenPath; const auto futureTime = Aws::Utils::DateTime::Now().Millis() + 3600000; @@ -152,12 +152,12 @@ class SSOCredentialsProviderTrackingTest : public Aws::Testing::AwsCppSdkGTestSu "}\n"; tokenFile.close(); - std::ifstream check(tokenPath.c_str()); + Aws::IFStream check(tokenPath.c_str()); ASSERT_TRUE(check.good()) << "Token not created at: " << tokenPath; check.close(); } - void RunTrackingProbe(const std::shared_ptr& provider, const Aws::String& marker) { + void RunTestWithCredentialsProvider(const std::shared_ptr& provider, const Aws::String& marker) { // 200 OK dummy response for the signed call auto req = CreateHttpRequest(URI("dummy"), HttpMethod::HTTP_POST, Aws::Utils::Stream::DefaultResponseStreamFactoryMethod); auto ok = Aws::MakeShared(TEST_LOG_TAG, req); @@ -229,14 +229,14 @@ TEST_F(SSOCredentialsProviderTrackingTest, TestSSOCredentialsTracking){ EXPECT_EQ("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", creds.GetAWSSecretKey()); // Fire a signed request and assert the business metric appears once - RunTrackingProbe(provider, "s"); + RunTestWithCredentialsProvider(provider, "s"); } TEST_F(SSOCredentialsProviderTrackingTest, TestSSOLegacyCredentialsTracking){ const Aws::String startUrl = "https://test.awsapps.com/start"; // Create legacy SSO config (without sso_session) - std::ofstream cfg(m_configPath.c_str()); + Aws::OFStream cfg(m_configPath.c_str()); cfg << "[default]\n" "sso_account_id = 123456789012\n" "sso_region = us-east-1\n" @@ -271,5 +271,5 @@ TEST_F(SSOCredentialsProviderTrackingTest, TestSSOLegacyCredentialsTracking){ EXPECT_EQ("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", creds.GetAWSSecretKey()); // Fire a signed request and assert the legacy SSO business metric appears - RunTrackingProbe(provider, "u"); + RunTestWithCredentialsProvider(provider, "u"); } \ No newline at end of file From 886433e6cafbaf0f79754d5860cd5009b23dfd82 Mon Sep 17 00:00:00 2001 From: kai lin Date: Wed, 10 Sep 2025 12:53:49 -0400 Subject: [PATCH 12/12] updated to use a real AWSClient --- .../aws/auth/SSOCredentialTrackingTest.cpp | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp b/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp index 26676cd7dc5..1b5bc776364 100644 --- a/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp +++ b/tests/aws-cpp-sdk-core-tests/aws/auth/SSOCredentialTrackingTest.cpp @@ -37,24 +37,26 @@ Aws::String computeHashedStartUrl(const Aws::String& startUrl) { return HashingUtils::HexEncode(sha1); // lower-case hex the same as provider } -// Minimal AWSClient wrapper so we can make a signed call and inspect User-Agent -class CredentialTestingClient : public AWSClient { +class CredentialTestingClient : public AWSClient +{ public: - explicit CredentialTestingClient(const ClientConfiguration& config, - const std::shared_ptr& provider) - : AWSClient(config, - Aws::MakeShared(TEST_LOG_TAG, provider, "service", config.region), - Aws::MakeShared(TEST_LOG_TAG)) {} - - HttpResponseOutcome MakeRequest(const Aws::AmazonWebServiceRequest& request) { - URI uri("https://test.com"); - return AttemptExhaustively(uri, request, HttpMethod::HTTP_POST, Aws::Auth::SIGV4_SIGNER); + CredentialTestingClient() : AWSClient(ClientConfiguration(), Aws::MakeShared(TEST_LOG_TAG, Aws::MakeShared(TEST_LOG_TAG), + "rds", Aws::Region::US_EAST_1), Aws::MakeShared(TEST_LOG_TAG)) {} + CredentialTestingClient(const Aws::Client::ClientConfiguration& configuration, const std::shared_ptr& signer) : + AWSClient(configuration, signer, Aws::MakeShared(TEST_LOG_TAG)) {} + + Aws::Client::HttpResponseOutcome PublicAttemptExhaustively( + const Aws::Http::URI& uri, + const Aws::AmazonWebServiceRequest& request, + Http::HttpMethod method, + const char* signerName) { + return AttemptExhaustively(uri.GetURIString(), request, method, signerName); } - const char* GetServiceClientName() const override { return "CredentialTestingClient"; } -protected: - AWSError BuildAWSError(const std::shared_ptr&) const override { - return {CoreErrors::UNKNOWN, false}; + Aws::Client::AWSError BuildAWSError(const std::shared_ptr&) const override + { + Aws::Client::AWSError error; + return error; } }; @@ -159,7 +161,7 @@ class SSOCredentialsProviderTrackingTest : public Aws::Testing::AwsCppSdkGTestSu void RunTestWithCredentialsProvider(const std::shared_ptr& provider, const Aws::String& marker) { // 200 OK dummy response for the signed call - auto req = CreateHttpRequest(URI("dummy"), HttpMethod::HTTP_POST, Aws::Utils::Stream::DefaultResponseStreamFactoryMethod); + auto req = CreateHttpRequest(URI("https://test-service.us-east-1.amazonaws.com/"), HttpMethod::HTTP_POST, Aws::Utils::Stream::DefaultResponseStreamFactoryMethod); auto ok = Aws::MakeShared(TEST_LOG_TAG, req); ok->SetResponseCode(HttpResponseCode::OK); ok->GetResponseBody() << "{}"; @@ -169,9 +171,13 @@ class SSOCredentialsProviderTrackingTest : public Aws::Testing::AwsCppSdkGTestSu ClientConfiguration cfg(initVals); cfg.region = Aws::Region::US_EAST_1; - CredentialTestingClient client(cfg, provider); + auto signer = Aws::MakeShared(TEST_LOG_TAG, provider, "test-service", cfg.region); + CredentialTestingClient client(cfg, signer); AmazonWebServiceRequestMock mockReq; - auto outcome = client.MakeRequest(mockReq); + + // Use public AWS client method to make a request + URI uri("https://test-service.us-east-1.amazonaws.com/"); + auto outcome = client.PublicAttemptExhaustively(uri, mockReq, HttpMethod::HTTP_POST, Aws::Auth::SIGV4_SIGNER); ASSERT_TRUE(outcome.IsSuccess()); auto last = mockHttpClient->GetMostRecentHttpRequest();