diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ChildProfileCredentialsProviderFactory.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ChildProfileCredentialsProviderFactory.java index 620e32decfe2..60b2b93b2cf1 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ChildProfileCredentialsProviderFactory.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ChildProfileCredentialsProviderFactory.java @@ -40,5 +40,45 @@ public interface ChildProfileCredentialsProviderFactory { * provider. * @return The credentials provider with permissions derived from the source credentials provider and profile. */ - AwsCredentialsProvider create(AwsCredentialsProvider sourceCredentialsProvider, Profile profile); + default AwsCredentialsProvider create(AwsCredentialsProvider sourceCredentialsProvider, Profile profile) { + ChildProfileCredentialsRequest request = new ChildProfileCredentialsRequest(sourceCredentialsProvider, profile, null); + return create(request); + } + + /** + * Create a credentials provider for the provided profile, using the provided source credentials provider to authenticate + * with AWS. In the case of STS, the returned credentials provider is for a role that has been assumed, and the provided + * source credentials provider is the credentials that should be used to authenticate that the user is allowed to assume + * that role. + * + * @param request The request containing all parameters needed to create the child credentials provider. + * @return The credentials provider with permissions derived from the request parameters. + */ + AwsCredentialsProvider create(ChildProfileCredentialsRequest request); + + final class ChildProfileCredentialsRequest { + private final AwsCredentialsProvider sourceCredentialsProvider; + private final Profile profile; + private final String source; + + public ChildProfileCredentialsRequest(AwsCredentialsProvider sourceCredentialsProvider, + Profile profile, + String source) { + this.sourceCredentialsProvider = sourceCredentialsProvider; + this.profile = profile; + this.source = source; + } + + public AwsCredentialsProvider sourceCredentialsProvider() { + return sourceCredentialsProvider; + } + + public Profile profile() { + return profile; + } + + public String source() { + return source; + } + } } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProvider.java index 2879a3070443..6d998e0458cd 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProvider.java @@ -93,6 +93,7 @@ public final class ContainerCredentialsProvider private final String asyncThreadName; private final String source; + private final String providerName; /** * @see #builder() @@ -102,6 +103,9 @@ private ContainerCredentialsProvider(BuilderImpl builder) { this.asyncCredentialUpdateEnabled = builder.asyncCredentialUpdateEnabled; this.asyncThreadName = builder.asyncThreadName; this.source = builder.source; + this.providerName = StringUtils.isEmpty(builder.source) + ? PROVIDER_NAME + : builder.source + "," + PROVIDER_NAME; this.httpCredentialsLoader = HttpCredentialsLoader.create(providerName()); if (Boolean.TRUE.equals(builder.asyncCredentialUpdateEnabled)) { @@ -165,11 +169,7 @@ private Instant prefetchTime(Instant expiration) { } private String providerName() { - String providerName = PROVIDER_NAME; - if (source != null && !source.isEmpty()) { - providerName = String.format("%s,%s", source, providerName); - } - return providerName; + return this.providerName; } @Override diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/HttpCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/HttpCredentialsProvider.java index 31efde720d34..b93d23606ad4 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/HttpCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/HttpCredentialsProvider.java @@ -16,6 +16,7 @@ package software.amazon.awssdk.auth.credentials; import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.utils.SdkAutoCloseable; /** @@ -49,7 +50,8 @@ interface Builder cacheBuilder = CachedSupplier.builder(this::refreshCredentials) .cachedValueName(toString()); @@ -177,11 +182,7 @@ private JsonNode parseProcessOutput(String processOutput) { } private String providerName() { - String providerName = PROVIDER_NAME; - if (source != null && !source.isEmpty()) { - providerName = String.format("%s,%s", source, providerName); - } - return providerName; + return this.providerName; } /** diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/StaticCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/StaticCredentialsProvider.java index 591bac5b3d06..18dc21629379 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/StaticCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/StaticCredentialsProvider.java @@ -35,10 +35,20 @@ private StaticCredentialsProvider(AwsCredentials credentials) { private AwsCredentials withProviderName(AwsCredentials credentials) { if (credentials instanceof AwsBasicCredentials) { - return ((AwsBasicCredentials) credentials).copy(c -> c.providerName(PROVIDER_NAME)); + AwsBasicCredentials basicCreds = (AwsBasicCredentials) credentials; + if (basicCreds.providerName().isPresent() && + BusinessMetricFeatureId.CREDENTIALS_PROFILE.value().equals(basicCreds.providerName().get())) { + return basicCreds; + } + return basicCreds.copy(c -> c.providerName(PROVIDER_NAME)); } if (credentials instanceof AwsSessionCredentials) { - return ((AwsSessionCredentials) credentials).copy(c -> c.providerName(PROVIDER_NAME)); + AwsSessionCredentials sessionCreds = (AwsSessionCredentials) credentials; + if (sessionCreds.providerName().isPresent() && + BusinessMetricFeatureId.CREDENTIALS_PROFILE.value().equals(sessionCreds.providerName().get())) { + return sessionCreds; + } + return sessionCreds.copy(c -> c.providerName(PROVIDER_NAME)); } return credentials; } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/internal/ProfileCredentialsUtils.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/internal/ProfileCredentialsUtils.java index 22da5e9986fd..a4fe866c7f4a 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/internal/ProfileCredentialsUtils.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/internal/ProfileCredentialsUtils.java @@ -40,6 +40,7 @@ import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.auth.credentials.SystemPropertyCredentialsProvider; import software.amazon.awssdk.core.internal.util.ClassLoaderHelper; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.profiles.Profile; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.profiles.ProfileProperty; @@ -161,6 +162,7 @@ private AwsCredentialsProvider basicProfileCredentialsProvider() { .accessKeyId(properties.get(ProfileProperty.AWS_ACCESS_KEY_ID)) .secretAccessKey(properties.get(ProfileProperty.AWS_SECRET_ACCESS_KEY)) .accountId(properties.get(ProfileProperty.AWS_ACCOUNT_ID)) + .providerName(BusinessMetricFeatureId.CREDENTIALS_PROFILE.value()) .build(); return StaticCredentialsProvider.create(credentials); } @@ -177,6 +179,7 @@ private AwsCredentialsProvider sessionProfileCredentialsProvider() { .secretAccessKey(properties.get(ProfileProperty.AWS_SECRET_ACCESS_KEY)) .sessionToken(properties.get(ProfileProperty.AWS_SESSION_TOKEN)) .accountId(properties.get(ProfileProperty.AWS_ACCOUNT_ID)) + .providerName(BusinessMetricFeatureId.CREDENTIALS_PROFILE.value()) .build(); return StaticCredentialsProvider.create(credentials); } @@ -187,6 +190,7 @@ private AwsCredentialsProvider credentialProcessCredentialsProvider() { return ProcessCredentialsProvider.builder() .command(properties.get(ProfileProperty.CREDENTIAL_PROCESS)) .staticAccountId(properties.get(ProfileProperty.AWS_ACCOUNT_ID)) + .source(BusinessMetricFeatureId.CREDENTIALS_PROFILE_PROCESS.value()) .build(); } @@ -195,10 +199,16 @@ private AwsCredentialsProvider credentialProcessCredentialsProvider() { */ private AwsCredentialsProvider ssoProfileCredentialsProvider() { validateRequiredPropertiesForSsoCredentialsProvider(); + boolean isLegacy = isLegacySsoConfiguration(); + String sourceFeatureId = isLegacy ? + BusinessMetricFeatureId.CREDENTIALS_PROFILE_SSO_LEGACY.value() : + BusinessMetricFeatureId.CREDENTIALS_PROFILE_SSO.value(); + return ssoCredentialsProviderFactory().create( ProfileProviderCredentialsContext.builder() .profile(profile) .profileFile(profileFile) + .source(sourceFeatureId) .build()); } @@ -211,6 +221,10 @@ private void validateRequiredPropertiesForSsoCredentialsProvider() { } } + private boolean isLegacySsoConfiguration() { + return !properties.containsKey(ProfileSection.SSO_SESSION.getPropertyKeyName()); + } + private AwsCredentialsProvider roleAndWebIdentityTokenProfileCredentialsProvider() { requireProperties(ProfileProperty.ROLE_ARN, ProfileProperty.WEB_IDENTITY_TOKEN_FILE); @@ -223,6 +237,7 @@ private AwsCredentialsProvider roleAndWebIdentityTokenProfileCredentialsProvider .roleArn(roleArn) .roleSessionName(roleSessionName) .webIdentityTokenFile(webIdentityTokenFile) + .source(BusinessMetricFeatureId.CREDENTIALS_PROFILE_STS_WEB_ID_TOKEN.value()) .build(); return WebIdentityCredentialsUtils.factory().create(credentialProperties); @@ -249,7 +264,8 @@ private AwsCredentialsProvider roleAndSourceProfileBasedProfileCredentialsProvid .credentialsProvider(children)) .orElseThrow(this::noSourceCredentialsException); - return stsCredentialsProviderFactory().create(sourceCredentialsProvider, profile); + String sourceFeatureId = BusinessMetricFeatureId.CREDENTIALS_PROFILE_SOURCE_PROFILE.value(); + return createStsCredentialsProviderWithMetrics(sourceCredentialsProvider, sourceFeatureId); } /** @@ -260,8 +276,10 @@ private AwsCredentialsProvider roleAndCredentialSourceBasedProfileCredentialsPro requireProperties(ProfileProperty.CREDENTIAL_SOURCE); CredentialSourceType credentialSource = CredentialSourceType.parse(properties.get(ProfileProperty.CREDENTIAL_SOURCE)); + String profileSource = BusinessMetricFeatureId.CREDENTIALS_PROFILE_NAMED_PROVIDER.value(); AwsCredentialsProvider credentialsProvider = credentialSourceCredentialProvider(credentialSource); - return stsCredentialsProviderFactory().create(credentialsProvider, profile); + + return createStsCredentialsProviderWithMetrics(credentialsProvider, profileSource); } private AwsCredentialsProvider credentialSourceCredentialProvider(CredentialSourceType credentialSource) { @@ -298,6 +316,39 @@ private IllegalStateException noSourceCredentialsException() { return new IllegalStateException(error); } + /** + * Extract business metrics from a credentials provider by resolving credentials and checking the provider name. + * This is used to propagate business metrics from source credentials to assume role operations. + */ + private String extractBusinessMetricsFromProvider(AwsCredentialsProvider credentialsProvider) { + try { + AwsCredentials credentials = credentialsProvider.resolveCredentials(); + return credentials.providerName().orElse(null); + } catch (Exception e) { + return null; + } + } + + /** + * Helper method to create STS credentials provider with business metrics propagation. + * This method extracts business metrics from the source credentials provider and combines them + * with the profile-level business metrics before creating the STS credentials provider. + */ + private AwsCredentialsProvider createStsCredentialsProviderWithMetrics(AwsCredentialsProvider sourceCredentialsProvider, + String profileMetric) { + String sourceMetrics = extractBusinessMetricsFromProvider(sourceCredentialsProvider); + + String combinedSource = profileMetric; + if (sourceMetrics != null && !sourceMetrics.isEmpty()) { + combinedSource = profileMetric + "," + sourceMetrics; + } + + ChildProfileCredentialsProviderFactory.ChildProfileCredentialsRequest request = + new ChildProfileCredentialsProviderFactory + .ChildProfileCredentialsRequest(sourceCredentialsProvider, profile, combinedSource); + return stsCredentialsProviderFactory().create(request); + } + /** * Load the factory that can be used to create the STS credentials provider, assuming it is on the classpath. */ diff --git a/services/sso/src/main/java/software/amazon/awssdk/services/sso/auth/SsoCredentialsProvider.java b/services/sso/src/main/java/software/amazon/awssdk/services/sso/auth/SsoCredentialsProvider.java index ce4fbaf2ca97..d010bd81ed51 100644 --- a/services/sso/src/main/java/software/amazon/awssdk/services/sso/auth/SsoCredentialsProvider.java +++ b/services/sso/src/main/java/software/amazon/awssdk/services/sso/auth/SsoCredentialsProvider.java @@ -25,11 +25,13 @@ import software.amazon.awssdk.auth.credentials.AwsCredentials; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.services.sso.SsoClient; import software.amazon.awssdk.services.sso.internal.SessionCredentialsHolder; import software.amazon.awssdk.services.sso.model.GetRoleCredentialsRequest; import software.amazon.awssdk.services.sso.model.RoleCredentials; import software.amazon.awssdk.utils.SdkAutoCloseable; +import software.amazon.awssdk.utils.StringUtils; import software.amazon.awssdk.utils.builder.CopyableBuilder; import software.amazon.awssdk.utils.builder.ToCopyableBuilder; import software.amazon.awssdk.utils.cache.CachedSupplier; @@ -51,7 +53,7 @@ @SdkPublicApi public final class SsoCredentialsProvider implements AwsCredentialsProvider, SdkAutoCloseable, ToCopyableBuilder { - private static final String PROVIDER_NAME = "SsoCredentialsProvider"; + private static final String PROVIDER_NAME = BusinessMetricFeatureId.CREDENTIALS_SSO.value(); private static final Duration DEFAULT_STALE_TIME = Duration.ofMinutes(1); private static final Duration DEFAULT_PREFETCH_TIME = Duration.ofMinutes(5); @@ -59,6 +61,8 @@ public final class SsoCredentialsProvider implements AwsCredentialsProvider, Sdk private static final String ASYNC_THREAD_NAME = "sdk-sso-credentials-provider"; private final Supplier getRoleCredentialsRequestSupplier; + private final String source; + private final String providerName; private final SsoClient ssoClient; private final Duration staleTime; @@ -77,6 +81,11 @@ private SsoCredentialsProvider(BuilderImpl builder) { this.staleTime = Optional.ofNullable(builder.staleTime).orElse(DEFAULT_STALE_TIME); this.prefetchTime = Optional.ofNullable(builder.prefetchTime).orElse(DEFAULT_PREFETCH_TIME); + this.source = builder.source; + + this.providerName = StringUtils.isEmpty(builder.source) + ? PROVIDER_NAME + : builder.source + "," + PROVIDER_NAME; this.asyncCredentialUpdateEnabled = builder.asyncCredentialUpdateEnabled; CachedSupplier.Builder cacheBuilder = @@ -95,11 +104,11 @@ private SsoCredentialsProvider(BuilderImpl builder) { */ private RefreshResult updateSsoCredentials() { SessionCredentialsHolder credentials = getUpdatedCredentials(ssoClient); - Instant acutalTokenExpiration = credentials.sessionCredentialsExpiration(); + Instant actualTokenExpiration = credentials.sessionCredentialsExpiration(); return RefreshResult.builder(credentials) - .staleTime(acutalTokenExpiration.minus(staleTime)) - .prefetchTime(acutalTokenExpiration.minus(prefetchTime)) + .staleTime(actualTokenExpiration.minus(staleTime)) + .prefetchTime(actualTokenExpiration.minus(prefetchTime)) .build(); } @@ -112,11 +121,15 @@ private SessionCredentialsHolder getUpdatedCredentials(SsoClient ssoClient) { .secretAccessKey(roleCredentials.secretAccessKey()) .sessionToken(roleCredentials.sessionToken()) .accountId(request.accountId()) - .providerName(PROVIDER_NAME) + .providerName(providerName()) .build(); return new SessionCredentialsHolder(sessionCredentials, Instant.ofEpochMilli(roleCredentials.expiration())); } + private String providerName() { + return this.providerName; + } + /** * The amount of time, relative to session token expiration, that the cached credentials are considered stale and * should no longer be used. All threads will block until the value is updated. @@ -206,6 +219,12 @@ public interface Builder extends CopyableBuilder getRoleCredentialsRequestSupplier); + /** + * An optional string list of {@link software.amazon.awssdk.core.useragent.BusinessMetricFeatureId} denoting previous + * credentials providers that are chained with this one. + */ + Builder source(String source); + /** * Create a {@link SsoCredentialsProvider} using the configuration applied to this builder. * @return @@ -220,6 +239,7 @@ protected static final class BuilderImpl implements Builder { private Duration staleTime; private Duration prefetchTime; private Supplier getRoleCredentialsRequestSupplier; + private String source; BuilderImpl() { @@ -231,6 +251,7 @@ public BuilderImpl(SsoCredentialsProvider provider) { this.staleTime = provider.staleTime; this.prefetchTime = provider.prefetchTime; this.getRoleCredentialsRequestSupplier = provider.getRoleCredentialsRequestSupplier; + this.source = provider.source; } @Override @@ -268,6 +289,12 @@ public Builder refreshRequest(Supplier getRoleCredent return this; } + @Override + public Builder source(String source) { + this.source = source; + return this; + } + @Override public SsoCredentialsProvider build() { return new SsoCredentialsProvider(this); diff --git a/services/sso/src/main/java/software/amazon/awssdk/services/sso/auth/SsoProfileCredentialsProviderFactory.java b/services/sso/src/main/java/software/amazon/awssdk/services/sso/auth/SsoProfileCredentialsProviderFactory.java index f3b910c3e1fa..efa8379315ae 100644 --- a/services/sso/src/main/java/software/amazon/awssdk/services/sso/auth/SsoProfileCredentialsProviderFactory.java +++ b/services/sso/src/main/java/software/amazon/awssdk/services/sso/auth/SsoProfileCredentialsProviderFactory.java @@ -63,10 +63,7 @@ public class SsoProfileCredentialsProviderFactory implements ProfileCredentialsP */ @Override public AwsCredentialsProvider create(ProfileProviderCredentialsContext credentialsContext) { - return new SsoProfileCredentialsProvider(credentialsContext.profile(), - credentialsContext.profileFile(), - sdkTokenProvider(credentialsContext.profile(), - credentialsContext.profileFile())); + return new SsoProfileCredentialsProvider(credentialsContext, sdkTokenProvider(credentialsContext)); } /** @@ -74,26 +71,27 @@ public AwsCredentialsProvider create(ProfileProviderCredentialsContext credentia * This method is only used for testing. */ @SdkTestInternalApi - public AwsCredentialsProvider create(Profile profile, ProfileFile profileFile, + public AwsCredentialsProvider create(ProfileProviderCredentialsContext credentialsContext, SdkTokenProvider tokenProvider) { - return new SsoProfileCredentialsProvider(profile, profileFile, tokenProvider); + return new SsoProfileCredentialsProvider(credentialsContext, tokenProvider); } /** * A wrapper for a {@link SsoCredentialsProvider} that is returned by this factory when {@link - * #create(ProfileProviderCredentialsContext)} * or {@link #create(Profile, ProfileFile, SdkTokenProvider)} is invoked. This - * wrapper is important because it ensures * the parent credentials provider is closed when the sso credentials provider is no - * longer needed. + * #create(ProfileProviderCredentialsContext)} * or {@link #create(ProfileProviderCredentialsContext, SdkTokenProvider)} + * is invoked. This wrapper is important because it ensures * the parent credentials provider is closed when the sso + * credentials provider is no longer needed. */ private static final class SsoProfileCredentialsProvider implements AwsCredentialsProvider, SdkAutoCloseable { private final SsoClient ssoClient; private final SsoCredentialsProvider credentialsProvider; - private SsoProfileCredentialsProvider(Profile profile, ProfileFile profileFile, + private SsoProfileCredentialsProvider(ProfileProviderCredentialsContext credentialsContext, SdkTokenProvider tokenProvider) { + Profile profile = credentialsContext.profile(); String ssoAccountId = profile.properties().get(ProfileProperty.SSO_ACCOUNT_ID); String ssoRoleName = profile.properties().get(ProfileProperty.SSO_ROLE_NAME); - String ssoRegion = regionFromProfileOrSession(profile, profileFile); + String ssoRegion = regionFromProfileOrSession(profile, credentialsContext.profileFile()); this.ssoClient = SsoClient.builder() .credentialsProvider(AnonymousCredentialsProvider.create()) @@ -114,6 +112,7 @@ private SsoProfileCredentialsProvider(Profile profile, ProfileFile profileFile, this.credentialsProvider = SsoCredentialsProvider.builder() .ssoClient(ssoClient) .refreshRequest(supplier) + .source(credentialsContext.source()) .build(); } @@ -157,7 +156,9 @@ private static Profile ssoSessionInProfile(String sessionName, ProfileFile profi return ssoProfile; } - private static SdkTokenProvider sdkTokenProvider(Profile profile, ProfileFile profileFile) { + private static SdkTokenProvider sdkTokenProvider(ProfileProviderCredentialsContext credentialsContext) { + Profile profile = credentialsContext.profile(); + ProfileFile profileFile = credentialsContext.profileFile(); Optional ssoSession = profile.property(ProfileSection.SSO_SESSION.getPropertyKeyName()); @@ -172,11 +173,9 @@ private static SdkTokenProvider sdkTokenProvider(Profile profile, ProfileFile pr .profileFile(() -> profileFile) .profileName(profile.name()) .build()); - } else { - return new SsoAccessTokenProvider(generateCachedTokenPath( - profile.properties().get(ProfileProperty.SSO_START_URL), TOKEN_DIRECTORY)); - } + return new SsoAccessTokenProvider(generateCachedTokenPath(profile.properties().get(ProfileProperty.SSO_START_URL), + TOKEN_DIRECTORY)); } private static void validateCommonProfileProperties(Profile profile, Profile ssoSessionProfileFile, String propertyName) { diff --git a/services/sso/src/test/java/software/amazon/awssdk/services/sso/auth/SsoCredentialsProviderTest.java b/services/sso/src/test/java/software/amazon/awssdk/services/sso/auth/SsoCredentialsProviderTest.java index 9540a77ba6c6..d7be6cdd852c 100644 --- a/services/sso/src/test/java/software/amazon/awssdk/services/sso/auth/SsoCredentialsProviderTest.java +++ b/services/sso/src/test/java/software/amazon/awssdk/services/sso/auth/SsoCredentialsProviderTest.java @@ -27,6 +27,7 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.services.sso.SsoClient; import software.amazon.awssdk.services.sso.model.GetRoleCredentialsRequest; import software.amazon.awssdk.services.sso.model.GetRoleCredentialsResponse; @@ -136,7 +137,7 @@ private void callClientWithCredentialsProvider(Instant credentialsExpirationDate assertThat(actualCredentials.accessKeyId()).isEqualTo("a"); assertThat(actualCredentials.secretAccessKey()).isEqualTo("b"); assertThat(actualCredentials.sessionToken()).isEqualTo("c"); - assertThat(actualCredentials.providerName()).isPresent().contains("SsoCredentialsProvider"); + assertThat(actualCredentials.providerName()).isPresent().contains(BusinessMetricFeatureId.CREDENTIALS_SSO.value()); assertThat(actualCredentials.accountId()).isPresent().contains("123456789"); } } diff --git a/services/sso/src/test/java/software/amazon/awssdk/services/sso/auth/SsoProfileCredentialsProviderFactoryTest.java b/services/sso/src/test/java/software/amazon/awssdk/services/sso/auth/SsoProfileCredentialsProviderFactoryTest.java index c5cb2b57834d..8da326bf589f 100644 --- a/services/sso/src/test/java/software/amazon/awssdk/services/sso/auth/SsoProfileCredentialsProviderFactoryTest.java +++ b/services/sso/src/test/java/software/amazon/awssdk/services/sso/auth/SsoProfileCredentialsProviderFactoryTest.java @@ -79,9 +79,12 @@ public void createSsoCredentialsProviderWithFactorySucceed() throws IOException cachedTokenFilePath); SsoProfileCredentialsProviderFactory factory = new SsoProfileCredentialsProviderFactory(); - assertThat(factory.create(profileFile.profile("foo").get(), - profileFile, - tokenProvider)).isInstanceOf(AwsCredentialsProvider.class); + assertThat(factory.create(ProfileProviderCredentialsContext.builder() + .profile(profileFile.profile("foo").get()) + .profileFile(profileFile) + .build(), + tokenProvider)) + .isInstanceOf(AwsCredentialsProvider.class); } private Path prepareTestCachedTokenFile(String tokenFileContent, String generatedTokenFileName) throws IOException { @@ -169,7 +172,10 @@ public void tokenResolvedFromTokenProvider(@Mock SdkTokenProvider sdkTokenProvid "sso_start_url=https//d-abc123.awsapps.com/start"); SsoProfileCredentialsProviderFactory factory = new SsoProfileCredentialsProviderFactory(); when(sdkTokenProvider.resolveToken()).thenReturn(SsoAccessToken.builder().accessToken("sample").expiresAt(Instant.now()).build()); - AwsCredentialsProvider credentialsProvider = factory.create(profileFile.profile("test").get(), profileFile, sdkTokenProvider); + AwsCredentialsProvider credentialsProvider = factory.create(ProfileProviderCredentialsContext.builder() + .profile(profileFile.profile("test").get()) + .profileFile(profileFile) + .build(), sdkTokenProvider); try { credentialsProvider.resolveCredentials(); } catch (Exception e) { diff --git a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleCredentialsProvider.java b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleCredentialsProvider.java index a59570be0103..d28861c2e16d 100644 --- a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleCredentialsProvider.java +++ b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleCredentialsProvider.java @@ -25,9 +25,11 @@ import software.amazon.awssdk.annotations.ThreadSafe; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.model.AssumeRoleRequest; import software.amazon.awssdk.services.sts.model.AssumeRoleResponse; +import software.amazon.awssdk.utils.StringUtils; import software.amazon.awssdk.utils.ToString; import software.amazon.awssdk.utils.Validate; import software.amazon.awssdk.utils.builder.ToCopyableBuilder; @@ -49,8 +51,10 @@ public final class StsAssumeRoleCredentialsProvider extends StsCredentialsProvider implements ToCopyableBuilder { - private static final String PROVIDER_NAME = "StsAssumeRoleCredentialsProvider"; + private static final String PROVIDER_NAME = BusinessMetricFeatureId.CREDENTIALS_STS_ASSUME_ROLE.value(); private final Supplier assumeRoleRequestSupplier; + private final String source; + private final String providerName; /** * @see #builder() @@ -60,6 +64,10 @@ private StsAssumeRoleCredentialsProvider(Builder builder) { Validate.notNull(builder.assumeRoleRequestSupplier, "Assume role request must not be null."); this.assumeRoleRequestSupplier = builder.assumeRoleRequestSupplier; + this.source = builder.source; + this.providerName = StringUtils.isEmpty(builder.source) + ? PROVIDER_NAME + : builder.source + "," + PROVIDER_NAME; } /** @@ -75,7 +83,7 @@ protected AwsSessionCredentials getUpdatedCredentials(StsClient stsClient) { Validate.notNull(assumeRoleRequest, "Assume role request must not be null."); AssumeRoleResponse assumeRoleResponse = stsClient.assumeRole(assumeRoleRequest); return fromStsCredentials(assumeRoleResponse.credentials(), - PROVIDER_NAME, + providerName(), accountIdFromArn(assumeRoleResponse.assumedRoleUser())); } @@ -93,7 +101,7 @@ public Builder toBuilder() { @Override String providerName() { - return PROVIDER_NAME; + return this.providerName; } /** @@ -103,6 +111,7 @@ String providerName() { @NotThreadSafe public static final class Builder extends BaseBuilder { private Supplier assumeRoleRequestSupplier; + private String source; private Builder() { super(StsAssumeRoleCredentialsProvider::new); @@ -111,6 +120,7 @@ private Builder() { private Builder(StsAssumeRoleCredentialsProvider provider) { super(StsAssumeRoleCredentialsProvider::new, provider); this.assumeRoleRequestSupplier = provider.assumeRoleRequestSupplier; + this.source = provider.source; } /** @@ -145,6 +155,17 @@ public Builder refreshRequest(Consumer assumeRoleRequ return refreshRequest(AssumeRoleRequest.builder().applyMutation(assumeRoleRequest).build()); } + /** + * An optional string list of {@link BusinessMetricFeatureId} denoting previous credentials providers + * that are chained with this one. + *

Note: This method is primarily intended for use by AWS SDK internal components. + * {@link BusinessMetricFeatureId} is a protected API and should not be used directly by external users.

+ */ + public Builder source(String source) { + this.source = source; + return this; + } + @Override public StsAssumeRoleCredentialsProvider build() { return super.build(); diff --git a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithSamlCredentialsProvider.java b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithSamlCredentialsProvider.java index 6d99b555e311..73c602fec438 100644 --- a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithSamlCredentialsProvider.java +++ b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithSamlCredentialsProvider.java @@ -25,9 +25,11 @@ import software.amazon.awssdk.annotations.ThreadSafe; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.model.AssumeRoleWithSamlRequest; import software.amazon.awssdk.services.sts.model.AssumeRoleWithSamlResponse; +import software.amazon.awssdk.utils.StringUtils; import software.amazon.awssdk.utils.Validate; import software.amazon.awssdk.utils.builder.ToCopyableBuilder; @@ -48,8 +50,10 @@ public final class StsAssumeRoleWithSamlCredentialsProvider extends StsCredentialsProvider implements ToCopyableBuilder { - private static final String PROVIDER_NAME = "StsAssumeRoleWithSamlCredentialsProvider"; + private static final String PROVIDER_NAME = BusinessMetricFeatureId.CREDENTIALS_STS_ASSUME_ROLE_SAML.value(); private final Supplier assumeRoleWithSamlRequestSupplier; + private final String source; + private final String providerName; /** @@ -60,6 +64,10 @@ private StsAssumeRoleWithSamlCredentialsProvider(Builder builder) { Validate.notNull(builder.assumeRoleWithSamlRequestSupplier, "Assume role with SAML request must not be null."); this.assumeRoleWithSamlRequestSupplier = builder.assumeRoleWithSamlRequestSupplier; + this.source = builder.source; + this.providerName = StringUtils.isEmpty(builder.source) + ? PROVIDER_NAME + : builder.source + "," + PROVIDER_NAME; } /** @@ -75,7 +83,7 @@ protected AwsSessionCredentials getUpdatedCredentials(StsClient stsClient) { Validate.notNull(assumeRoleWithSamlRequest, "Assume role with saml request must not be null."); AssumeRoleWithSamlResponse assumeRoleResponse = stsClient.assumeRoleWithSAML(assumeRoleWithSamlRequest); return fromStsCredentials(assumeRoleResponse.credentials(), - PROVIDER_NAME, + providerName(), accountIdFromArn(assumeRoleResponse.assumedRoleUser())); } @@ -86,7 +94,7 @@ public Builder toBuilder() { @Override String providerName() { - return PROVIDER_NAME; + return this.providerName; } /** @@ -96,6 +104,7 @@ String providerName() { @NotThreadSafe public static final class Builder extends BaseBuilder { private Supplier assumeRoleWithSamlRequestSupplier; + private String source; private Builder() { super(StsAssumeRoleWithSamlCredentialsProvider::new); @@ -104,6 +113,7 @@ private Builder() { public Builder(StsAssumeRoleWithSamlCredentialsProvider provider) { super(StsAssumeRoleWithSamlCredentialsProvider::new, provider); this.assumeRoleWithSamlRequestSupplier = provider.assumeRoleWithSamlRequestSupplier; + this.source = provider.source; } /** @@ -138,6 +148,21 @@ public Builder refreshRequest(Consumer assume return refreshRequest(AssumeRoleWithSamlRequest.builder().applyMutation(assumeRoleWithSamlRequest).build()); } + /** + * Configure the source of this credentials provider. This is used for business metrics tracking + * to identify the credential provider chain. + * + *

Note: This method is primarily intended for use by AWS SDK internal components. + * {@link BusinessMetricFeatureId} is a protected API and should not be used directly by external users.

+ * + * @param source The source identifier for business metrics tracking. + * @return This object for chained calls. + */ + public Builder source(String source) { + this.source = source; + return this; + } + @Override public StsAssumeRoleWithSamlCredentialsProvider build() { return super.build(); diff --git a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithWebIdentityCredentialsProvider.java b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithWebIdentityCredentialsProvider.java index 4cbb325f7458..2653b967b581 100644 --- a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithWebIdentityCredentialsProvider.java +++ b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithWebIdentityCredentialsProvider.java @@ -26,9 +26,11 @@ import software.amazon.awssdk.annotations.ThreadSafe; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.model.AssumeRoleWithWebIdentityRequest; import software.amazon.awssdk.services.sts.model.AssumeRoleWithWebIdentityResponse; +import software.amazon.awssdk.utils.StringUtils; import software.amazon.awssdk.utils.builder.ToCopyableBuilder; /** @@ -49,8 +51,10 @@ public final class StsAssumeRoleWithWebIdentityCredentialsProvider extends StsCredentialsProvider implements ToCopyableBuilder { - private static final String PROVIDER_NAME = "StsAssumeRoleWithWebIdentityCredentialsProvider"; + private static final String PROVIDER_NAME = BusinessMetricFeatureId.CREDENTIALS_STS_ASSUME_ROLE_WEB_ID.value(); private final Supplier assumeRoleWithWebIdentityRequest; + private final String source; + private final String providerName; /** * @see #builder() @@ -60,6 +64,10 @@ private StsAssumeRoleWithWebIdentityCredentialsProvider(Builder builder) { notNull(builder.assumeRoleWithWebIdentityRequestSupplier, "Assume role with web identity request must not be null."); this.assumeRoleWithWebIdentityRequest = builder.assumeRoleWithWebIdentityRequestSupplier; + this.source = builder.source; + this.providerName = StringUtils.isEmpty(builder.source) + ? PROVIDER_NAME + : builder.source + "," + PROVIDER_NAME; } /** @@ -75,7 +83,7 @@ protected AwsSessionCredentials getUpdatedCredentials(StsClient stsClient) { notNull(request, "AssumeRoleWithWebIdentityRequest can't be null"); AssumeRoleWithWebIdentityResponse assumeRoleResponse = stsClient.assumeRoleWithWebIdentity(request); return fromStsCredentials(assumeRoleResponse.credentials(), - PROVIDER_NAME, + providerName(), accountIdFromArn(assumeRoleResponse.assumedRoleUser())); } @@ -86,7 +94,7 @@ public Builder toBuilder() { @Override String providerName() { - return PROVIDER_NAME; + return this.providerName; } /** @@ -96,6 +104,7 @@ String providerName() { @NotThreadSafe public static final class Builder extends BaseBuilder { private Supplier assumeRoleWithWebIdentityRequestSupplier; + private String source; private Builder() { super(StsAssumeRoleWithWebIdentityCredentialsProvider::new); @@ -104,6 +113,7 @@ private Builder() { public Builder(StsAssumeRoleWithWebIdentityCredentialsProvider provider) { super(StsAssumeRoleWithWebIdentityCredentialsProvider::new, provider); this.assumeRoleWithWebIdentityRequestSupplier = provider.assumeRoleWithWebIdentityRequest; + this.source = provider.source; } /** @@ -139,6 +149,21 @@ public Builder refreshRequest(Consumer .build()); } + /** + * Configure the source of this credentials provider. This is used for business metrics tracking + * to identify the credential provider chain. + * + *

Note: This method is primarily intended for use by AWS SDK internal components. + * {@link BusinessMetricFeatureId} is a protected API and should not be used directly by external users.

+ * + * @param source The source identifier for business metrics tracking. + * @return This object for chained calls. + */ + public Builder source(String source) { + this.source = source; + return this; + } + @Override public StsAssumeRoleWithWebIdentityCredentialsProvider build() { return super.build(); diff --git a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsGetFederationTokenCredentialsProvider.java b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsGetFederationTokenCredentialsProvider.java index da28815b686e..781b3008c644 100644 --- a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsGetFederationTokenCredentialsProvider.java +++ b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsGetFederationTokenCredentialsProvider.java @@ -23,11 +23,13 @@ import software.amazon.awssdk.annotations.ThreadSafe; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.endpoints.internal.Arn; import software.amazon.awssdk.services.sts.model.FederatedUser; import software.amazon.awssdk.services.sts.model.GetFederationTokenRequest; import software.amazon.awssdk.services.sts.model.GetFederationTokenResponse; +import software.amazon.awssdk.utils.StringUtils; import software.amazon.awssdk.utils.Validate; import software.amazon.awssdk.utils.builder.ToCopyableBuilder; @@ -48,9 +50,11 @@ public class StsGetFederationTokenCredentialsProvider extends StsCredentialsProvider implements ToCopyableBuilder { - private static final String PROVIDER_NAME = "StsGetFederationTokenCredentialsProvider"; + private static final String PROVIDER_NAME = BusinessMetricFeatureId.CREDENTIALS_STS_FEDERATION_TOKEN.value(); private final GetFederationTokenRequest getFederationTokenRequest; + private final String source; + private final String providerName; /** * @see #builder() @@ -60,6 +64,10 @@ private StsGetFederationTokenCredentialsProvider(Builder builder) { Validate.notNull(builder.getFederationTokenRequest, "Get session token request must not be null."); this.getFederationTokenRequest = builder.getFederationTokenRequest; + this.source = builder.source; + this.providerName = StringUtils.isEmpty(builder.source) + ? PROVIDER_NAME + : builder.source + "," + PROVIDER_NAME; } /** @@ -73,7 +81,7 @@ public static Builder builder() { protected AwsSessionCredentials getUpdatedCredentials(StsClient stsClient) { GetFederationTokenResponse federationToken = stsClient.getFederationToken(getFederationTokenRequest); return fromStsCredentials(federationToken.credentials(), - PROVIDER_NAME, + providerName(), accountIdFromArn(federationToken.federatedUser())); } @@ -93,7 +101,7 @@ public Builder toBuilder() { @Override String providerName() { - return PROVIDER_NAME; + return this.providerName; } /** @@ -103,6 +111,7 @@ String providerName() { @NotThreadSafe public static final class Builder extends BaseBuilder { private GetFederationTokenRequest getFederationTokenRequest; + private String source; private Builder() { super(StsGetFederationTokenCredentialsProvider::new); @@ -111,6 +120,7 @@ private Builder() { public Builder(StsGetFederationTokenCredentialsProvider provider) { super(StsGetFederationTokenCredentialsProvider::new, provider); this.getFederationTokenRequest = provider.getFederationTokenRequest; + this.source = provider.source; } /** @@ -134,6 +144,21 @@ public Builder refreshRequest(Consumer getFed return refreshRequest(GetFederationTokenRequest.builder().applyMutation(getFederationTokenRequest).build()); } + /** + * Configure the source of this credentials provider. This is used for business metrics tracking + * to identify the credential provider chain. + * + *

Note: This method is primarily intended for use by AWS SDK internal components. + * {@link BusinessMetricFeatureId} is a protected API and should not be used directly by external users.

+ * + * @param source The source identifier for business metrics tracking. + * @return This object for chained calls. + */ + public Builder source(String source) { + this.source = source; + return this; + } + @Override public StsGetFederationTokenCredentialsProvider build() { return super.build(); diff --git a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsGetSessionTokenCredentialsProvider.java b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsGetSessionTokenCredentialsProvider.java index 8ca66114d2be..6828084b5203 100644 --- a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsGetSessionTokenCredentialsProvider.java +++ b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsGetSessionTokenCredentialsProvider.java @@ -23,9 +23,11 @@ import software.amazon.awssdk.annotations.ThreadSafe; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.model.GetSessionTokenRequest; import software.amazon.awssdk.services.sts.model.GetSessionTokenResponse; +import software.amazon.awssdk.utils.StringUtils; import software.amazon.awssdk.utils.Validate; import software.amazon.awssdk.utils.builder.ToCopyableBuilder; @@ -46,9 +48,11 @@ public class StsGetSessionTokenCredentialsProvider extends StsCredentialsProvider implements ToCopyableBuilder { - private static final String PROVIDER_NAME = "StsGetSessionTokenCredentialsProvider"; + private static final String PROVIDER_NAME = BusinessMetricFeatureId.CREDENTIALS_STS_SESSION_TOKEN.value(); private final GetSessionTokenRequest getSessionTokenRequest; + private final String source; + private final String providerName; /** * @see #builder() @@ -58,6 +62,10 @@ private StsGetSessionTokenCredentialsProvider(Builder builder) { Validate.notNull(builder.getSessionTokenRequest, "Get session token request must not be null."); this.getSessionTokenRequest = builder.getSessionTokenRequest; + this.source = builder.source; + this.providerName = StringUtils.isEmpty(builder.source) + ? PROVIDER_NAME + : builder.source + "," + PROVIDER_NAME; } /** @@ -70,7 +78,7 @@ public static Builder builder() { @Override protected AwsSessionCredentials getUpdatedCredentials(StsClient stsClient) { GetSessionTokenResponse sessionToken = stsClient.getSessionToken(getSessionTokenRequest); - return fromStsCredentials(sessionToken.credentials(), PROVIDER_NAME); + return fromStsCredentials(sessionToken.credentials(), providerName()); } @Override @@ -80,7 +88,7 @@ public Builder toBuilder() { @Override String providerName() { - return PROVIDER_NAME; + return this.providerName; } /** @@ -90,6 +98,7 @@ String providerName() { @NotThreadSafe public static final class Builder extends BaseBuilder { private GetSessionTokenRequest getSessionTokenRequest = GetSessionTokenRequest.builder().build(); + private String source; private Builder() { super(StsGetSessionTokenCredentialsProvider::new); @@ -98,6 +107,7 @@ private Builder() { public Builder(StsGetSessionTokenCredentialsProvider provider) { super(StsGetSessionTokenCredentialsProvider::new, provider); this.getSessionTokenRequest = provider.getSessionTokenRequest; + this.source = provider.source; } /** @@ -122,6 +132,21 @@ public Builder refreshRequest(GetSessionTokenRequest getSessionTokenRequest) { public Builder refreshRequest(Consumer getFederationTokenRequest) { return refreshRequest(GetSessionTokenRequest.builder().applyMutation(getFederationTokenRequest).build()); } + + /** + * Configure the source of this credentials provider. This is used for business metrics tracking + * to identify the credential provider chain. + * + *

Note: This method is primarily intended for use by AWS SDK internal components. + * {@link BusinessMetricFeatureId} is a protected API and should not be used directly by external users.

+ * + * @param source The source identifier for business metrics tracking. + * @return This object for chained calls. + */ + public Builder source(String source) { + this.source = source; + return this; + } @Override public StsGetSessionTokenCredentialsProvider build() { diff --git a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsWebIdentityTokenFileCredentialsProvider.java b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsWebIdentityTokenFileCredentialsProvider.java index c812da56e21e..c4ca16469e8b 100644 --- a/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsWebIdentityTokenFileCredentialsProvider.java +++ b/services/sts/src/main/java/software/amazon/awssdk/services/sts/auth/StsWebIdentityTokenFileCredentialsProvider.java @@ -22,6 +22,7 @@ import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Optional; import java.util.function.Consumer; import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkPublicApi; @@ -30,6 +31,7 @@ import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; import software.amazon.awssdk.auth.credentials.internal.WebIdentityTokenCredentialProperties; import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.internal.AssumeRoleWithWebIdentityRequestSupplier; import software.amazon.awssdk.services.sts.model.AssumeRoleWithWebIdentityRequest; @@ -56,7 +58,7 @@ public final class StsWebIdentityTokenFileCredentialsProvider extends StsCredentialsProvider implements ToCopyableBuilder { - private static final String PROVIDER_NAME = "StsWebIdentityTokenFileCredentialsProvider"; + private static final String PROVIDER_NAME = BusinessMetricFeatureId.CREDENTIALS_ENV_VARS_STS_WEB_ID_TOKEN.value(); private final AwsCredentialsProvider credentialsProvider; private final RuntimeException loadException; @@ -132,7 +134,16 @@ public AwsCredentials resolveCredentials() { if (loadException != null) { throw loadException; } - return credentialsProvider.resolveCredentials(); + AwsCredentials awsCredentials = credentialsProvider.resolveCredentials(); + if (awsCredentials instanceof AwsSessionCredentials) { + AwsSessionCredentials sessionCredentials = (AwsSessionCredentials) awsCredentials; + Optional providerName = awsCredentials.providerName(); + if (providerName.isPresent() && !providerName.get().isEmpty()) { + return sessionCredentials.copy(s -> s.providerName(providerName.get() + "," + PROVIDER_NAME)); + } + return sessionCredentials.copy(s -> s.providerName(PROVIDER_NAME)); + } + return awsCredentials; } @Override @@ -303,4 +314,4 @@ public StsWebIdentityTokenFileCredentialsProvider build() { } } -} \ No newline at end of file +} diff --git a/services/sts/src/main/java/software/amazon/awssdk/services/sts/internal/AssumeRoleWithWebIdentityRequestSupplier.java b/services/sts/src/main/java/software/amazon/awssdk/services/sts/internal/AssumeRoleWithWebIdentityRequestSupplier.java index 03b91890af8a..95162bfc00d2 100644 --- a/services/sts/src/main/java/software/amazon/awssdk/services/sts/internal/AssumeRoleWithWebIdentityRequestSupplier.java +++ b/services/sts/src/main/java/software/amazon/awssdk/services/sts/internal/AssumeRoleWithWebIdentityRequestSupplier.java @@ -20,6 +20,7 @@ import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Optional; import java.util.function.Supplier; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.services.sts.model.AssumeRoleWithWebIdentityRequest; @@ -31,12 +32,13 @@ public class AssumeRoleWithWebIdentityRequestSupplier implements Supplier source() { + return Optional.ofNullable(source); + } + //file extraction private String getToken(Path file) { try (InputStream webIdentityTokenStream = Files.newInputStream(file)) { @@ -63,6 +69,7 @@ public static class Builder { private Path webIdentityTokenFile; + private String source; public Builder assumeRoleWithWebIdentityRequest(AssumeRoleWithWebIdentityRequest request) { this.request = request; @@ -78,6 +85,11 @@ public AssumeRoleWithWebIdentityRequestSupplier build() { return new AssumeRoleWithWebIdentityRequestSupplier(this); } + public Builder source(String source) { + this.source = source; + return this; + } + } -} \ No newline at end of file +} diff --git a/services/sts/src/main/java/software/amazon/awssdk/services/sts/internal/StsProfileCredentialsProviderFactory.java b/services/sts/src/main/java/software/amazon/awssdk/services/sts/internal/StsProfileCredentialsProviderFactory.java index 4e5559e73680..4de1b3656b82 100644 --- a/services/sts/src/main/java/software/amazon/awssdk/services/sts/internal/StsProfileCredentialsProviderFactory.java +++ b/services/sts/src/main/java/software/amazon/awssdk/services/sts/internal/StsProfileCredentialsProviderFactory.java @@ -41,21 +41,21 @@ public final class StsProfileCredentialsProviderFactory implements ChildProfileC + "'%s' profile."; @Override - public AwsCredentialsProvider create(AwsCredentialsProvider sourceCredentialsProvider, Profile profile) { - return new StsProfileCredentialsProvider(sourceCredentialsProvider, profile); + public AwsCredentialsProvider create(ChildProfileCredentialsRequest request) { + return new StsProfileCredentialsProvider(request.sourceCredentialsProvider(), request.profile(), request.source()); } /** * A wrapper for a {@link StsAssumeRoleCredentialsProvider} that is returned by this factory when - * {@link #create(AwsCredentialsProvider, Profile)} is invoked. This wrapper is important because it ensures the parent - * credentials provider is closed when the assume-role credentials provider is no longer needed. + * {@link #create(ChildProfileCredentialsRequest)} is invoked. This wrapper is important because it ensures the + * parent credentials provider is closed when the assume-role credentials provider is no longer needed. */ private static final class StsProfileCredentialsProvider implements AwsCredentialsProvider, SdkAutoCloseable { private final StsClient stsClient; private final AwsCredentialsProvider parentCredentialsProvider; private final StsAssumeRoleCredentialsProvider credentialsProvider; - private StsProfileCredentialsProvider(AwsCredentialsProvider parentCredentialsProvider, Profile profile) { + private StsProfileCredentialsProvider(AwsCredentialsProvider parentCredentialsProvider, Profile profile, String source) { String roleArn = requireProperty(profile, ProfileProperty.ROLE_ARN); String roleSessionName = profile.property(ProfileProperty.ROLE_SESSION_NAME) .orElseGet(() -> "aws-sdk-java-" + System.currentTimeMillis()); @@ -76,6 +76,7 @@ private StsProfileCredentialsProvider(AwsCredentialsProvider parentCredentialsPr this.credentialsProvider = StsAssumeRoleCredentialsProvider.builder() .stsClient(stsClient) .refreshRequest(assumeRoleRequest) + .source(source) .build(); } diff --git a/services/sts/src/main/java/software/amazon/awssdk/services/sts/internal/StsWebIdentityCredentialsProviderFactory.java b/services/sts/src/main/java/software/amazon/awssdk/services/sts/internal/StsWebIdentityCredentialsProviderFactory.java index 86340d4f857d..8d964ea79fdd 100644 --- a/services/sts/src/main/java/software/amazon/awssdk/services/sts/internal/StsWebIdentityCredentialsProviderFactory.java +++ b/services/sts/src/main/java/software/amazon/awssdk/services/sts/internal/StsWebIdentityCredentialsProviderFactory.java @@ -87,13 +87,15 @@ private StsWebIdentityCredentialsProvider(WebIdentityTokenCredentialProperties c AssumeRoleWithWebIdentityRequestSupplier.builder() .assumeRoleWithWebIdentityRequest(requestBuilder.build()) .webIdentityTokenFile(credentialProperties.webIdentityTokenFile()) + .source(credentialProperties.source()) .build(); StsAssumeRoleWithWebIdentityCredentialsProvider.Builder builder = StsAssumeRoleWithWebIdentityCredentialsProvider.builder() .asyncCredentialUpdateEnabled(asyncCredentialUpdateEnabled) .stsClient(stsClient) - .refreshRequest(supplier); + .refreshRequest(supplier) + .source(credentialProperties.source()); if (credentialProperties.prefetchTime() != null) { builder.prefetchTime(credentialProperties.prefetchTime()); diff --git a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleCredentialsProviderTest.java b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleCredentialsProviderTest.java index e4d7b6c6bc5c..b36cd6e67613 100644 --- a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleCredentialsProviderTest.java +++ b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleCredentialsProviderTest.java @@ -15,6 +15,7 @@ package software.amazon.awssdk.services.sts.auth; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.model.AssumeRoleRequest; import software.amazon.awssdk.services.sts.model.AssumeRoleResponse; @@ -51,6 +52,6 @@ protected AssumeRoleResponse callClient(StsClient client, AssumeRoleRequest requ @Override protected String providerName() { - return "StsAssumeRoleCredentialsProvider"; + return BusinessMetricFeatureId.CREDENTIALS_STS_ASSUME_ROLE.value(); } } diff --git a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithSamlCredentialsProviderTest.java b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithSamlCredentialsProviderTest.java index fb4729f98f79..34c503ac37da 100644 --- a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithSamlCredentialsProviderTest.java +++ b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithSamlCredentialsProviderTest.java @@ -15,6 +15,7 @@ package software.amazon.awssdk.services.sts.auth; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.auth.StsAssumeRoleWithSamlCredentialsProvider.Builder; import software.amazon.awssdk.services.sts.model.AssumeRoleWithSamlRequest; @@ -54,6 +55,6 @@ protected AssumeRoleWithSamlResponse callClient(StsClient client, AssumeRoleWith @Override protected String providerName() { - return "StsAssumeRoleWithSamlCredentialsProvider"; + return BusinessMetricFeatureId.CREDENTIALS_STS_ASSUME_ROLE_SAML.value(); } } diff --git a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithWebIdentityCredentialsProviderTest.java b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithWebIdentityCredentialsProviderTest.java index d037597897a2..8f1e1c4808c3 100644 --- a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithWebIdentityCredentialsProviderTest.java +++ b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsAssumeRoleWithWebIdentityCredentialsProviderTest.java @@ -15,6 +15,7 @@ package software.amazon.awssdk.services.sts.auth; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.auth.StsAssumeRoleWithWebIdentityCredentialsProvider.Builder; import software.amazon.awssdk.services.sts.model.AssumeRoleWithWebIdentityRequest; @@ -53,6 +54,6 @@ protected AssumeRoleWithWebIdentityResponse callClient(StsClient client, AssumeR @Override protected String providerName() { - return "StsAssumeRoleWithWebIdentityCredentialsProvider"; + return BusinessMetricFeatureId.CREDENTIALS_STS_ASSUME_ROLE_WEB_ID.value(); } } diff --git a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsGetFederationTokenCredentialsProviderTest.java b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsGetFederationTokenCredentialsProviderTest.java index bdc50a817aaa..b5154f646ff6 100644 --- a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsGetFederationTokenCredentialsProviderTest.java +++ b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsGetFederationTokenCredentialsProviderTest.java @@ -15,6 +15,7 @@ package software.amazon.awssdk.services.sts.auth; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.auth.StsGetFederationTokenCredentialsProvider.Builder; import software.amazon.awssdk.services.sts.model.AssumedRoleUser; @@ -54,6 +55,6 @@ protected GetFederationTokenResponse callClient(StsClient client, GetFederationT @Override protected String providerName() { - return "StsGetFederationTokenCredentialsProvider"; + return BusinessMetricFeatureId.CREDENTIALS_STS_FEDERATION_TOKEN.value(); } } diff --git a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsGetSessionTokenCredentialsProviderTest.java b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsGetSessionTokenCredentialsProviderTest.java index 18f9feadf796..1ab263152602 100644 --- a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsGetSessionTokenCredentialsProviderTest.java +++ b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsGetSessionTokenCredentialsProviderTest.java @@ -15,6 +15,7 @@ package software.amazon.awssdk.services.sts.auth; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.auth.StsGetSessionTokenCredentialsProvider.Builder; import software.amazon.awssdk.services.sts.model.AssumedRoleUser; @@ -52,6 +53,6 @@ protected GetSessionTokenResponse callClient(StsClient client, GetSessionTokenRe @Override protected String providerName() { - return "StsGetSessionTokenCredentialsProvider"; + return BusinessMetricFeatureId.CREDENTIALS_STS_SESSION_TOKEN.value(); } } diff --git a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsWebIdentityTokenCredentialsProviderBaseTest.java b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsWebIdentityTokenCredentialsProviderBaseTest.java index cb3ca75140bf..7d64f194edde 100644 --- a/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsWebIdentityTokenCredentialsProviderBaseTest.java +++ b/services/sts/src/test/java/software/amazon/awssdk/services/sts/auth/StsWebIdentityTokenCredentialsProviderBaseTest.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.services.sts.auth.StsWebIdentityTokenFileCredentialsProvider.Builder; import software.amazon.awssdk.services.sts.model.AssumeRoleWithWebIdentityRequest; @@ -83,7 +84,8 @@ protected AssumeRoleWithWebIdentityResponse callClient(StsClient client, AssumeR @Override protected String providerName() { - return "StsAssumeRoleWithWebIdentityCredentialsProvider"; + return String.format("%s,%s", BusinessMetricFeatureId.CREDENTIALS_STS_ASSUME_ROLE_WEB_ID, + BusinessMetricFeatureId.CREDENTIALS_ENV_VARS_STS_WEB_ID_TOKEN.value()); } private String getToken(Path file) { diff --git a/test/auth-tests/pom.xml b/test/auth-tests/pom.xml index f6c77323c33a..04d51b409c7c 100644 --- a/test/auth-tests/pom.xml +++ b/test/auth-tests/pom.xml @@ -141,6 +141,10 @@ log4j-slf4j-impl test + + software.amazon.awssdk + test-utils + diff --git a/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/EnvironmentVariableCredentialsProviderUserAgentTest.java b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/EnvironmentVariableCredentialsProviderUserAgentTest.java index b026d1e3b43d..8c171a3ed57a 100644 --- a/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/EnvironmentVariableCredentialsProviderUserAgentTest.java +++ b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/EnvironmentVariableCredentialsProviderUserAgentTest.java @@ -36,6 +36,7 @@ import software.amazon.awssdk.services.sts.StsClient; import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient; import software.amazon.awssdk.utils.StringInputStream; +import software.amazon.awssdk.testutils.EnvironmentVariableHelper; /** * Test class to verify that EnvironmentVariableCredentialsProvider correctly includes @@ -45,6 +46,7 @@ class EnvironmentVariableCredentialsProviderUserAgentTest { private MockSyncHttpClient mockHttpClient; + private static final EnvironmentVariableHelper ENVIRONMENT_VARIABLE_HELPER = new EnvironmentVariableHelper(); @BeforeEach public void setup() { @@ -52,6 +54,8 @@ public void setup() { // Configure environment variable credentials System.setProperty(SdkSystemSetting.AWS_ACCESS_KEY_ID.property(), "test-access-key"); System.setProperty(SdkSystemSetting.AWS_SECRET_ACCESS_KEY.property(), "test-secret-key"); + ENVIRONMENT_VARIABLE_HELPER.set(SdkSystemSetting.AWS_ACCESS_KEY_ID.environmentVariable(), "akid2"); + ENVIRONMENT_VARIABLE_HELPER.set(SdkSystemSetting.AWS_SECRET_ACCESS_KEY.environmentVariable(), "skid2"); mockHttpClient = new MockSyncHttpClient(); mockHttpClient.stubNextResponse(mockStsResponse()); @@ -75,8 +79,7 @@ private static HttpExecuteResponse mockStsResponse() { @MethodSource("environmentVariableCredentialProviders") void userAgentString_containsEnvironmentVariableBusinessMetric_WhenUsingEnvironmentVariableCredentials( IdentityProvider provider, String expected) throws Exception { - - try { + stsClient(provider, mockHttpClient).getCallerIdentity(); SdkHttpRequest lastRequest = mockHttpClient.getLastRequest(); @@ -85,8 +88,7 @@ void userAgentString_containsEnvironmentVariableBusinessMetric_WhenUsingEnvironm List userAgentHeaders = lastRequest.headers().get("User-Agent"); assertThat(userAgentHeaders).isNotNull().hasSize(1); assertThat(userAgentHeaders.get(0)).contains(expected); - } catch (Exception e) { - } + } private static Stream environmentVariableCredentialProviders() { @@ -101,8 +103,7 @@ void userAgentString_containsEnvironmentVariableBusinessMetric_WhenUsingEnvironm IdentityProvider provider, String expected) throws Exception { System.setProperty(SdkSystemSetting.AWS_SESSION_TOKEN.property(), "test-session-token"); - - try { + stsClient(provider, mockHttpClient).getCallerIdentity(); SdkHttpRequest lastRequest = mockHttpClient.getLastRequest(); @@ -111,8 +112,6 @@ void userAgentString_containsEnvironmentVariableBusinessMetric_WhenUsingEnvironm List userAgentHeaders = lastRequest.headers().get("User-Agent"); assertThat(userAgentHeaders).isNotNull().hasSize(1); assertThat(userAgentHeaders.get(0)).contains(expected); - } catch (Exception e) { - } } private static Stream environmentVariableCredentialProvidersWithSessionToken() { diff --git a/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/ProcessCredentialsProviderUserAgentTest.java b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/ProcessCredentialsProviderUserAgentTest.java index 851fb9826036..0731a48b071f 100644 --- a/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/ProcessCredentialsProviderUserAgentTest.java +++ b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/ProcessCredentialsProviderUserAgentTest.java @@ -65,7 +65,7 @@ private static HttpExecuteResponse mockStsResponse() { @MethodSource("processCredentialProviders") void userAgentString_containsProcessBusinessMetric_WhenUsingProcessCredentials( IdentityProvider provider, String expected) throws Exception { - + stsClient(provider, mockHttpClient).getCallerIdentity(); SdkHttpRequest lastRequest = mockHttpClient.getLastRequest(); @@ -79,7 +79,7 @@ void userAgentString_containsProcessBusinessMetric_WhenUsingProcessCredentials( private static Stream processCredentialProviders() { String mockCommand = createMockCredentialsCommand(false); List mockCommandList = createMockCredentialsCommandList(false); - + return Stream.of( Arguments.of(ProcessCredentialsProvider.builder() .command(mockCommand) @@ -95,7 +95,7 @@ private static Stream processCredentialProviders() { @MethodSource("processCredentialProvidersWithSessionToken") void userAgentString_containsProcessBusinessMetric_WhenUsingProcessCredentialsWithSessionToken( IdentityProvider provider, String expected) throws Exception { - + stsClient(provider, mockHttpClient).getCallerIdentity(); SdkHttpRequest lastRequest = mockHttpClient.getLastRequest(); @@ -108,7 +108,7 @@ void userAgentString_containsProcessBusinessMetric_WhenUsingProcessCredentialsWi private static Stream processCredentialProvidersWithSessionToken() { String mockCommand = createMockCredentialsCommand(true); - + return Stream.of( Arguments.of(ProcessCredentialsProvider.builder() .command(mockCommand) @@ -124,7 +124,7 @@ private static String createMockCredentialsCommand(boolean includeSessionToken) private static List createMockCredentialsCommandList(boolean includeSessionToken) { String credentialsJson = createCredentialsJson(includeSessionToken); - + // Use echo command as a list return Arrays.asList("echo", credentialsJson); } @@ -135,15 +135,15 @@ private static String createCredentialsJson(boolean includeSessionToken) { json.append("\"Version\": 1,"); json.append("\"AccessKeyId\": \"test-access-key\","); json.append("\"SecretAccessKey\": \"test-secret-key\""); - + if (includeSessionToken) { json.append(",\"SessionToken\": \"test-session-token\""); } - + // Add expiration time (1 hour from now) String expiration = DateUtils.formatIso8601Date(Instant.now().plus(1, ChronoUnit.HOURS)); json.append(",\"Expiration\": \"").append(expiration).append("\""); - + json.append("}"); return json.toString(); } diff --git a/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/ProfileCredentialProviderUserAgentTest.java b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/ProfileCredentialProviderUserAgentTest.java new file mode 100644 index 000000000000..1bf7a1ecd3b7 --- /dev/null +++ b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/ProfileCredentialProviderUserAgentTest.java @@ -0,0 +1,150 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.auth.source; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider; +import software.amazon.awssdk.http.AbortableInputStream; +import software.amazon.awssdk.http.HttpExecuteResponse; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.SdkHttpResponse; +import software.amazon.awssdk.profiles.ProfileFile; +import software.amazon.awssdk.services.sts.StsClient; +import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient; +import software.amazon.awssdk.utils.StringInputStream; + +/** + * Test class to verify Profile credentials provider business metrics. + */ +class ProfileCredentialProviderUserAgentTest { + + private MockSyncHttpClient mockHttpClient; + private Path tempConfigFile; + + @BeforeEach + public void setup() { + mockHttpClient = new MockSyncHttpClient(); + mockHttpClient.stubNextResponse(mockStsResponse()); + } + + @AfterEach + public void teardown() throws IOException { + if (tempConfigFile != null && Files.exists(tempConfigFile)) { + Files.delete(tempConfigFile); + } + } + + private static HttpExecuteResponse mockStsResponse() { + return HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder().statusCode(200).build()) + .responseBody(AbortableInputStream.create(new StringInputStream(""))) + .build(); + } + + // Basic profile credentials - Expected Feature ID: "n" + @Test + void basicProfileCredentials_containsFeatureIdN() throws Exception { + String configContent = + "[profile A]\n" + + "aws_access_key_id = abc123\n" + + "aws_secret_access_key = def456\n"; + + tempConfigFile = Files.createTempFile("aws-config-basic-", ".tmp"); + Files.write(tempConfigFile, configContent.getBytes()); + + ProfileFile profileFile = ProfileFile.builder() + .content(tempConfigFile) + .type(ProfileFile.Type.CONFIGURATION) + .build(); + + ProfileCredentialsProvider credentialsProvider = ProfileCredentialsProvider.builder() + .profileFile(profileFile) + .profileName("A") + .build(); + + StsClient stsClient = StsClient.builder() + .credentialsProvider(credentialsProvider) + .httpClient(mockHttpClient) + .build(); + + stsClient.getCallerIdentity(); + + assertThat(mockHttpClient.getRequests()).hasSize(1); + + SdkHttpRequest lastRequest = mockHttpClient.getLastRequest(); + assertThat(lastRequest).isNotNull(); + + List userAgentHeaders = lastRequest.headers().get("User-Agent"); + assertThat(userAgentHeaders).isNotNull().hasSize(1); + String userAgent = userAgentHeaders.get(0); + + assertThat(userAgent).contains("m/D,n"); + + credentialsProvider.close(); + stsClient.close(); + } + + //Profile with credential_process - Expected Feature IDs: "v,w" + @Test + void profileWithCredentialProcess_containsFeatureIdVW() throws Exception { + String configContent = + "[profile A]\n" + + "credential_process = echo '{\"Version\": 1, \"AccessKeyId\": \"abc123\", \"SecretAccessKey\": \"def456\"}'\n"; + + tempConfigFile = Files.createTempFile("aws-config-process-", ".tmp"); + Files.write(tempConfigFile, configContent.getBytes()); + + ProfileFile profileFile = ProfileFile.builder() + .content(tempConfigFile) + .type(ProfileFile.Type.CONFIGURATION) + .build(); + + ProfileCredentialsProvider credentialsProvider = ProfileCredentialsProvider.builder() + .profileFile(profileFile) + .profileName("A") + .build(); + + StsClient stsClient = StsClient.builder() + .credentialsProvider(credentialsProvider) + .httpClient(mockHttpClient) + .build(); + + stsClient.getCallerIdentity(); + + assertThat(mockHttpClient.getRequests()).hasSize(1); + + SdkHttpRequest lastRequest = mockHttpClient.getLastRequest(); + assertThat(lastRequest).isNotNull(); + + List userAgentHeaders = lastRequest.headers().get("User-Agent"); + assertThat(userAgentHeaders).isNotNull().hasSize(1); + String userAgent = userAgentHeaders.get(0); + + assertThat(userAgent).contains("m/D,v,w"); + + credentialsProvider.close(); + stsClient.close(); + } + +} diff --git a/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/WebIdentityTokenFileCredentialsProviderUserAgentTest.java b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/WebIdentityTokenFileCredentialsProviderUserAgentTest.java deleted file mode 100644 index b3d47b961a87..000000000000 --- a/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/WebIdentityTokenFileCredentialsProviderUserAgentTest.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package software.amazon.awssdk.auth.source; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.stream.Stream; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import software.amazon.awssdk.auth.credentials.WebIdentityTokenFileCredentialsProvider; -import software.amazon.awssdk.http.AbortableInputStream; -import software.amazon.awssdk.http.HttpExecuteResponse; -import software.amazon.awssdk.http.SdkHttpClient; -import software.amazon.awssdk.http.SdkHttpRequest; -import software.amazon.awssdk.http.SdkHttpResponse; -import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; -import software.amazon.awssdk.identity.spi.IdentityProvider; -import software.amazon.awssdk.services.sts.StsClient; -import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient; -import software.amazon.awssdk.utils.StringInputStream; - -/** - * Test class to verify that WebIdentityTokenFileCredentialsProvider correctly includes - * business metrics in the User-Agent header. This test focuses specifically on the - * CREDENTIALS_ENV_VARS_STS_WEB_ID_TOKEN ("h") business metric feature ID. - */ -class WebIdentityTokenFileCredentialsProviderUserAgentTest { - - private static final String ROLE_ARN = "arn:aws:iam::123456789012:role/TestRole"; - private static final String ROLE_SESSION_NAME = "test-session"; - - private MockSyncHttpClient mockHttpClient; - private Path tokenFile; - - @BeforeEach - public void setup() throws IOException { - String existingTokenPath = Paths.get("../../services/sts/src/test/resources/token.jwt").toAbsolutePath().toString(); - byte[] tokenBytes = Files.readAllBytes(Paths.get(existingTokenPath)); - String tokenContent = new String(tokenBytes); - - tokenFile = Files.createTempFile("web-identity-token", ".jwt"); - Files.write(tokenFile, tokenContent.getBytes()); - - mockHttpClient = new MockSyncHttpClient(); - mockHttpClient.stubNextResponse(mockStsResponse()); - } - - @AfterEach - public void teardown() throws IOException { - if (tokenFile != null && Files.exists(tokenFile)) { - Files.delete(tokenFile); - } - } - - private static HttpExecuteResponse mockStsResponse() { - String responseBody = "" + - "" + - "" + - "" + - "AKIAIOSFODNN7EXAMPLE" + - "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" + - "session-token" + - "2024-12-31T23:59:59Z" + - "" + - "" + - ""; - - return HttpExecuteResponse.builder() - .response(SdkHttpResponse.builder().statusCode(200).build()) - .responseBody(AbortableInputStream.create(new StringInputStream(responseBody))) - .build(); - } - - @ParameterizedTest - @MethodSource("webIdentityTokenCredentialProvidersWithBuilder") - void userAgentString_containsWebIdentityTokenBusinessMetric_WhenUsingWebIdentityTokenCredentialsWithBuilder( - IdentityProvider provider, String expected) throws Exception { - - try { - stsClient(provider, mockHttpClient).getCallerIdentity(); - - SdkHttpRequest lastRequest = mockHttpClient.getLastRequest(); - assertThat(lastRequest).isNotNull(); - - List userAgentHeaders = lastRequest.headers().get("User-Agent"); - assertThat(userAgentHeaders).isNotNull().hasSize(1); - assertThat(userAgentHeaders.get(0)).contains(expected); - } catch (Exception e) { - } - } - - private static Stream webIdentityTokenCredentialProvidersWithBuilder() throws IOException { - String existingTokenPath = Paths.get("../../services/sts/src/test/resources/token.jwt").toAbsolutePath().toString(); - - return Stream.of( - Arguments.of(WebIdentityTokenFileCredentialsProvider.builder() - .roleArn(ROLE_ARN) - .roleSessionName(ROLE_SESSION_NAME) - .webIdentityTokenFile(Paths.get(existingTokenPath)) - .build(), "m/D,h") - ); - } - - private static StsClient stsClient(IdentityProvider provider, SdkHttpClient httpClient) { - return StsClient.builder() - .credentialsProvider(provider) - .httpClient(httpClient) - .build(); - } -} diff --git a/test/auth-tests/src/it/java/software/amazon/awssdk/auth/sts/StsCredentialsProviderUserAgentTest.java b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/sts/StsCredentialsProviderUserAgentTest.java new file mode 100644 index 000000000000..dade9fcc57de --- /dev/null +++ b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/sts/StsCredentialsProviderUserAgentTest.java @@ -0,0 +1,312 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.awssdk.auth.sts; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; +import software.amazon.awssdk.http.AbortableInputStream; +import software.amazon.awssdk.http.HttpExecuteResponse; +import software.amazon.awssdk.http.SdkHttpRequest; +import software.amazon.awssdk.http.SdkHttpResponse; +import software.amazon.awssdk.services.sts.StsClient; +import software.amazon.awssdk.services.sts.auth.StsAssumeRoleCredentialsProvider; +import software.amazon.awssdk.services.sts.auth.StsAssumeRoleWithSamlCredentialsProvider; +import software.amazon.awssdk.services.sts.auth.StsAssumeRoleWithWebIdentityCredentialsProvider; +import software.amazon.awssdk.services.sts.auth.StsGetFederationTokenCredentialsProvider; +import software.amazon.awssdk.services.sts.auth.StsGetSessionTokenCredentialsProvider; +import software.amazon.awssdk.services.sts.auth.StsWebIdentityTokenFileCredentialsProvider; +import software.amazon.awssdk.core.SdkSystemSetting; +import software.amazon.awssdk.testutils.service.http.MockSyncHttpClient; +import software.amazon.awssdk.utils.StringInputStream; + +/** + * Tests STS credentials provider business metrics emission in User-Agent headers. + * + * Tests the following business metrics: + * - CREDENTIALS_STS_ASSUME_ROLE("i") - StsAssumeRoleCredentialsProvider + * - CREDENTIALS_STS_ASSUME_ROLE_SAML("j") - StsAssumeRoleWithSamlCredentialsProvider + * - CREDENTIALS_STS_ASSUME_ROLE_WEB_ID("k") - StsAssumeRoleWithWebIdentityCredentialsProvider + * - CREDENTIALS_STS_FEDERATION_TOKEN("l") - StsGetFederationTokenCredentialsProvider + * - CREDENTIALS_STS_SESSION_TOKEN("m") - StsGetSessionTokenCredentialsProvider + * - CREDENTIALS_ENV_VARS_STS_WEB_ID_TOKEN("h") - WebIdentityTokenFileCredentialsProvider + */ +class StsCredentialsProviderUserAgentTest { + + private MockSyncHttpClient mockHttpClient; + + @BeforeEach + public void setup() { + mockHttpClient = new MockSyncHttpClient(); + mockHttpClient.stubNextResponse(mockStsResponse()); + } + + @ParameterizedTest + @MethodSource("stsCredentialsProviders") + void stsCredentialsProvider_emitsCorrectBusinessMetrics(AwsCredentialsProvider provider, + String expected, + String providerName) throws Exception { + StsClient stsClient = StsClient.builder() + .credentialsProvider(provider) + .httpClient(mockHttpClient) + .build(); + + stsClient.getCallerIdentity(); + + SdkHttpRequest lastRequest = mockHttpClient.getLastRequest(); + assertThat(lastRequest).isNotNull(); + + List userAgentHeaders = lastRequest.headers().get("User-Agent"); + assertThat(userAgentHeaders).isNotNull().hasSize(1); + assertThat(userAgentHeaders.get(0)).contains(expected); + + stsClient.close(); + } + + private static Stream stsCredentialsProviders() throws Exception { + return Stream.of( + Arguments.of(createAssumeRoleProvider(), "m/D,i", "StsAssumeRoleCredentialsProvider"), + Arguments.of(createAssumeRoleWithSamlProvider(), "m/D,j", "StsAssumeRoleWithSamlCredentialsProvider"), + Arguments.of(createAssumeRoleWithWebIdentityProvider(), "m/D,k", "StsAssumeRoleWithWebIdentityCredentialsProvider"), + Arguments.of(createFederationTokenProvider(), "m/D,l", "StsGetFederationTokenCredentialsProvider"), + Arguments.of(createSessionTokenProvider(), "m/D,m", "StsGetSessionTokenCredentialsProvider"), + Arguments.of(createWebIdentityTokenFileProvider(), "m/D,k,h", "StsWebIdentityTokenFileCredentialsProvider") + ); + } + + private static AwsCredentialsProvider createAssumeRoleProvider() { + MockSyncHttpClient mockHttpClient = new MockSyncHttpClient(); + mockHttpClient.stubNextResponse(createStsResponse("AssumeRole")); + + StsClient stsClient = StsClient.builder() + .httpClient(mockHttpClient) + .region(software.amazon.awssdk.regions.Region.US_EAST_1) + .build(); + + return StsAssumeRoleCredentialsProvider.builder() + .stsClient(stsClient) + .refreshRequest(r -> r.roleArn("arn:aws:iam::123456789012:role/TestRole") + .roleSessionName("test-session")) + .build(); + } + + private static AwsCredentialsProvider createAssumeRoleWithSamlProvider() { + MockSyncHttpClient mockHttpClient = new MockSyncHttpClient(); + mockHttpClient.stubNextResponse(createStsResponse("AssumeRoleWithSAML")); + + StsClient stsClient = StsClient.builder() + .httpClient(mockHttpClient) + .region(software.amazon.awssdk.regions.Region.US_EAST_1) + .build(); + + String samlAssertion = "PHNhbWw6QXNzZXJ0aW9uPjwvc2FtbDpBc3NlcnRpb24+"; + + return StsAssumeRoleWithSamlCredentialsProvider.builder() + .stsClient(stsClient) + .refreshRequest(r -> r.roleArn("arn:aws:iam::123456789012:role/TestRole") + .principalArn("arn:aws:iam::123456789012:saml-provider/TestProvider") + .samlAssertion(samlAssertion)) + .build(); + } + + private static AwsCredentialsProvider createAssumeRoleWithWebIdentityProvider() { + MockSyncHttpClient mockHttpClient = new MockSyncHttpClient(); + mockHttpClient.stubNextResponse(createStsResponse("AssumeRoleWithWebIdentity")); + + StsClient stsClient = StsClient.builder() + .httpClient(mockHttpClient) + .region(software.amazon.awssdk.regions.Region.US_EAST_1) + .build(); + + String webIdentityToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"; + + return StsAssumeRoleWithWebIdentityCredentialsProvider.builder() + .stsClient(stsClient) + .refreshRequest(r -> r.roleArn("arn:aws:iam::123456789012:role/TestRole") + .webIdentityToken(webIdentityToken) + .roleSessionName("test-session")) + .build(); + } + + private static AwsCredentialsProvider createFederationTokenProvider() { + MockSyncHttpClient mockHttpClient = new MockSyncHttpClient(); + mockHttpClient.stubNextResponse(createStsResponse("GetFederationToken")); + + StsClient stsClient = StsClient.builder() + .httpClient(mockHttpClient) + .region(software.amazon.awssdk.regions.Region.US_EAST_1) + .build(); + + return StsGetFederationTokenCredentialsProvider.builder() + .stsClient(stsClient) + .refreshRequest(r -> r.name("test-user")) + .build(); + } + + private static AwsCredentialsProvider createSessionTokenProvider() { + MockSyncHttpClient mockHttpClient = new MockSyncHttpClient(); + mockHttpClient.stubNextResponse(createStsResponse("GetSessionToken")); + + StsClient stsClient = StsClient.builder() + .httpClient(mockHttpClient) + .region(software.amazon.awssdk.regions.Region.US_EAST_1) + .build(); + + return StsGetSessionTokenCredentialsProvider.builder() + .stsClient(stsClient) + .build(); + } + + private static AwsCredentialsProvider createWebIdentityTokenFileProvider() throws Exception { + // Create temporary token file + Path tempTokenFile = Files.createTempFile("test-token", ".jwt"); + Files.write(tempTokenFile, "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c".getBytes()); + + System.setProperty(SdkSystemSetting.AWS_ROLE_ARN.property(), "arn:aws:iam::123456789012:role/TestRole"); + System.setProperty(SdkSystemSetting.AWS_WEB_IDENTITY_TOKEN_FILE.property(), tempTokenFile.toString()); + System.setProperty(SdkSystemSetting.AWS_ROLE_SESSION_NAME.property(), "test-session"); + + MockSyncHttpClient mockHttpClient = new MockSyncHttpClient(); + mockHttpClient.stubNextResponse(createStsResponse("AssumeRoleWithWebIdentity")); + + StsClient stsClient = StsClient.builder() + .httpClient(mockHttpClient) + .region(software.amazon.awssdk.regions.Region.US_EAST_1) + .build(); + + return StsWebIdentityTokenFileCredentialsProvider.builder() + .stsClient(stsClient) + .build(); + } + + private static HttpExecuteResponse mockStsResponse() { + String getCallerIdentityResponseBody = "\n" + + " \n" + + " arn:aws:sts::123456789012:assumed-role/TestRole/test-session\n" + + " AROATEST:test-session\n" + + " 123456789012\n" + + " \n" + + ""; + + return HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder().statusCode(200).build()) + .responseBody(AbortableInputStream.create(new StringInputStream(getCallerIdentityResponseBody))) + .build(); + } + + private static HttpExecuteResponse createStsResponse(String operation) { + String responseBody; + + switch (operation) { + case "AssumeRole": + responseBody = "\n" + + " \n" + + " \n" + + " AKIATEST\n" + + " test-secret\n" + + " test-session-token\n" + + " 2025-09-24T00:00:00Z\n" + + " \n" + + " \n" + + " arn:aws:sts::123456789012:assumed-role/TestRole/test-session\n" + + " AROATEST:test-session\n" + + " \n" + + " \n" + + ""; + break; + + case "AssumeRoleWithSAML": + responseBody = "\n" + + " \n" + + " \n" + + " AKIATEST\n" + + " test-secret\n" + + " test-session-token\n" + + " 2025-09-24T00:00:00Z\n" + + " \n" + + " \n" + + " arn:aws:sts::123456789012:assumed-role/TestRole/test-session\n" + + " AROATEST:test-session\n" + + " \n" + + " \n" + + ""; + break; + + case "AssumeRoleWithWebIdentity": + responseBody = "\n" + + " \n" + + " \n" + + " AKIATEST\n" + + " test-secret\n" + + " test-session-token\n" + + " 2025-09-24T00:00:00Z\n" + + " \n" + + " \n" + + " arn:aws:sts::123456789012:assumed-role/TestRole/test-session\n" + + " AROATEST:test-session\n" + + " \n" + + " \n" + + ""; + break; + + case "GetFederationToken": + responseBody = "\n" + + " \n" + + " \n" + + " AKIATEST\n" + + " test-secret\n" + + " test-session-token\n" + + " 2025-09-24T00:00:00Z\n" + + " \n" + + " \n" + + " arn:aws:sts::123456789012:federated-user/test-user\n" + + " 123456789012:test-user\n" + + " \n" + + " \n" + + ""; + break; + + case "GetSessionToken": + responseBody = "\n" + + " \n" + + " \n" + + " AKIATEST\n" + + " test-secret\n" + + " test-session-token\n" + + " 2025-09-24T00:00:00Z\n" + + " \n" + + " \n" + + ""; + break; + + default: + throw new IllegalArgumentException("Unknown STS operation: " + operation); + } + + return HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder().statusCode(200).build()) + .responseBody(AbortableInputStream.create(new StringInputStream(responseBody))) + .build(); + } +}