77#include < aws/testing/AwsTestHelpers.h>
88#include < aws/testing/mocks/aws/client/MockAWSClient.h>
99#include < aws/testing/mocks/http/MockHttpClient.h>
10+ #include < aws/testing/mocks/aws/auth/MockAWSHttpResourceClient.h>
1011#include < aws/testing/platform/PlatformTesting.h>
1112#include < aws/core/auth/AWSCredentialsProvider.h>
1213#include < aws/core/auth/AWSCredentialsProviderChain.h>
1314#include < aws/core/client/AWSClient.h>
1415#include < aws/core/utils/StringUtils.h>
16+ #include < aws/core/utils/HashingUtils.h>
17+ #include < aws/core/platform/FileSystem.h>
18+ #include < aws/core/utils/FileSystemUtils.h>
19+ #include < fstream>
20+ #include < sys/stat.h>
1521
1622using namespace Aws ::Client;
1723using namespace Aws ::Auth;
1824using namespace Aws ::Http;
1925
2026static const char ALLOCATION_TAG[] = " CredentialTrackingTest" ;
2127
28+ static Aws::String WrapEchoStringWithSingleQuoteForUnixShell (Aws::String str)
29+ {
30+ #ifndef _WIN32
31+ str.insert (0 , 1 , ' \' ' );
32+ str.append (1 , ' \' ' );
33+ #endif
34+ return str;
35+ }
36+
2237// Custom client that uses default credential provider for testing
2338class CredentialTestingClient : public Aws ::Client::AWSClient
2439{
@@ -32,6 +47,17 @@ class CredentialTestingClient : public Aws::Client::AWSClient
3247 {
3348 }
3449
50+ // Constructor with custom credential provider for IMDS test
51+ explicit CredentialTestingClient (const Aws::Client::ClientConfiguration& configuration,
52+ std::shared_ptr<AWSCredentialsProvider> credentialsProvider)
53+ : AWSClient(configuration,
54+ Aws::MakeShared<Aws::Client::AWSAuthV4Signer>(ALLOCATION_TAG,
55+ credentialsProvider,
56+ " service" , configuration.region),
57+ Aws::MakeShared<MockAWSErrorMarshaller>(ALLOCATION_TAG))
58+ {
59+ }
60+
3561 Aws::Client::HttpResponseOutcome MakeRequest (const Aws::AmazonWebServiceRequest& request)
3662 {
3763 auto uri = Aws::Http::URI (" https://test.com" );
@@ -127,3 +153,194 @@ TEST_F(CredentialTrackingTest, TestEnvironmentCredentialsTracking)
127153
128154 EXPECT_TRUE (businessMetrics != userAgentParsed.end ());
129155}
156+
157+ TEST_F (CredentialTrackingTest, TestProfileCredentialsTracking)
158+ {
159+ // Create temporary credentials file
160+ Aws::Utils::TempFile credentialsFile (std::ios_base::out | std::ios_base::trunc);
161+ ASSERT_TRUE (credentialsFile.good ());
162+ credentialsFile << " [default]" << std::endl;
163+ credentialsFile << " aws_access_key_id = test-profile-access-key" << std::endl;
164+ credentialsFile << " aws_secret_access_key = test-profile-secret-key" << std::endl;
165+ credentialsFile.close ();
166+
167+ // Set environment to use our test credentials file
168+ Aws::Environment::EnvironmentRAII testEnvironment{{
169+ {" AWS_SHARED_CREDENTIALS_FILE" , credentialsFile.GetFileName ().c_str ()},
170+ }};
171+ Aws::Config::ReloadCachedCredentialsFile ();
172+
173+ // Setup mock response
174+ std::shared_ptr<HttpRequest> requestTmp =
175+ CreateHttpRequest (Aws::Http::URI (" dummy" ), Aws::Http::HttpMethod::HTTP_POST,
176+ Aws::Utils::Stream::DefaultResponseStreamFactoryMethod);
177+ auto successResponse = Aws::MakeShared<Standard::StandardHttpResponse>(ALLOCATION_TAG, requestTmp);
178+ successResponse->SetResponseCode (HttpResponseCode::OK);
179+ successResponse->GetResponseBody () << " {}" ;
180+ mockHttpClient->AddResponseToReturn (successResponse);
181+
182+ // Create client configuration
183+ Aws::Client::ClientConfigurationInitValues cfgInit;
184+ cfgInit.shouldDisableIMDS = true ;
185+ Aws::Client::ClientConfiguration clientConfig (cfgInit);
186+ clientConfig.region = Aws::Region::US_EAST_1;
187+
188+ // Create credential testing client that uses default provider chain
189+ CredentialTestingClient client (clientConfig);
190+
191+ // Create mock request
192+ AmazonWebServiceRequestMock mockRequest;
193+
194+ // Make request
195+ auto outcome = client.MakeRequest (mockRequest);
196+ ASSERT_TRUE (outcome.IsSuccess ());
197+
198+ // Verify User-Agent contains profile credentials tracking
199+ auto lastRequest = mockHttpClient->GetMostRecentHttpRequest ();
200+ EXPECT_TRUE (lastRequest.HasHeader (Aws::Http::USER_AGENT_HEADER));
201+ const auto & userAgent = lastRequest.GetHeaderValue (Aws::Http::USER_AGENT_HEADER);
202+ EXPECT_FALSE (userAgent.empty ());
203+
204+ const auto userAgentParsed = Aws::Utils::StringUtils::Split (userAgent, ' ' );
205+
206+ // Verify there's only one m/ section (no duplicate m/ sections)
207+ int mSectionCount = 0 ;
208+ for (const auto & part : userAgentParsed) {
209+ if (part.find (" m/" ) != Aws::String::npos) {
210+ mSectionCount ++;
211+ }
212+ }
213+ EXPECT_EQ (1 , mSectionCount );
214+
215+ // Check for profile credentials business metric (n) in user agent
216+ auto businessMetrics = std::find_if (userAgentParsed.begin (), userAgentParsed.end (),
217+ [](const Aws::String& value) { return value.find (" m/" ) != Aws::String::npos && value.find (" n" ) != Aws::String::npos; });
218+
219+ EXPECT_TRUE (businessMetrics != userAgentParsed.end ());
220+ }
221+
222+ TEST_F (CredentialTrackingTest, TestProcessCredentialsTracking)
223+ {
224+ // Create temporary config file with credential_process
225+ Aws::Utils::TempFile configFile (std::ios_base::out | std::ios_base::trunc);
226+ ASSERT_TRUE (configFile.good ());
227+ configFile << " [default]" << std::endl;
228+ configFile << " credential_process = echo " << WrapEchoStringWithSingleQuoteForUnixShell (" {\" Version\" : 1, \" AccessKeyId\" : \" test-process-key\" , \" SecretAccessKey\" : \" test-process-secret\" }" ) << std::endl;
229+ configFile.close ();
230+
231+ // Set environment to use our test config file
232+ Aws::Environment::EnvironmentRAII testEnvironment{{
233+ {" AWS_CONFIG_FILE" , configFile.GetFileName ().c_str ()},
234+ }};
235+
236+ // Force reload config file after setting environment variable
237+ Aws::Config::ReloadCachedConfigFile ();
238+
239+ // Setup mock response
240+ std::shared_ptr<HttpRequest> requestTmp =
241+ CreateHttpRequest (Aws::Http::URI (" dummy" ), Aws::Http::HttpMethod::HTTP_POST,
242+ Aws::Utils::Stream::DefaultResponseStreamFactoryMethod);
243+ auto successResponse = Aws::MakeShared<Standard::StandardHttpResponse>(ALLOCATION_TAG, requestTmp);
244+ successResponse->SetResponseCode (HttpResponseCode::OK);
245+ successResponse->GetResponseBody () << " {}" ;
246+ mockHttpClient->AddResponseToReturn (successResponse);
247+
248+ // Create client configuration
249+ Aws::Client::ClientConfigurationInitValues cfgInit;
250+ cfgInit.shouldDisableIMDS = true ;
251+ Aws::Client::ClientConfiguration clientConfig (cfgInit);
252+ clientConfig.region = Aws::Region::US_EAST_1;
253+
254+ // Create credential testing client that uses default provider chain
255+ CredentialTestingClient client (clientConfig);
256+
257+ // Create mock request
258+ AmazonWebServiceRequestMock mockRequest;
259+
260+ // Make request
261+ auto outcome = client.MakeRequest (mockRequest);
262+ ASSERT_TRUE (outcome.IsSuccess ());
263+
264+ // Verify User-Agent contains process credentials tracking
265+ auto lastRequest = mockHttpClient->GetMostRecentHttpRequest ();
266+ EXPECT_TRUE (lastRequest.HasHeader (Aws::Http::USER_AGENT_HEADER));
267+ const auto & userAgent = lastRequest.GetHeaderValue (Aws::Http::USER_AGENT_HEADER);
268+ EXPECT_FALSE (userAgent.empty ());
269+
270+ const auto userAgentParsed = Aws::Utils::StringUtils::Split (userAgent, ' ' );
271+
272+ // Verify there's only one m/ section (no duplicate m/ sections)
273+ int mSectionCount = 0 ;
274+ for (const auto & part : userAgentParsed) {
275+ if (part.find (" m/" ) != Aws::String::npos) {
276+ mSectionCount ++;
277+ }
278+ }
279+ EXPECT_EQ (1 , mSectionCount );
280+
281+ // Check for process credentials business metric (w) in user agent
282+ auto businessMetrics = std::find_if (userAgentParsed.begin (), userAgentParsed.end (),
283+ [](const Aws::String& value) { return value.find (" m/" ) != Aws::String::npos && value.find (" w" ) != Aws::String::npos; });
284+
285+ EXPECT_TRUE (businessMetrics != userAgentParsed.end ());
286+ }
287+
288+ TEST_F (CredentialTrackingTest, TestInstanceProfileCredentialsTracking)
289+ {
290+ // Create mock EC2 metadata client with valid credentials
291+ auto mockClient = Aws::MakeShared<MockEC2MetadataClient>(ALLOCATION_TAG);
292+ const char * validCredentials = R"( { "AccessKeyId": "test-imds-access-key", "SecretAccessKey": "test-imds-secret-key", "Token": "test-imds-token", "Code": "Success", "Expiration": "2037-04-19T00:00:00Z" })" ;
293+ mockClient->SetMockedCredentialsValue (validCredentials);
294+
295+ // Create IMDS credential provider with mock client
296+ auto imdsProvider = Aws::MakeShared<InstanceProfileCredentialsProvider>(ALLOCATION_TAG,
297+ Aws::MakeShared<Aws::Config::EC2InstanceProfileConfigLoader>(ALLOCATION_TAG, mockClient), 1000 * 60 * 15 );
298+
299+ // Setup mock response for service call
300+ std::shared_ptr<HttpRequest> requestTmp =
301+ CreateHttpRequest (Aws::Http::URI (" dummy" ), Aws::Http::HttpMethod::HTTP_POST,
302+ Aws::Utils::Stream::DefaultResponseStreamFactoryMethod);
303+ auto successResponse = Aws::MakeShared<Standard::StandardHttpResponse>(ALLOCATION_TAG, requestTmp);
304+ successResponse->SetResponseCode (HttpResponseCode::OK);
305+ successResponse->GetResponseBody () << " {}" ;
306+ mockHttpClient->AddResponseToReturn (successResponse);
307+
308+ // Create client configuration
309+ Aws::Client::ClientConfigurationInitValues cfgInit;
310+ cfgInit.shouldDisableIMDS = false ;
311+ Aws::Client::ClientConfiguration clientConfig (cfgInit);
312+ clientConfig.region = Aws::Region::US_EAST_1;
313+
314+ // Create credential testing client with IMDS provider
315+ CredentialTestingClient client (clientConfig, imdsProvider);
316+
317+ // Create mock request
318+ AmazonWebServiceRequestMock mockRequest;
319+
320+ // Make request
321+ auto outcome = client.MakeRequest (mockRequest);
322+ ASSERT_TRUE (outcome.IsSuccess ());
323+
324+ // Verify User-Agent contains IMDS credentials tracking
325+ auto lastRequest = mockHttpClient->GetMostRecentHttpRequest ();
326+ EXPECT_TRUE (lastRequest.HasHeader (Aws::Http::USER_AGENT_HEADER));
327+ const auto & userAgent = lastRequest.GetHeaderValue (Aws::Http::USER_AGENT_HEADER);
328+ EXPECT_FALSE (userAgent.empty ());
329+
330+ const auto userAgentParsed = Aws::Utils::StringUtils::Split (userAgent, ' ' );
331+
332+ // Verify there's only one m/ section (no duplicate m/ sections)
333+ int mSectionCount = 0 ;
334+ for (const auto & part : userAgentParsed) {
335+ if (part.find (" m/" ) != Aws::String::npos) {
336+ mSectionCount ++;
337+ }
338+ }
339+ EXPECT_EQ (1 , mSectionCount );
340+
341+ // Check for IMDS credentials business metric (0) in user agent
342+ auto businessMetrics = std::find_if (userAgentParsed.begin (), userAgentParsed.end (),
343+ [](const Aws::String& value) { return value.find (" m/" ) != Aws::String::npos && value.find (" 0" ) != Aws::String::npos; });
344+
345+ EXPECT_TRUE (businessMetrics != userAgentParsed.end ());
346+ }
0 commit comments