diff --git a/.changes/next-release/feature-AWSSDKforJavav2-c2d8ec6.json b/.changes/next-release/feature-AWSSDKforJavav2-c2d8ec6.json new file mode 100644 index 000000000000..bb7048a73f7e --- /dev/null +++ b/.changes/next-release/feature-AWSSDKforJavav2-c2d8ec6.json @@ -0,0 +1,6 @@ +{ + "type": "feature", + "category": "AWS SDK for Java v2", + "contributor": "", + "description": "Adds business metrics tracking for credential providers." +} 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..93773cf1f9ff 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 @@ -25,8 +25,8 @@ * provider via the 'software.amazon.awssdk.services.sts.internal.StsProfileCredentialsProviderFactory', assuming STS is on the * classpath. */ -@FunctionalInterface @SdkProtectedApi +@FunctionalInterface public interface ChildProfileCredentialsProviderFactory { /** * Create a credentials provider for the provided profile, using the provided source credentials provider to authenticate @@ -41,4 +41,70 @@ public interface ChildProfileCredentialsProviderFactory { * @return The credentials provider with permissions derived from the source credentials provider and profile. */ AwsCredentialsProvider create(AwsCredentialsProvider sourceCredentialsProvider, Profile profile); + + /** + * 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. + */ + default AwsCredentialsProvider create(ChildProfileCredentialsRequest request) { + return create(request.sourceCredentialsProvider(), request.profile()); + } + + final class ChildProfileCredentialsRequest { + private final AwsCredentialsProvider sourceCredentialsProvider; + private final Profile profile; + private final String sourceChain; + + private ChildProfileCredentialsRequest(Builder builder) { + this.sourceCredentialsProvider = builder.sourceCredentialsProvider; + this.profile = builder.profile; + this.sourceChain = builder.sourceChain; + } + + public static Builder builder() { + return new Builder(); + } + + public AwsCredentialsProvider sourceCredentialsProvider() { + return sourceCredentialsProvider; + } + + public Profile profile() { + return profile; + } + + public String sourceChain() { + return sourceChain; + } + + public static final class Builder { + private AwsCredentialsProvider sourceCredentialsProvider; + private Profile profile; + private String sourceChain; + + public Builder sourceCredentialsProvider(AwsCredentialsProvider sourceCredentialsProvider) { + this.sourceCredentialsProvider = sourceCredentialsProvider; + return this; + } + + public Builder profile(Profile profile) { + this.profile = profile; + return this; + } + + public Builder sourceChain(String sourceChain) { + this.sourceChain = sourceChain; + return this; + } + + public ChildProfileCredentialsRequest build() { + return new ChildProfileCredentialsRequest(this); + } + } + } } 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 efec7ffce6bd..5fefed5e8d65 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 @@ -39,6 +39,7 @@ import software.amazon.awssdk.auth.credentials.internal.HttpCredentialsLoader.LoadedCredentials; import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.core.util.SdkUserAgent; import software.amazon.awssdk.regions.util.ResourcesEndpointProvider; import software.amazon.awssdk.regions.util.ResourcesEndpointRetryPolicy; @@ -72,7 +73,8 @@ public final class ContainerCredentialsProvider implements HttpCredentialsProvider, ToCopyableBuilder { - private static final String PROVIDER_NAME = "ContainerCredentialsProvider"; + private static final String CLASS_NAME = "ContainerCredentialsProvider"; + private static final String PROVIDER_NAME = BusinessMetricFeatureId.CREDENTIALS_HTTP.value(); private static final Predicate IS_LOOPBACK_ADDRESS = InetAddress::isLoopbackAddress; private static final Predicate ALLOWED_HOSTS_RULES = IS_LOOPBACK_ADDRESS; private static final String HTTPS = "https"; @@ -90,6 +92,8 @@ public final class ContainerCredentialsProvider private final Boolean asyncCredentialUpdateEnabled; private final String asyncThreadName; + private final String sourceChain; + private final String providerName; /** * @see #builder() @@ -98,7 +102,11 @@ private ContainerCredentialsProvider(BuilderImpl builder) { this.endpoint = builder.endpoint; this.asyncCredentialUpdateEnabled = builder.asyncCredentialUpdateEnabled; this.asyncThreadName = builder.asyncThreadName; - this.httpCredentialsLoader = HttpCredentialsLoader.create(PROVIDER_NAME); + this.sourceChain = builder.sourceChain; + this.providerName = StringUtils.isEmpty(builder.sourceChain) + ? PROVIDER_NAME + : builder.sourceChain + "," + PROVIDER_NAME; + this.httpCredentialsLoader = HttpCredentialsLoader.create(this.providerName); if (Boolean.TRUE.equals(builder.asyncCredentialUpdateEnabled)) { Validate.paramNotBlank(builder.asyncThreadName, "asyncThreadName"); @@ -126,7 +134,7 @@ public static Builder builder() { @Override public String toString() { - return ToString.create(PROVIDER_NAME); + return ToString.create(CLASS_NAME); } private RefreshResult refreshCredentials() { @@ -318,6 +326,7 @@ private static final class BuilderImpl implements Builder { private String endpoint; private Boolean asyncCredentialUpdateEnabled; private String asyncThreadName; + private String sourceChain; private BuilderImpl() { asyncThreadName("container-credentials-provider"); @@ -327,6 +336,7 @@ private BuilderImpl(ContainerCredentialsProvider credentialsProvider) { this.endpoint = credentialsProvider.endpoint; this.asyncCredentialUpdateEnabled = credentialsProvider.asyncCredentialUpdateEnabled; this.asyncThreadName = credentialsProvider.asyncThreadName; + this.sourceChain = credentialsProvider.sourceChain; } @Override @@ -359,6 +369,21 @@ public void setAsyncThreadName(String asyncThreadName) { asyncThreadName(asyncThreadName); } + /** + * An optional string denoting previous credentials providers that are chained with this one. + *

Note: This method is primarily intended for use by AWS SDK internal components + * and should not be used directly by external users.

+ */ + @Override + public Builder sourceChain(String sourceChain) { + this.sourceChain = sourceChain; + return this; + } + + public void setSourceChain(String sourceChain) { + sourceChain(sourceChain); + } + @Override public ContainerCredentialsProvider build() { return new ContainerCredentialsProvider(this); diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/EnvironmentVariableCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/EnvironmentVariableCredentialsProvider.java index e05c24eed05a..f7eb0df32e6b 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/EnvironmentVariableCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/EnvironmentVariableCredentialsProvider.java @@ -18,6 +18,7 @@ import java.util.Optional; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.auth.credentials.internal.SystemSettingsCredentialsProvider; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.utils.SystemSetting; import software.amazon.awssdk.utils.ToString; @@ -28,7 +29,8 @@ @SdkPublicApi public final class EnvironmentVariableCredentialsProvider extends SystemSettingsCredentialsProvider { - private static final String PROVIDER_NAME = "EnvironmentVariableCredentialsProvider"; + private static final String CLASS_NAME = "EnvironmentVariableCredentialsProvider"; + private static final String PROVIDER_NAME = BusinessMetricFeatureId.CREDENTIALS_ENV_VARS.value(); private EnvironmentVariableCredentialsProvider() { } @@ -52,6 +54,6 @@ protected String provider() { @Override public String toString() { - return ToString.create(PROVIDER_NAME); + return ToString.create(CLASS_NAME); } } 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 ccc7e7aa7101..29239b34908d 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 @@ -48,6 +48,15 @@ interface BuilderNote: This method is primarily intended for use by AWS SDK internal components + * and should not be used directly by external users.

+ */ + default BuilderT sourceChain(String sourceChain) { + throw new UnsupportedOperationException(); + } + /** * Build the credentials provider based on the configuration on this builder. */ diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProvider.java index b1ddc5d7faef..70210097c66f 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProvider.java @@ -37,6 +37,7 @@ import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.core.exception.SdkClientException; import software.amazon.awssdk.core.exception.SdkServiceException; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.profiles.ProfileFile; import software.amazon.awssdk.profiles.ProfileFileSupplier; import software.amazon.awssdk.profiles.ProfileFileSystemSetting; @@ -44,6 +45,7 @@ import software.amazon.awssdk.regions.util.HttpResourcesUtils; import software.amazon.awssdk.regions.util.ResourcesEndpointProvider; import software.amazon.awssdk.utils.Logger; +import software.amazon.awssdk.utils.StringUtils; import software.amazon.awssdk.utils.ToString; import software.amazon.awssdk.utils.Validate; import software.amazon.awssdk.utils.builder.CopyableBuilder; @@ -67,7 +69,8 @@ public final class InstanceProfileCredentialsProvider implements HttpCredentialsProvider, ToCopyableBuilder { private static final Logger log = Logger.loggerFor(InstanceProfileCredentialsProvider.class); - private static final String PROVIDER_NAME = "InstanceProfileCredentialsProvider"; + private static final String CLASS_NAME = "InstanceProfileCredentialsProvider"; + private static final String PROVIDER_NAME = BusinessMetricFeatureId.CREDENTIALS_IMDS.value(); private static final String EC2_METADATA_TOKEN_HEADER = "x-aws-ec2-metadata-token"; private static final String SECURITY_CREDENTIALS_RESOURCE = "/latest/meta-data/iam/security-credentials/"; private static final String TOKEN_RESOURCE = "/latest/api/token"; @@ -90,6 +93,9 @@ public final class InstanceProfileCredentialsProvider private final Duration staleTime; + private final String sourceChain; + private final String providerName; + /** * @see #builder() */ @@ -102,8 +108,12 @@ private InstanceProfileCredentialsProvider(BuilderImpl builder) { .orElseGet(() -> ProfileFileSupplier.fixedProfileFile(ProfileFile.defaultProfileFile())); this.profileName = Optional.ofNullable(builder.profileName) .orElseGet(ProfileFileSystemSetting.AWS_PROFILE::getStringValueOrThrow); + this.sourceChain = builder.sourceChain; + this.providerName = StringUtils.isEmpty(builder.sourceChain) + ? PROVIDER_NAME + : builder.sourceChain + "," + PROVIDER_NAME; - this.httpCredentialsLoader = HttpCredentialsLoader.create(PROVIDER_NAME); + this.httpCredentialsLoader = HttpCredentialsLoader.create(this.providerName); this.configProvider = Ec2MetadataConfigProvider.builder() .profileFile(profileFile) @@ -204,7 +214,7 @@ public void close() { @Override public String toString() { - return ToString.create(PROVIDER_NAME); + return ToString.create(CLASS_NAME); } private ResourcesEndpointProvider createEndpointProvider() { @@ -372,6 +382,7 @@ static final class BuilderImpl implements Builder { private Supplier profileFile; private String profileName; private Duration staleTime; + private String sourceChain; private BuilderImpl() { asyncThreadName("instance-profile-credentials-provider"); @@ -385,6 +396,7 @@ private BuilderImpl(InstanceProfileCredentialsProvider provider) { this.profileFile = provider.profileFile; this.profileName = provider.profileName; this.staleTime = provider.staleTime; + this.sourceChain = provider.sourceChain; } Builder clock(Clock clock) { @@ -463,6 +475,21 @@ public void setStaleTime(Duration duration) { staleTime(duration); } + /** + * An optional string denoting previous credentials providers that are chained with this one. + *

Note: This method is primarily intended for use by AWS SDK internal components + * and should not be used directly by external users.

+ */ + @Override + public Builder sourceChain(String sourceChain) { + this.sourceChain = sourceChain; + return this; + } + + public void setSourceChain(String sourceChain) { + sourceChain(sourceChain); + } + @Override public InstanceProfileCredentialsProvider build() { return new InstanceProfileCredentialsProvider(this); diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProcessCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProcessCredentialsProvider.java index e27d511d0887..08d9cf373ab2 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProcessCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProcessCredentialsProvider.java @@ -25,12 +25,14 @@ import java.util.Collections; import java.util.List; import software.amazon.awssdk.annotations.SdkPublicApi; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.protocols.jsoncore.JsonNode; import software.amazon.awssdk.protocols.jsoncore.JsonNodeParser; import software.amazon.awssdk.utils.DateUtils; import software.amazon.awssdk.utils.IoUtils; import software.amazon.awssdk.utils.Platform; import software.amazon.awssdk.utils.SdkAutoCloseable; +import software.amazon.awssdk.utils.StringUtils; import software.amazon.awssdk.utils.ToString; import software.amazon.awssdk.utils.Validate; import software.amazon.awssdk.utils.builder.CopyableBuilder; @@ -64,7 +66,8 @@ public final class ProcessCredentialsProvider implements AwsCredentialsProvider, SdkAutoCloseable, ToCopyableBuilder { - private static final String PROVIDER_NAME = "ProcessCredentialsProvider"; + private static final String CLASS_NAME = "ProcessCredentialsProvider"; + private static final String PROVIDER_NAME = BusinessMetricFeatureId.CREDENTIALS_PROCESS.value(); private static final JsonNodeParser PARSER = JsonNodeParser.builder() .removeErrorLocations(true) .build(); @@ -82,6 +85,9 @@ public final class ProcessCredentialsProvider private final Boolean asyncCredentialUpdateEnabled; + private final String sourceChain; + private final String providerName; + /** * @see #builder() */ @@ -93,6 +99,10 @@ private ProcessCredentialsProvider(Builder builder) { this.commandAsListOfStringsFromBuilder = builder.commandAsListOfStrings; this.asyncCredentialUpdateEnabled = builder.asyncCredentialUpdateEnabled; this.staticAccountId = builder.staticAccountId; + this.sourceChain = builder.sourceChain; + this.providerName = StringUtils.isEmpty(builder.sourceChain) + ? PROVIDER_NAME + : builder.sourceChain + "," + PROVIDER_NAME; CachedSupplier.Builder cacheBuilder = CachedSupplier.builder(this::refreshCredentials) .cachedValueName(toString()); @@ -192,13 +202,13 @@ private AwsCredentials credentials(JsonNode credentialsJson) { .sessionToken(sessionToken) .expirationTime(credentialExpirationTime(credentialsJson)) .accountId(resolvedAccountId) - .providerName(PROVIDER_NAME) + .providerName(this.providerName) .build() : AwsBasicCredentials.builder() .accessKeyId(accessKeyId) .secretAccessKey(secretAccessKey) .accountId(resolvedAccountId) - .providerName(PROVIDER_NAME) + .providerName(this.providerName) .build(); } @@ -270,6 +280,7 @@ public static class Builder implements CopyableBuilderNote: This method is primarily intended for use by AWS SDK internal components + * and should not be used directly by external users.

+ */ + public Builder sourceChain(String sourceChain) { + this.sourceChain = sourceChain; + return this; + } + public ProcessCredentialsProvider build() { return new ProcessCredentialsProvider(this); } @@ -364,7 +386,7 @@ public ProcessCredentialsProvider build() { @Override public String toString() { - return ToString.builder(PROVIDER_NAME) + return ToString.builder(CLASS_NAME) .add("cmd", executableCommand) .build(); } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileProviderCredentialsContext.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileProviderCredentialsContext.java index 065f8b61da7c..c0a04ba8737c 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileProviderCredentialsContext.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/ProfileProviderCredentialsContext.java @@ -18,6 +18,7 @@ import java.util.Objects; import software.amazon.awssdk.annotations.SdkProtectedApi; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.profiles.Profile; import software.amazon.awssdk.profiles.ProfileFile; @@ -29,10 +30,12 @@ public final class ProfileProviderCredentialsContext { private final Profile profile; private final ProfileFile profileFile; + private final String sourceChain; - private ProfileProviderCredentialsContext(Profile profile, ProfileFile profileFile) { - this.profile = profile; - this.profileFile = profileFile; + private ProfileProviderCredentialsContext(Builder builder) { + this.profile = builder.profile; + this.profileFile = builder.profileFile; + this.sourceChain = builder.sourceChain; } public static Builder builder() { @@ -55,6 +58,14 @@ public ProfileFile profileFile() { return profileFile; } + /** + * An optional string list of {@link software.amazon.awssdk.core.useragent.BusinessMetricFeatureId} denoting previous + * credentials providers that are chained with this one. + */ + public String sourceChain() { + return sourceChain; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -78,6 +89,7 @@ public int hashCode() { public static final class Builder { private Profile profile; private ProfileFile profileFile; + private String sourceChain; private Builder() { } @@ -103,8 +115,22 @@ public Builder profileFile(ProfileFile profileFile) { return this; } + /** + * Builder interface to set source. + * @param sourceChain An optional string list of {@link BusinessMetricFeatureId} denoting previous credentials + * providers that are chained with this one. This method is primarily + * intended for use by AWS SDK internal components + * and should not be used directly by external users. + * + * @return Returns a reference to this object so that method calls can be chained together. + */ + public Builder sourceChain(String sourceChain) { + this.sourceChain = sourceChain; + return this; + } + public ProfileProviderCredentialsContext build() { - return new ProfileProviderCredentialsContext(profile, profileFile); + return new ProfileProviderCredentialsContext(this); } } } 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 7e340f634969..429da4672f75 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 @@ -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.ToString; import software.amazon.awssdk.utils.Validate; @@ -24,7 +25,7 @@ */ @SdkPublicApi public final class StaticCredentialsProvider implements AwsCredentialsProvider { - private static final String PROVIDER_NAME = "StaticCredentialsProvider"; + private static final String PROVIDER_NAME = BusinessMetricFeatureId.CREDENTIALS_CODE.value(); private final AwsCredentials credentials; private StaticCredentialsProvider(AwsCredentials credentials) { @@ -34,10 +35,18 @@ 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()) { + 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()) { + 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/SystemPropertyCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/SystemPropertyCredentialsProvider.java index bcc7d77af4e6..94ff10a0b1c5 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/SystemPropertyCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/SystemPropertyCredentialsProvider.java @@ -18,6 +18,7 @@ import java.util.Optional; import software.amazon.awssdk.annotations.SdkPublicApi; import software.amazon.awssdk.auth.credentials.internal.SystemSettingsCredentialsProvider; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.utils.SystemSetting; import software.amazon.awssdk.utils.ToString; @@ -28,7 +29,8 @@ @SdkPublicApi public final class SystemPropertyCredentialsProvider extends SystemSettingsCredentialsProvider { - private static final String PROVIDER_NAME = "SystemPropertyCredentialsProvider"; + private static final String CLASS_NAME = "SystemPropertyCredentialsProvider"; + private static final String PROVIDER_NAME = BusinessMetricFeatureId.CREDENTIALS_JVM_SYSTEM_PROPERTIES.value(); private SystemPropertyCredentialsProvider() { } @@ -52,6 +54,6 @@ protected String provider() { @Override public String toString() { - return ToString.create(PROVIDER_NAME); + return ToString.create(CLASS_NAME); } } diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/WebIdentityTokenFileCredentialsProvider.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/WebIdentityTokenFileCredentialsProvider.java index 6e5f68473809..a9eaf082b145 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/WebIdentityTokenFileCredentialsProvider.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/WebIdentityTokenFileCredentialsProvider.java @@ -24,6 +24,7 @@ import software.amazon.awssdk.auth.credentials.internal.WebIdentityCredentialsUtils; 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.utils.IoUtils; import software.amazon.awssdk.utils.SdkAutoCloseable; import software.amazon.awssdk.utils.ToString; @@ -108,6 +109,8 @@ private WebIdentityTokenFileCredentialsProvider(BuilderImpl builder) { .prefetchTime(prefetchTime) .staleTime(staleTime) .roleSessionDuration(roleSessionDuration) + .sourceChain(BusinessMetricFeatureId + .CREDENTIALS_ENV_VARS_STS_WEB_ID_TOKEN.value()) .build(); credentialsProvider = WebIdentityCredentialsUtils.factory().create(credentialProperties); 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..1b5e67118a27 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; @@ -108,8 +109,15 @@ public Optional credentialsProvider() { * @param children The child profiles that source credentials from this profile. */ private Optional credentialsProvider(Set children) { + return credentialsProviderWithFeatureID(children).map(CredentialsWithFeatureId::provider); + } + + /** + * Internal method that returns both the credentials provider and its feature ID. + */ + private Optional credentialsProviderWithFeatureID(Set children) { if (properties.containsKey(ProfileProperty.ROLE_ARN) && properties.containsKey(ProfileProperty.WEB_IDENTITY_TOKEN_FILE)) { - return Optional.ofNullable(roleAndWebIdentityTokenProfileCredentialsProvider()); + return Optional.of(roleAndWebIdentityTokenProfileCredentialsProvider()); } if (properties.containsKey(ProfileProperty.SSO_ROLE_NAME) @@ -117,7 +125,7 @@ private Optional credentialsProvider(Set childre || properties.containsKey(ProfileProperty.SSO_REGION) || properties.containsKey(ProfileProperty.SSO_START_URL) || properties.containsKey(ProfileSection.SSO_SESSION.getPropertyKeyName())) { - return Optional.ofNullable(ssoProfileCredentialsProvider()); + return Optional.of(ssoProfileCredentialsProvider()); } if (properties.containsKey(ProfileProperty.ROLE_ARN)) { @@ -128,16 +136,16 @@ private Optional credentialsProvider(Set childre ProfileProperty.SOURCE_PROFILE, ProfileProperty.CREDENTIAL_SOURCE); if (hasSourceProfile) { - return Optional.ofNullable(roleAndSourceProfileBasedProfileCredentialsProvider(children)); + return Optional.of(roleAndSourceProfileBasedProfileCredentialsProvider(children)); } if (hasCredentialSource) { - return Optional.ofNullable(roleAndCredentialSourceBasedProfileCredentialsProvider()); + return Optional.of(roleAndCredentialSourceBasedProfileCredentialsProvider()); } } if (properties.containsKey(ProfileProperty.CREDENTIAL_PROCESS)) { - return Optional.ofNullable(credentialProcessCredentialsProvider()); + return Optional.of(credentialProcessCredentialsProvider()); } if (properties.containsKey(ProfileProperty.AWS_SESSION_TOKEN)) { @@ -154,52 +162,72 @@ private Optional credentialsProvider(Set childre /** * Load a basic set of credentials that have been configured in this profile. */ - private AwsCredentialsProvider basicProfileCredentialsProvider() { + private CredentialsWithFeatureId basicProfileCredentialsProvider() { requireProperties(ProfileProperty.AWS_ACCESS_KEY_ID, ProfileProperty.AWS_SECRET_ACCESS_KEY); + + String featureId = BusinessMetricFeatureId.CREDENTIALS_PROFILE.value(); AwsCredentials credentials = AwsBasicCredentials.builder() .accessKeyId(properties.get(ProfileProperty.AWS_ACCESS_KEY_ID)) .secretAccessKey(properties.get(ProfileProperty.AWS_SECRET_ACCESS_KEY)) .accountId(properties.get(ProfileProperty.AWS_ACCOUNT_ID)) + .providerName(featureId) .build(); - return StaticCredentialsProvider.create(credentials); + + return new CredentialsWithFeatureId(StaticCredentialsProvider.create(credentials), featureId); } /** * Load a set of session credentials that have been configured in this profile. */ - private AwsCredentialsProvider sessionProfileCredentialsProvider() { + private CredentialsWithFeatureId sessionProfileCredentialsProvider() { requireProperties(ProfileProperty.AWS_ACCESS_KEY_ID, ProfileProperty.AWS_SECRET_ACCESS_KEY, ProfileProperty.AWS_SESSION_TOKEN); + + String featureId = BusinessMetricFeatureId.CREDENTIALS_PROFILE.value(); AwsCredentials credentials = AwsSessionCredentials.builder() .accessKeyId(properties.get(ProfileProperty.AWS_ACCESS_KEY_ID)) .secretAccessKey(properties.get(ProfileProperty.AWS_SECRET_ACCESS_KEY)) .sessionToken(properties.get(ProfileProperty.AWS_SESSION_TOKEN)) .accountId(properties.get(ProfileProperty.AWS_ACCOUNT_ID)) + .providerName(featureId) .build(); - return StaticCredentialsProvider.create(credentials); + + return new CredentialsWithFeatureId(StaticCredentialsProvider.create(credentials), featureId); } - private AwsCredentialsProvider credentialProcessCredentialsProvider() { + private CredentialsWithFeatureId credentialProcessCredentialsProvider() { requireProperties(ProfileProperty.CREDENTIAL_PROCESS); - return ProcessCredentialsProvider.builder() + String featureId = BusinessMetricFeatureId.CREDENTIALS_PROFILE_PROCESS.value(); + AwsCredentialsProvider provider = ProcessCredentialsProvider.builder() .command(properties.get(ProfileProperty.CREDENTIAL_PROCESS)) .staticAccountId(properties.get(ProfileProperty.AWS_ACCOUNT_ID)) + .sourceChain(featureId) .build(); + + return new CredentialsWithFeatureId(provider, featureId); } /** * Create the SSO credentials provider based on the related profile properties. */ - private AwsCredentialsProvider ssoProfileCredentialsProvider() { + private CredentialsWithFeatureId ssoProfileCredentialsProvider() { validateRequiredPropertiesForSsoCredentialsProvider(); - return ssoCredentialsProviderFactory().create( + boolean isLegacy = isLegacySsoConfiguration(); + String featureId = isLegacy ? + BusinessMetricFeatureId.CREDENTIALS_PROFILE_SSO_LEGACY.value() : + BusinessMetricFeatureId.CREDENTIALS_PROFILE_SSO.value(); + + AwsCredentialsProvider provider = ssoCredentialsProviderFactory().create( ProfileProviderCredentialsContext.builder() .profile(profile) .profileFile(profileFile) + .sourceChain(featureId) .build()); + + return new CredentialsWithFeatureId(provider, featureId); } private void validateRequiredPropertiesForSsoCredentialsProvider() { @@ -211,9 +239,14 @@ private void validateRequiredPropertiesForSsoCredentialsProvider() { } } - private AwsCredentialsProvider roleAndWebIdentityTokenProfileCredentialsProvider() { + private boolean isLegacySsoConfiguration() { + return !properties.containsKey(ProfileSection.SSO_SESSION.getPropertyKeyName()); + } + + private CredentialsWithFeatureId roleAndWebIdentityTokenProfileCredentialsProvider() { requireProperties(ProfileProperty.ROLE_ARN, ProfileProperty.WEB_IDENTITY_TOKEN_FILE); + String featureId = BusinessMetricFeatureId.CREDENTIALS_PROFILE_STS_WEB_ID_TOKEN.value(); String roleArn = properties.get(ProfileProperty.ROLE_ARN); String roleSessionName = properties.get(ProfileProperty.ROLE_SESSION_NAME); Path webIdentityTokenFile = Paths.get(properties.get(ProfileProperty.WEB_IDENTITY_TOKEN_FILE)); @@ -223,9 +256,10 @@ private AwsCredentialsProvider roleAndWebIdentityTokenProfileCredentialsProvider .roleArn(roleArn) .roleSessionName(roleSessionName) .webIdentityTokenFile(webIdentityTokenFile) + .sourceChain(featureId) .build(); - return WebIdentityCredentialsUtils.factory().create(credentialProperties); + return new CredentialsWithFeatureId(WebIdentityCredentialsUtils.factory().create(credentialProperties), featureId); } /** @@ -234,50 +268,84 @@ private AwsCredentialsProvider roleAndWebIdentityTokenProfileCredentialsProvider * * @param children The child profiles that source credentials from this profile. */ - private AwsCredentialsProvider roleAndSourceProfileBasedProfileCredentialsProvider(Set children) { + private CredentialsWithFeatureId roleAndSourceProfileBasedProfileCredentialsProvider(Set children) { requireProperties(ProfileProperty.SOURCE_PROFILE); Validate.validState(!children.contains(name), "Invalid profile file: Circular relationship detected with profiles %s.", children); Validate.validState(credentialsSourceResolver != null, - "The profile '%s' must be configured with a source profile in order to use assumed roles.", name); + "The profile '%s' must be configured with a source profile in order to use assumed roles.", + name); children.add(name); - AwsCredentialsProvider sourceCredentialsProvider = - credentialsSourceResolver.apply(properties.get(ProfileProperty.SOURCE_PROFILE)) - .flatMap(p -> new ProfileCredentialsUtils(profileFile, p, credentialsSourceResolver) - .credentialsProvider(children)) - .orElseThrow(this::noSourceCredentialsException); + Optional sourceResult = credentialsSourceResolver + .apply(properties.get(ProfileProperty.SOURCE_PROFILE)) + .flatMap(p -> new ProfileCredentialsUtils(profileFile, p, credentialsSourceResolver) + .credentialsProviderWithFeatureID(children)); + + if (!sourceResult.isPresent()) { + throw noSourceCredentialsException(); + } + + CredentialsWithFeatureId source = sourceResult.get(); + String profileMetric = BusinessMetricFeatureId.CREDENTIALS_PROFILE_SOURCE_PROFILE.value(); + String combinedMetrics = profileMetric + "," + source.featureId(); - return stsCredentialsProviderFactory().create(sourceCredentialsProvider, profile); + AwsCredentialsProvider stsProvider = createStsCredentialsProviderWithFeatureID(source.provider(), combinedMetrics); + return new CredentialsWithFeatureId(stsProvider, combinedMetrics); } /** * Load an assumed-role credentials provider that has been configured in this profile. This will attempt to locate the STS * module in order to generate the credentials provider. If it's not available, an illegal state exception will be raised. */ - private AwsCredentialsProvider roleAndCredentialSourceBasedProfileCredentialsProvider() { + private CredentialsWithFeatureId roleAndCredentialSourceBasedProfileCredentialsProvider() { requireProperties(ProfileProperty.CREDENTIAL_SOURCE); CredentialSourceType credentialSource = CredentialSourceType.parse(properties.get(ProfileProperty.CREDENTIAL_SOURCE)); - AwsCredentialsProvider credentialsProvider = credentialSourceCredentialProvider(credentialSource); - return stsCredentialsProviderFactory().create(credentialsProvider, profile); + String profileMetric = BusinessMetricFeatureId.CREDENTIALS_PROFILE_NAMED_PROVIDER.value(); + CredentialsWithFeatureId sourceResult = credentialSourceCredentialProvider(credentialSource); + + String combinedMetrics = profileMetric + "," + sourceResult.featureId(); + AwsCredentialsProvider stsProvider = createStsCredentialsProviderWithFeatureID(sourceResult.provider(), combinedMetrics); + return new CredentialsWithFeatureId(stsProvider, combinedMetrics); } - private AwsCredentialsProvider credentialSourceCredentialProvider(CredentialSourceType credentialSource) { + /** + * Helper method to create STS credentials provider with business metrics. + */ + private AwsCredentialsProvider createStsCredentialsProviderWithFeatureID(AwsCredentialsProvider sourceProvider, + String combinedMetrics) { + ChildProfileCredentialsProviderFactory.ChildProfileCredentialsRequest request = + ChildProfileCredentialsProviderFactory.ChildProfileCredentialsRequest.builder() + .sourceCredentialsProvider(sourceProvider) + .profile(profile) + .sourceChain(combinedMetrics) + .build(); + + return stsCredentialsProviderFactory().create(request); + } + + private CredentialsWithFeatureId credentialSourceCredentialProvider(CredentialSourceType credentialSource) { switch (credentialSource) { case ECS_CONTAINER: - return ContainerCredentialsProvider.builder().build(); + return new CredentialsWithFeatureId( + ContainerCredentialsProvider.builder().build(), + BusinessMetricFeatureId.CREDENTIALS_HTTP.value()); case EC2_INSTANCE_METADATA: - return InstanceProfileCredentialsProvider.builder() - .profileFile(profileFile) - .profileName(name) - .build(); + return new CredentialsWithFeatureId( + InstanceProfileCredentialsProvider.builder() + .profileFile(profileFile) + .profileName(name) + .build(), + BusinessMetricFeatureId.CREDENTIALS_IMDS.value()); case ENVIRONMENT: - return AwsCredentialsProviderChain.builder() - .addCredentialsProvider(SystemPropertyCredentialsProvider.create()) - .addCredentialsProvider(EnvironmentVariableCredentialsProvider.create()) - .build(); + return new CredentialsWithFeatureId( + AwsCredentialsProviderChain.builder() + .addCredentialsProvider(SystemPropertyCredentialsProvider.create()) + .addCredentialsProvider(EnvironmentVariableCredentialsProvider.create()) + .build(), + BusinessMetricFeatureId.CREDENTIALS_ENV_VARS.value()); default: throw noSourceCredentialsException(); } @@ -298,6 +366,27 @@ private IllegalStateException noSourceCredentialsException() { return new IllegalStateException(error); } + /** + * Simple data class to hold both a credentials provider and its feature ID. + */ + private static final class CredentialsWithFeatureId { + private final AwsCredentialsProvider provider; + private final String featureId; + + CredentialsWithFeatureId(AwsCredentialsProvider provider, String featureId) { + this.provider = provider; + this.featureId = featureId; + } + + AwsCredentialsProvider provider() { + return provider; + } + + String featureId() { + return featureId; + } + } + /** * Load the factory that can be used to create the STS credentials provider, assuming it is on the classpath. */ diff --git a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/internal/WebIdentityTokenCredentialProperties.java b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/internal/WebIdentityTokenCredentialProperties.java index 91391909b7a8..08637d33947a 100644 --- a/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/internal/WebIdentityTokenCredentialProperties.java +++ b/core/auth/src/main/java/software/amazon/awssdk/auth/credentials/internal/WebIdentityTokenCredentialProperties.java @@ -32,6 +32,7 @@ public class WebIdentityTokenCredentialProperties { private final Duration prefetchTime; private final Duration staleTime; private final Duration roleSessionDuration; + private final String sourceChain; private WebIdentityTokenCredentialProperties(Builder builder) { this.roleArn = builder.roleArn; @@ -41,6 +42,7 @@ private WebIdentityTokenCredentialProperties(Builder builder) { this.prefetchTime = builder.prefetchTime; this.staleTime = builder.staleTime; this.roleSessionDuration = builder.roleSessionDuration; + this.sourceChain = builder.sourceChain; } public String roleArn() { @@ -71,6 +73,10 @@ public Duration roleSessionDuration() { return this.roleSessionDuration; } + public String sourceChain() { + return sourceChain; + } + public static Builder builder() { return new Builder(); } @@ -83,6 +89,7 @@ public static final class Builder { private Duration prefetchTime; private Duration staleTime; private Duration roleSessionDuration; + private String sourceChain; public Builder roleArn(String roleArn) { this.roleArn = roleArn; @@ -119,6 +126,11 @@ public Builder roleSessionDuration(Duration roleSessionDuration) { return this; } + public Builder sourceChain(String sourceChain) { + this.sourceChain = sourceChain; + return this; + } + public WebIdentityTokenCredentialProperties build() { return new WebIdentityTokenCredentialProperties(this); } diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProviderTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProviderTest.java index 0f20fe51a5a6..568c82724f3b 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProviderTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ContainerCredentialsProviderTest.java @@ -31,6 +31,7 @@ import org.junit.ClassRule; import org.junit.Test; import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.core.util.SdkUserAgent; import software.amazon.awssdk.testutils.EnvironmentVariableHelper; @@ -73,8 +74,13 @@ public void testEnvVariableNotSet() { .resolveCredentials(); } + @Test + public void testClassName() { + assertThat(credentialsProvider.toString()).contains("ContainerCredentialsProvider"); + } + /** - * Tests that the getCredentials returns a value when it receives a valid 200 response from endpoint. + * Tests that the getCredentials returns a valid response from endpoint. */ @Test public void testGetCredentialsReturnsValidResponseFromEcsEndpoint() { @@ -86,7 +92,7 @@ public void testGetCredentialsReturnsValidResponseFromEcsEndpoint() { assertThat(credentials.accessKeyId()).isEqualTo(ACCESS_KEY_ID); assertThat(credentials.secretAccessKey()).isEqualTo(SECRET_ACCESS_KEY); assertThat(credentials.sessionToken()).isEqualTo(TOKEN); - assertThat(credentials.providerName()).isPresent().contains("ContainerCredentialsProvider"); + assertThat(credentials.providerName()).isPresent().contains(BusinessMetricFeatureId.CREDENTIALS_HTTP.value()); } /** diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProviderTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProviderTest.java index 671e591b17b5..055967055c25 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProviderTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/InstanceProfileCredentialsProviderTest.java @@ -63,6 +63,7 @@ import org.mockito.Mockito; import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.core.util.SdkUserAgent; import software.amazon.awssdk.identity.spi.AwsCredentialsIdentity; import software.amazon.awssdk.profiles.ProfileFile; @@ -143,6 +144,12 @@ private void verifyImdsCallInsecure() { .withHeader(USER_AGENT_HEADER, equalTo(USER_AGENT))); } + @Test + void testClassName() { + InstanceProfileCredentialsProvider provider = InstanceProfileCredentialsProvider.builder().build(); + assertThat(provider.toString()).contains("InstanceProfileCredentialsProvider"); + } + @Test void resolveCredentials_usesTokenByDefault() { stubSecureCredentialsResponse(aResponse().withBody(STUB_CREDENTIALS)); @@ -150,7 +157,7 @@ void resolveCredentials_usesTokenByDefault() { AwsCredentials credentials = provider.resolveCredentials(); assertThat(credentials.accessKeyId()).isEqualTo("ACCESS_KEY_ID"); assertThat(credentials.secretAccessKey()).isEqualTo("SECRET_ACCESS_KEY"); - assertThat(credentials.providerName()).isPresent().contains("InstanceProfileCredentialsProvider"); + assertThat(credentials.providerName()).isPresent().contains(BusinessMetricFeatureId.CREDENTIALS_IMDS.value()); verifyImdsCallWithToken(); } @@ -162,7 +169,7 @@ void resolveCredentials_WhenConnectionDelaySetToHighValue() { AwsCredentials credentials = provider.resolveCredentials(); assertThat(credentials.accessKeyId()).isEqualTo("ACCESS_KEY_ID"); assertThat(credentials.secretAccessKey()).isEqualTo("SECRET_ACCESS_KEY"); - assertThat(credentials.providerName()).isPresent().contains("InstanceProfileCredentialsProvider"); + assertThat(credentials.providerName()).isPresent().contains(BusinessMetricFeatureId.CREDENTIALS_IMDS.value()); verifyImdsCallWithToken(); } @@ -186,7 +193,7 @@ void resolveIdentity_WhenConnectionDelaySetToHighValue() { AwsCredentialsIdentity credentialsIdentity = provider.resolveIdentity().join(); assertThat(credentialsIdentity.accessKeyId()).isEqualTo("ACCESS_KEY_ID"); assertThat(credentialsIdentity.secretAccessKey()).isEqualTo("SECRET_ACCESS_KEY"); - assertThat(credentialsIdentity.providerName()).isPresent().contains("InstanceProfileCredentialsProvider"); + assertThat(credentialsIdentity.providerName()).isPresent().contains(BusinessMetricFeatureId.CREDENTIALS_IMDS.value()); verifyImdsCallWithToken(); } @@ -649,7 +656,7 @@ void shouldNotRetry_whenSucceeds() { AwsCredentials credentials = provider.resolveCredentials(); assertThat(credentials.accessKeyId()).isEqualTo("ACCESS_KEY_ID"); assertThat(credentials.secretAccessKey()).isEqualTo("SECRET_ACCESS_KEY"); - assertThat(credentials.providerName()).isPresent().contains("InstanceProfileCredentialsProvider"); + assertThat(credentials.providerName()).isPresent().contains(BusinessMetricFeatureId.CREDENTIALS_IMDS.value()); verifyImdsCallWithToken(); WireMock.verify(exactly(1), getRequestedFor(urlPathEqualTo(CREDENTIALS_RESOURCE_PATH + "some-profile"))); } @@ -680,7 +687,7 @@ public void checkPermission(Permission perm) { // Verify credentials are correctly resolved from instance profile assertThat(credentials.accessKeyId()).isEqualTo("ACCESS_KEY_ID"); assertThat(credentials.secretAccessKey()).isEqualTo("SECRET_ACCESS_KEY"); - assertThat(credentials.providerName()).isPresent().contains("InstanceProfileCredentialsProvider"); + assertThat(credentials.providerName()).isPresent().contains(BusinessMetricFeatureId.CREDENTIALS_IMDS.value()); // Verify IMDS was called verifyImdsCallWithToken(); diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProcessCredentialsProviderTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProcessCredentialsProviderTest.java index 0fdedff07646..3f9f2050154f 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProcessCredentialsProviderTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/ProcessCredentialsProviderTest.java @@ -36,6 +36,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.utils.DateUtils; import software.amazon.awssdk.utils.IoUtils; import software.amazon.awssdk.utils.Platform; @@ -70,6 +71,12 @@ static void teardown() { } } + @Test + void testToString() { + ProcessCredentialsProvider provider = ProcessCredentialsProvider.builder().command("test").build(); + assertThat(provider.toString()).contains("ProcessCredentialsProvider"); + } + @ParameterizedTest(name = "{index} - {0}") @MethodSource("staticCredentialsValues") void staticCredentialsCanBeLoaded(String description, String staticAccountId, Optional expectedValue, @@ -143,7 +150,7 @@ public void staticCredentials_commandAsListOfStrings_CanBeLoaded() { assertThat(credentials).isInstanceOf(AwsBasicCredentials.class); assertThat(credentials.accessKeyId()).isEqualTo("accessKeyId"); assertThat(credentials.secretAccessKey()).isEqualTo("secretAccessKey"); - assertThat(credentials.providerName()).isPresent().contains("ProcessCredentialsProvider"); + assertThat(credentials.providerName()).isPresent().hasValue(BusinessMetricFeatureId.CREDENTIALS_PROCESS.value()); } @Test @@ -186,11 +193,13 @@ void sessionCredentialsWithStaticAccountIdCanBeLoaded() { scriptLocation, ACCESS_KEY_ID, SECRET_ACCESS_KEY, expiration)) .credentialRefreshThreshold(Duration.ofSeconds(1)) .staticAccountId("staticAccountId") + .sourceChain("v") .build(); AwsCredentials credentials = credentialsProvider.resolveCredentials(); verifySessionCredentials(credentials, expiration); assertThat(credentials.accountId()).isPresent().hasValue("staticAccountId"); + assertThat(credentials.providerName()).isPresent().hasValue("v,w"); } private void verifySessionCredentials(AwsCredentials credentials, String expiration) { diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/StaticCredentialsProviderTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/StaticCredentialsProviderTest.java index d02b633dfd2e..5502610543b1 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/StaticCredentialsProviderTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/StaticCredentialsProviderTest.java @@ -19,6 +19,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import org.junit.jupiter.api.Test; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; class StaticCredentialsProviderTest { @Test @@ -39,6 +40,7 @@ void getAwsCredentialsWithAccountId_ReturnsSameCredentials() { .build(); AwsCredentials actualCredentials = StaticCredentialsProvider.create(credentials).resolveCredentials(); assertThat(actualCredentials).isEqualTo(credentials); + assertThat(actualCredentials.providerName()).isPresent().contains(BusinessMetricFeatureId.CREDENTIALS_CODE.value()); } @@ -48,7 +50,7 @@ void getSessionAwsCredentials_ReturnsSameCredentials() { AwsCredentials actualCredentials = StaticCredentialsProvider.create(credentials).resolveCredentials(); assertThat(credentials).isEqualTo(actualCredentials); assertThat(credentials.providerName()).isNotPresent(); - assertThat(actualCredentials.providerName()).isPresent(); + assertThat(actualCredentials.providerName()).isPresent().contains(BusinessMetricFeatureId.CREDENTIALS_CODE.value()); } @Test diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/SystemSettingCredentialsProvidersTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/SystemSettingCredentialsProvidersTest.java index 8961c5d0a18c..9ecc620a5af4 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/SystemSettingCredentialsProvidersTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/SystemSettingCredentialsProvidersTest.java @@ -22,13 +22,16 @@ import java.util.List; import java.util.Optional; import java.util.function.Consumer; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import software.amazon.awssdk.core.SdkSystemSetting; import software.amazon.awssdk.core.exception.SdkClientException; +import software.amazon.awssdk.core.useragent.BusinessMetricFeatureId; import software.amazon.awssdk.testutils.EnvironmentVariableHelper; import software.amazon.awssdk.utils.Pair; @@ -66,7 +69,9 @@ void configureEnvVars_resolveCredentials(String description, configureEnvironmentVariables(systemSettings); EnvironmentVariableCredentialsProvider provider = EnvironmentVariableCredentialsProvider.create(); if (expected != null) { - assertThat(provider.resolveCredentials()).satisfies(expected); + AwsCredentials resolvedCredentials = provider.resolveCredentials(); + assertThat(resolvedCredentials).satisfies(expected); + assertThat(resolvedCredentials.providerName()).isPresent().contains(BusinessMetricFeatureId.CREDENTIALS_ENV_VARS.value()); } else { assertThatThrownBy(provider::resolveCredentials).isInstanceOf(SdkClientException.class); } @@ -80,7 +85,9 @@ void configureSystemProperties_resolveCredentials(String description, configureSystemProperties(systemSettings); SystemPropertyCredentialsProvider provider = SystemPropertyCredentialsProvider.create(); if (expected != null) { - assertThat(provider.resolveCredentials()).satisfies(expected); + AwsCredentials resolvedCredentials = provider.resolveCredentials(); + assertThat(resolvedCredentials).satisfies(expected); + assertThat(resolvedCredentials.providerName()).isPresent().contains(BusinessMetricFeatureId.CREDENTIALS_JVM_SYSTEM_PROPERTIES.value()); } else { assertThatThrownBy(provider::resolveCredentials).isInstanceOf(SdkClientException.class); } @@ -123,6 +130,18 @@ private static List config() { ); } + @Test + void testEnvVarsClassName() { + EnvironmentVariableCredentialsProvider provider = EnvironmentVariableCredentialsProvider.create(); + Assertions.assertThat(provider.toString()).contains("EnvironmentVariableCredentialsProvider"); + } + + @Test + void testSystemPropertyClassName() { + SystemPropertyCredentialsProvider provider = SystemPropertyCredentialsProvider.create(); + Assertions.assertThat(provider.toString()).contains("SystemPropertyCredentialsProvider"); + } + private void configureEnvironmentVariables(List> systemSettings) { for (Pair setting : systemSettings) { ENVIRONMENT_VARIABLE_HELPER.set(setting.left(), setting.right()); diff --git a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/SystemSettingsCredentialsProviderTest.java b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/SystemSettingsCredentialsProviderTest.java index 81905de526ac..95cfc899460f 100644 --- a/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/SystemSettingsCredentialsProviderTest.java +++ b/core/auth/src/test/java/software/amazon/awssdk/auth/credentials/SystemSettingsCredentialsProviderTest.java @@ -47,7 +47,7 @@ void systemPropertyCredentialsProvider_resolveCredentials_returnsCredentialsWith AwsCredentials credentials = SystemPropertyCredentialsProvider.create().resolveCredentials(); assertThat(credentials.accessKeyId()).isEqualTo("akid1"); assertThat(credentials.secretAccessKey()).isEqualTo("skid1"); - assertThat(credentials.providerName()).isPresent().contains("SystemPropertyCredentialsProvider"); + assertThat(credentials.providerName()).isPresent().contains("f"); } @Test @@ -55,6 +55,6 @@ void environmentVariableCredentialsProvider_resolveCredentials_returnsCredential AwsCredentials credentials = EnvironmentVariableCredentialsProvider.create().resolveCredentials(); assertThat(credentials.accessKeyId()).isEqualTo("akid2"); assertThat(credentials.secretAccessKey()).isEqualTo("skid2"); - assertThat(credentials.providerName()).isPresent().contains("EnvironmentVariableCredentialsProvider"); + assertThat(credentials.providerName()).isPresent().contains("g"); } } diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStage.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStage.java index ef1e3fb2cc9d..6cbb536ad43b 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStage.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStage.java @@ -15,23 +15,19 @@ package software.amazon.awssdk.core.internal.http.pipeline.stages; -import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.AUTH_SOURCE; import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.BUSINESS_METADATA; -import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.CONFIG_METADATA; import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.SLASH; import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.SPACE; import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.appendSpaceAndField; -import static software.amazon.awssdk.core.internal.useragent.UserAgentConstant.uaPair; import static software.amazon.awssdk.utils.StringUtils.trim; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.concurrent.CompletableFuture; import software.amazon.awssdk.annotations.SdkInternalApi; import software.amazon.awssdk.core.ApiName; -import software.amazon.awssdk.core.SelectedAuthScheme; import software.amazon.awssdk.core.client.config.SdkAdvancedClientOption; import software.amazon.awssdk.core.client.config.SdkClientConfiguration; import software.amazon.awssdk.core.client.config.SdkClientOption; @@ -40,12 +36,10 @@ import software.amazon.awssdk.core.internal.http.HttpClientDependencies; import software.amazon.awssdk.core.internal.http.RequestExecutionContext; import software.amazon.awssdk.core.internal.http.pipeline.MutableRequestToRequestPipeline; -import software.amazon.awssdk.core.internal.useragent.IdentityProviderNameMapping; import software.amazon.awssdk.core.useragent.AdditionalMetadata; import software.amazon.awssdk.core.useragent.BusinessMetricCollection; import software.amazon.awssdk.http.SdkHttpFullRequest; import software.amazon.awssdk.identity.spi.Identity; -import software.amazon.awssdk.utils.CollectionUtils; import software.amazon.awssdk.utils.CompletableFutureUtils; import software.amazon.awssdk.utils.Logger; import software.amazon.awssdk.utils.Pair; @@ -118,10 +112,6 @@ private String finalizeUserAgent(RequestExecutionContext context) { userAgentMetadata.forEach(s -> javaUserAgent.append(SPACE).append(s)); } - //add remaining SDK user agent properties - identityProviderName(context.executionAttributes()).ifPresent( - authSource -> appendSpaceAndField(javaUserAgent, CONFIG_METADATA, uaPair(AUTH_SOURCE, authSource))); - Optional businessMetrics = getBusinessMetricsString(context.executionAttributes(), groupedApiNames.right()); businessMetrics.ifPresent( metrics -> appendSpaceAndField(javaUserAgent, BUSINESS_METADATA, metrics) @@ -156,29 +146,33 @@ private static Optional getBusinessMetricsString(ExecutionAttributes exe Collection metricsFromApiNames) { BusinessMetricCollection businessMetrics = executionAttributes.getAttribute(SdkInternalExecutionAttribute.BUSINESS_METRICS); - if (businessMetrics == null && CollectionUtils.isNullOrEmpty(metricsFromApiNames)) { - return Optional.empty(); - } if (businessMetrics == null) { businessMetrics = new BusinessMetricCollection(); } businessMetrics.merge(metricsFromApiNames); - return Optional.of(businessMetrics.asBoundedString()); - } - private static Optional identityProviderName(ExecutionAttributes executionAttributes) { - SelectedAuthScheme selectedAuthScheme = executionAttributes - .getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME); - if (selectedAuthScheme == null) { + credentialProviderBusinessMetrics(executionAttributes).ifPresent(businessMetrics::merge); + + if (businessMetrics.recordedMetrics().isEmpty()) { return Optional.empty(); } - return providerNameFromIdentity(selectedAuthScheme); + + return Optional.of(businessMetrics.asBoundedString()); } - private static Optional providerNameFromIdentity(SelectedAuthScheme selectedAuthScheme) { - CompletableFuture identityFuture = selectedAuthScheme.identity(); - T identity = CompletableFutureUtils.joinLikeSync(identityFuture); - return identity.providerName().flatMap(IdentityProviderNameMapping::mapFrom); + private static Optional> credentialProviderBusinessMetrics( + ExecutionAttributes executionAttributes) { + return Optional.ofNullable( + executionAttributes.getAttribute(SdkInternalExecutionAttribute.SELECTED_AUTH_SCHEME)) + .map(selectedAuthScheme -> + CompletableFutureUtils.joinLikeSync(selectedAuthScheme.identity())) + .flatMap(Identity::providerName) + .map(providerName -> { + if (StringUtils.isBlank(providerName)) { + return Collections.emptyList(); + } + return Collections.singletonList(providerName); + }); } /** diff --git a/core/sdk-core/src/main/java/software/amazon/awssdk/core/useragent/BusinessMetricFeatureId.java b/core/sdk-core/src/main/java/software/amazon/awssdk/core/useragent/BusinessMetricFeatureId.java index 7f1483d56895..884f57bf5691 100644 --- a/core/sdk-core/src/main/java/software/amazon/awssdk/core/useragent/BusinessMetricFeatureId.java +++ b/core/sdk-core/src/main/java/software/amazon/awssdk/core/useragent/BusinessMetricFeatureId.java @@ -22,7 +22,7 @@ /** * An enum class representing a short form of identity providers to record in the UA string. * - * Unimplemented metrics: I,J,K,M,O,S,U-c,e-[latest] + * Unimplemented metrics: I,J,K,M,O,S,U-c * Unsupported metrics (these will never be added): A,H */ @SdkProtectedApi @@ -42,6 +42,27 @@ public enum BusinessMetricFeatureId { RESOLVED_ACCOUNT_ID("T"), DDB_MAPPER("d"), BEARER_SERVICE_ENV_VARS("3"), + CREDENTIALS_CODE("e"), + CREDENTIALS_JVM_SYSTEM_PROPERTIES("f"), + CREDENTIALS_ENV_VARS("g"), + CREDENTIALS_ENV_VARS_STS_WEB_ID_TOKEN("h"), + CREDENTIALS_STS_ASSUME_ROLE("i"), + CREDENTIALS_STS_ASSUME_ROLE_SAML("j"), + CREDENTIALS_STS_ASSUME_ROLE_WEB_ID("k"), + CREDENTIALS_STS_FEDERATION_TOKEN("l"), + CREDENTIALS_STS_SESSION_TOKEN("m"), + CREDENTIALS_PROFILE("n"), + CREDENTIALS_PROFILE_SOURCE_PROFILE("o"), + CREDENTIALS_PROFILE_NAMED_PROVIDER("p"), + CREDENTIALS_PROFILE_STS_WEB_ID_TOKEN("q"), + CREDENTIALS_PROFILE_SSO("r"), + CREDENTIALS_SSO("s"), + CREDENTIALS_PROFILE_SSO_LEGACY("t"), + CREDENTIALS_SSO_LEGACY("u"), + CREDENTIALS_PROFILE_PROCESS("v"), + CREDENTIALS_PROCESS("w"), + CREDENTIALS_HTTP("z"), + CREDENTIALS_IMDS("0"), UNKNOWN("Unknown"); private static final Map VALUE_MAP = diff --git a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStageTest.java b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStageTest.java index d02654a78071..4db0103b7e3c 100644 --- a/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStageTest.java +++ b/core/sdk-core/src/test/java/software/amazon/awssdk/core/internal/http/pipeline/stages/ApplyUserAgentStageTest.java @@ -60,7 +60,7 @@ public class ApplyUserAgentStageTest { (HttpSigner) Mockito.mock(HttpSigner.class), AuthSchemeOption.builder().schemeId("mock").build()); - private static final String PROVIDER_SOURCE = "ProcessCredentialsProvider"; + private static final String PROVIDER_SOURCE = "w"; private static final AwsCredentialsIdentity IDENTITY_WITHOUT_SOURCE = AwsCredentialsIdentity.create("akid", "secret"); @@ -149,7 +149,7 @@ public void when_identityContainsProvider_authSourceIsPresent() throws Exception List userAgentHeaders = request.headers().get(HEADER_USER_AGENT); assertThat(userAgentHeaders).isNotNull().hasSize(1); - assertThat(userAgentHeaders.get(0)).contains("auth-source#proc"); + assertThat(userAgentHeaders.get(0)).contains("m/w"); } private static HttpClientDependencies dependencies(String clientUserAgent) { 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..42465940b7ca 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 sourceChain; + 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.sourceChain = builder.sourceChain; + + this.providerName = StringUtils.isEmpty(builder.sourceChain) + ? PROVIDER_NAME + : builder.sourceChain + "," + 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,7 +121,7 @@ private SessionCredentialsHolder getUpdatedCredentials(SsoClient ssoClient) { .secretAccessKey(roleCredentials.secretAccessKey()) .sessionToken(roleCredentials.sessionToken()) .accountId(request.accountId()) - .providerName(PROVIDER_NAME) + .providerName(this.providerName) .build(); return new SessionCredentialsHolder(sessionCredentials, Instant.ofEpochMilli(roleCredentials.expiration())); } @@ -206,6 +215,13 @@ public interface Builder extends CopyableBuilder getRoleCredentialsRequestSupplier); + /** + * An optional string denoting previous credentials providers that are chained with this one. + * This method is primarily intended for use by AWS SDK internal components and should not be used directly by + * external users. + */ + Builder sourceChain(String sourceChain); + /** * Create a {@link SsoCredentialsProvider} using the configuration applied to this builder. * @return @@ -220,6 +236,7 @@ protected static final class BuilderImpl implements Builder { private Duration staleTime; private Duration prefetchTime; private Supplier getRoleCredentialsRequestSupplier; + private String sourceChain; BuilderImpl() { @@ -231,6 +248,7 @@ public BuilderImpl(SsoCredentialsProvider provider) { this.staleTime = provider.staleTime; this.prefetchTime = provider.prefetchTime; this.getRoleCredentialsRequestSupplier = provider.getRoleCredentialsRequestSupplier; + this.sourceChain = provider.sourceChain; } @Override @@ -268,6 +286,12 @@ public Builder refreshRequest(Supplier getRoleCredent return this; } + @Override + public Builder sourceChain(String sourceChain) { + this.sourceChain = sourceChain; + 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..40a95b35f9ce 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) + .sourceChain(credentialsContext.sourceChain()) .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..0cd86614c242 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 sourceChain; + 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.sourceChain = builder.sourceChain; + this.providerName = StringUtils.isEmpty(builder.sourceChain) + ? PROVIDER_NAME + : builder.sourceChain + "," + 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 sourceChain; private Builder() { super(StsAssumeRoleCredentialsProvider::new); @@ -111,6 +120,7 @@ private Builder() { private Builder(StsAssumeRoleCredentialsProvider provider) { super(StsAssumeRoleCredentialsProvider::new, provider); this.assumeRoleRequestSupplier = provider.assumeRoleRequestSupplier; + this.sourceChain = provider.sourceChain; } /** @@ -145,6 +155,21 @@ public Builder refreshRequest(Consumer assumeRoleRequ return refreshRequest(AssumeRoleRequest.builder().applyMutation(assumeRoleRequest).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 + * and should not be used directly by external users.

+ * + * @param sourceChain The source identifier for business metrics tracking. + * @return This object for chained calls. + */ + public Builder sourceChain(String sourceChain) { + this.sourceChain = sourceChain; + 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..34e833cbda59 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 sourceChain; + 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.sourceChain = builder.sourceChain; + this.providerName = StringUtils.isEmpty(builder.sourceChain) + ? PROVIDER_NAME + : builder.sourceChain + "," + 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 sourceChain; private Builder() { super(StsAssumeRoleWithSamlCredentialsProvider::new); @@ -104,6 +113,7 @@ private Builder() { public Builder(StsAssumeRoleWithSamlCredentialsProvider provider) { super(StsAssumeRoleWithSamlCredentialsProvider::new, provider); this.assumeRoleWithSamlRequestSupplier = provider.assumeRoleWithSamlRequestSupplier; + this.sourceChain = provider.sourceChain; } /** @@ -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 + * and should not be used directly by external users.

+ * + * @param sourceChain The source identifier for business metrics tracking. + * @return This object for chained calls. + */ + public Builder sourceChain(String sourceChain) { + this.sourceChain = sourceChain; + 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..aad3bfed0566 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 sourceChain; + 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.sourceChain = builder.sourceChain; + this.providerName = StringUtils.isEmpty(builder.sourceChain) + ? PROVIDER_NAME + : builder.sourceChain + "," + 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 sourceChain; private Builder() { super(StsAssumeRoleWithWebIdentityCredentialsProvider::new); @@ -104,6 +113,7 @@ private Builder() { public Builder(StsAssumeRoleWithWebIdentityCredentialsProvider provider) { super(StsAssumeRoleWithWebIdentityCredentialsProvider::new, provider); this.assumeRoleWithWebIdentityRequestSupplier = provider.assumeRoleWithWebIdentityRequest; + this.sourceChain = provider.sourceChain; } /** @@ -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 + * and should not be used directly by external users.

+ * + * @param sourceChain The source identifier for business metrics tracking. + * @return This object for chained calls. + */ + public Builder sourceChain(String sourceChain) { + this.sourceChain = sourceChain; + 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..7fb6a33e9cba 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 sourceChain; + 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.sourceChain = builder.sourceChain; + this.providerName = StringUtils.isEmpty(builder.sourceChain) + ? PROVIDER_NAME + : builder.sourceChain + "," + 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 sourceChain; private Builder() { super(StsGetFederationTokenCredentialsProvider::new); @@ -111,6 +120,7 @@ private Builder() { public Builder(StsGetFederationTokenCredentialsProvider provider) { super(StsGetFederationTokenCredentialsProvider::new, provider); this.getFederationTokenRequest = provider.getFederationTokenRequest; + this.sourceChain = provider.sourceChain; } /** @@ -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 + * and should not be used directly by external users.

+ * + * @param sourceChain The source identifier for business metrics tracking. + * @return This object for chained calls. + */ + public Builder sourceChain(String sourceChain) { + this.sourceChain = sourceChain; + 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..263b82b146c8 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 sourceChain; + 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.sourceChain = builder.sourceChain; + this.providerName = StringUtils.isEmpty(builder.sourceChain) + ? PROVIDER_NAME + : builder.sourceChain + "," + 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 sourceChain; private Builder() { super(StsGetSessionTokenCredentialsProvider::new); @@ -98,6 +107,7 @@ private Builder() { public Builder(StsGetSessionTokenCredentialsProvider provider) { super(StsGetSessionTokenCredentialsProvider::new, provider); this.getSessionTokenRequest = provider.getSessionTokenRequest; + this.sourceChain = provider.sourceChain; } /** @@ -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 + * and should not be used directly by external users.

+ * + * @param sourceChain The source identifier for business metrics tracking. + * @return This object for chained calls. + */ + public Builder sourceChain(String sourceChain) { + this.sourceChain = sourceChain; + 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..2a7c007b700b 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 sourceChain() { + return Optional.ofNullable(sourceChain); + } + //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 sourceChain; public Builder assumeRoleWithWebIdentityRequest(AssumeRoleWithWebIdentityRequest request) { this.request = request; @@ -78,6 +85,11 @@ public AssumeRoleWithWebIdentityRequestSupplier build() { return new AssumeRoleWithWebIdentityRequestSupplier(this); } + public Builder sourceChain(String sourceChain) { + this.sourceChain = sourceChain; + 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..e20236a8652d 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 @@ -42,20 +42,27 @@ public final class StsProfileCredentialsProviderFactory implements ChildProfileC @Override public AwsCredentialsProvider create(AwsCredentialsProvider sourceCredentialsProvider, Profile profile) { - return new StsProfileCredentialsProvider(sourceCredentialsProvider, profile); + return new StsProfileCredentialsProvider(sourceCredentialsProvider, profile, null); + } + + @Override + public AwsCredentialsProvider create(ChildProfileCredentialsRequest request) { + return new StsProfileCredentialsProvider(request.sourceCredentialsProvider(), request.profile(), + request.sourceChain()); } /** * 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 sourceChain) { String roleArn = requireProperty(profile, ProfileProperty.ROLE_ARN); String roleSessionName = profile.property(ProfileProperty.ROLE_SESSION_NAME) .orElseGet(() -> "aws-sdk-java-" + System.currentTimeMillis()); @@ -76,6 +83,7 @@ private StsProfileCredentialsProvider(AwsCredentialsProvider parentCredentialsPr this.credentialsProvider = StsAssumeRoleCredentialsProvider.builder() .stsClient(stsClient) .refreshRequest(assumeRoleRequest) + .sourceChain(sourceChain) .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..eeaae98a6597 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()) + .sourceChain(credentialProperties.sourceChain()) .build(); StsAssumeRoleWithWebIdentityCredentialsProvider.Builder builder = StsAssumeRoleWithWebIdentityCredentialsProvider.builder() .asyncCredentialUpdateEnabled(asyncCredentialUpdateEnabled) .stsClient(stsClient) - .refreshRequest(supplier); + .refreshRequest(supplier) + .sourceChain(credentialProperties.sourceChain()); 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 d65c94e0516c..fc1da8f24f29 100644 --- a/test/auth-tests/pom.xml +++ b/test/auth-tests/pom.xml @@ -65,6 +65,12 @@ ${awsjavasdk.version} test + + software.amazon.awssdk + regions + ${awsjavasdk.version} + test + software.amazon.awssdk ssooidc @@ -141,6 +147,10 @@ log4j-slf4j-impl test + + software.amazon.awssdk + test-utils + diff --git a/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/ContainerCredentialsProviderUserAgentTest.java b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/ContainerCredentialsProviderUserAgentTest.java new file mode 100644 index 000000000000..f64591fefd5b --- /dev/null +++ b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/ContainerCredentialsProviderUserAgentTest.java @@ -0,0 +1,204 @@ +/* + * 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 com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; +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.ContainerCredentialsProvider; +import software.amazon.awssdk.core.SdkSystemSetting; +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.DateUtils; +import software.amazon.awssdk.utils.StringInputStream; + +/** + * Test class to verify that ContainerCredentialsProvider correctly includes + * business metrics in the User-Agent header. This test focuses specifically on the + * CREDENTIALS_HTTP ("z") business metric feature ID. + */ +class ContainerCredentialsProviderUserAgentTest { + private static final String CONTAINER_CREDENTIALS_PATH = "/v2/credentials/test-role-arn"; + private static final String CONTAINER_SERVICE_ENDPOINT = "http://localhost:"; + + private MockSyncHttpClient mockHttpClient; + + @RegisterExtension + static WireMockExtension wireMockServer = WireMockExtension.newInstance() + .options(wireMockConfig().dynamicPort()) + .configureStaticDsl(true) + .build(); + + @BeforeEach + public void setup() { + + System.setProperty(SdkSystemSetting.AWS_CONTAINER_SERVICE_ENDPOINT.property(), + CONTAINER_SERVICE_ENDPOINT + wireMockServer.getPort()); + System.setProperty(SdkSystemSetting.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI.property(), + CONTAINER_CREDENTIALS_PATH); + + mockHttpClient = new MockSyncHttpClient(); + mockHttpClient.stubNextResponse(mockStsResponse()); + + stubContainerCredentialsResponses(); + } + + @AfterAll + public static void teardown() { + System.clearProperty(SdkSystemSetting.AWS_CONTAINER_SERVICE_ENDPOINT.property()); + System.clearProperty(SdkSystemSetting.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI.property()); + System.clearProperty(SdkSystemSetting.AWS_CONTAINER_AUTHORIZATION_TOKEN.property()); + } + + private static HttpExecuteResponse mockStsResponse() { + return HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder().statusCode(200).build()) + .responseBody(AbortableInputStream.create(new StringInputStream(""))) + .build(); + } + + private void stubContainerCredentialsResponses() { + String credentialsResponse = createCredentialsResponse("ACCESS_KEY_ID", "SECRET_ACCESS_KEY", null); + wireMockServer.stubFor(get(urlPathEqualTo(CONTAINER_CREDENTIALS_PATH)) + .willReturn(aResponse().withBody(credentialsResponse))); + } + + private void stubContainerCredentialsResponsesWithSessionToken() { + String credentialsResponse = createCredentialsResponse("ACCESS_KEY_ID", "SECRET_ACCESS_KEY", "SESSION_TOKEN"); + wireMockServer.stubFor(get(urlPathEqualTo(CONTAINER_CREDENTIALS_PATH)) + .willReturn(aResponse().withBody(credentialsResponse))); + } + + private void stubContainerCredentialsResponsesWithAuthToken() { + System.setProperty(SdkSystemSetting.AWS_CONTAINER_AUTHORIZATION_TOKEN.property(), "test-auth-token"); + + String credentialsResponse = createCredentialsResponse("ACCESS_KEY_ID", "SECRET_ACCESS_KEY", null); + wireMockServer.stubFor(get(urlPathEqualTo(CONTAINER_CREDENTIALS_PATH)) + .willReturn(aResponse().withBody(credentialsResponse))); + } + + private String createCredentialsResponse(String accessKeyId, String secretAccessKey, String sessionToken) { + StringBuilder response = new StringBuilder(); + response.append("{"); + response.append("\"AccessKeyId\":\"").append(accessKeyId).append("\","); + response.append("\"SecretAccessKey\":\"").append(secretAccessKey).append("\","); + if (sessionToken != null) { + response.append("\"Token\":\"").append(sessionToken).append("\","); + } + response.append("\"Expiration\":\"").append(DateUtils.formatIso8601Date(Instant.now().plus(Duration.ofHours(1)))).append("\""); + response.append("}"); + return response.toString(); + } + + @ParameterizedTest + @MethodSource("containerCredentialProviders") + void userAgentString_containsContainerBusinessMetric_WhenUsingContainerCredentials( + IdentityProvider provider, String expected) throws Exception { + + 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); + } + + private static Stream containerCredentialProviders() { + return Stream.of( + Arguments.of(ContainerCredentialsProvider.create(), "m/D,z"), + + Arguments.of(ContainerCredentialsProvider.builder() + .endpoint(CONTAINER_SERVICE_ENDPOINT + wireMockServer.getPort()) + .build(), "m/D,z") + ); + } + + @ParameterizedTest + @MethodSource("containerCredentialProvidersWithSessionToken") + void userAgentString_containsContainerBusinessMetric_WhenUsingContainerCredentialsWithSessionToken( + IdentityProvider provider, String expected) throws Exception { + + stubContainerCredentialsResponsesWithSessionToken(); + + 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); + } + + private static Stream containerCredentialProvidersWithSessionToken() { + return Stream.of( + Arguments.of(ContainerCredentialsProvider.create(), "m/D,z") + ); + } + + @ParameterizedTest + @MethodSource("containerCredentialProvidersWithAuthToken") + void userAgentString_containsContainerBusinessMetric_WhenUsingContainerCredentialsWithAuthToken( + IdentityProvider provider, String expected) throws Exception { + + stubContainerCredentialsResponsesWithAuthToken(); + + 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); + } + + private static Stream containerCredentialProvidersWithAuthToken() { + return Stream.of( + Arguments.of(ContainerCredentialsProvider.create(), "m/D,z") + ); + } + + 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/source/EnvironmentVariableCredentialsProviderUserAgentTest.java b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/EnvironmentVariableCredentialsProviderUserAgentTest.java new file mode 100644 index 000000000000..8c171a3ed57a --- /dev/null +++ b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/EnvironmentVariableCredentialsProviderUserAgentTest.java @@ -0,0 +1,129 @@ +/* + * 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.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterAll; +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.EnvironmentVariableCredentialsProvider; +import software.amazon.awssdk.core.SdkSystemSetting; +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; +import software.amazon.awssdk.testutils.EnvironmentVariableHelper; + +/** + * Test class to verify that EnvironmentVariableCredentialsProvider correctly includes + * business metrics in the User-Agent header. This test focuses specifically on the + * CREDENTIALS_ENV_VARS ("g") business metric feature ID. + */ +class EnvironmentVariableCredentialsProviderUserAgentTest { + + private MockSyncHttpClient mockHttpClient; + private static final EnvironmentVariableHelper ENVIRONMENT_VARIABLE_HELPER = new EnvironmentVariableHelper(); + + @BeforeEach + 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()); + } + + @AfterAll + public static void teardown() { + System.clearProperty(SdkSystemSetting.AWS_ACCESS_KEY_ID.property()); + System.clearProperty(SdkSystemSetting.AWS_SECRET_ACCESS_KEY.property()); + System.clearProperty(SdkSystemSetting.AWS_SESSION_TOKEN.property()); + } + + private static HttpExecuteResponse mockStsResponse() { + return HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder().statusCode(200).build()) + .responseBody(AbortableInputStream.create(new StringInputStream(""))) + .build(); + } + + @ParameterizedTest + @MethodSource("environmentVariableCredentialProviders") + void userAgentString_containsEnvironmentVariableBusinessMetric_WhenUsingEnvironmentVariableCredentials( + IdentityProvider provider, String expected) throws Exception { + + 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); + + } + + private static Stream environmentVariableCredentialProviders() { + return Stream.of( + Arguments.of(EnvironmentVariableCredentialsProvider.create(), "m/D,g") + ); + } + + @ParameterizedTest + @MethodSource("environmentVariableCredentialProvidersWithSessionToken") + void userAgentString_containsEnvironmentVariableBusinessMetric_WhenUsingEnvironmentVariableCredentialsWithSessionToken( + IdentityProvider provider, String expected) throws Exception { + + System.setProperty(SdkSystemSetting.AWS_SESSION_TOKEN.property(), "test-session-token"); + + 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); + } + + private static Stream environmentVariableCredentialProvidersWithSessionToken() { + return Stream.of( + Arguments.of(EnvironmentVariableCredentialsProvider.create(), "m/D,g") + ); + } + + 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/source/ImdsUserAgentProviderTest.java b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/ImdsUserAgentProviderTest.java new file mode 100644 index 000000000000..07bf771d62c9 --- /dev/null +++ b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/ImdsUserAgentProviderTest.java @@ -0,0 +1,186 @@ +/* + * 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 com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.put; +import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.assertj.core.api.Assertions.assertThat; + +import com.github.tomakehurst.wiremock.junit5.WireMockExtension; +import java.time.Duration; +import java.time.Instant; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.extension.RegisterExtension; +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.InstanceProfileCredentialsProvider; +import software.amazon.awssdk.core.SdkSystemSetting; +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.DateUtils; +import software.amazon.awssdk.utils.StringInputStream; + +/** + * Test class to verify that InstanceProfileCredentialsProvider (IMDS) correctly includes + * business metrics in the User-Agent header. + */ +class ImdsUserAgentProviderTest { + private static final String TOKEN_RESOURCE_PATH = "/latest/api/token"; + private static final String CREDENTIALS_RESOURCE_PATH = "/latest/meta-data/iam/security-credentials/"; + private static final String TEST_ROLE_NAME = "test-role"; + private static final String TOKEN_STUB = "test-token"; + + private MockSyncHttpClient mockHttpClient; + + @RegisterExtension + static WireMockExtension wireMockServer = WireMockExtension.newInstance() + .options(wireMockConfig().dynamicPort()) + .configureStaticDsl(true) + .build(); + + @BeforeEach + public void setup() { + + System.setProperty(SdkSystemSetting.AWS_EC2_METADATA_SERVICE_ENDPOINT.property(), + "http://localhost:" + wireMockServer.getPort()); + + mockHttpClient = new MockSyncHttpClient(); + mockHttpClient.stubNextResponse(mockStsResponse()); + + stubImdsResponses(); + } + + @AfterAll + public static void teardown() { + System.clearProperty(SdkSystemSetting.AWS_EC2_METADATA_SERVICE_ENDPOINT.property()); + } + + private static HttpExecuteResponse mockStsResponse() { + return HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder().statusCode(200).build()) + .responseBody(AbortableInputStream.create(new StringInputStream(""))) + .build(); + } + + private void stubImdsResponses() { + wireMockServer.stubFor(put(urlPathEqualTo(TOKEN_RESOURCE_PATH)) + .willReturn(aResponse().withBody(TOKEN_STUB))); + + wireMockServer.stubFor(get(urlPathEqualTo(CREDENTIALS_RESOURCE_PATH)) + .willReturn(aResponse().withBody(TEST_ROLE_NAME))); + + String credentialsResponse = createCredentialsResponse("ACCESS_KEY_ID", "SECRET_ACCESS_KEY", + null); + wireMockServer.stubFor(get(urlPathEqualTo(CREDENTIALS_RESOURCE_PATH + TEST_ROLE_NAME)) + .willReturn(aResponse().withBody(credentialsResponse))); + } + + private void stubImdsResponsesWithSessionToken() { + wireMockServer.stubFor(put(urlPathEqualTo(TOKEN_RESOURCE_PATH)) + .willReturn(aResponse().withBody(TOKEN_STUB))); + + wireMockServer.stubFor(get(urlPathEqualTo(CREDENTIALS_RESOURCE_PATH)) + .willReturn(aResponse().withBody(TEST_ROLE_NAME))); + + String credentialsResponse = createCredentialsResponse("ACCESS_KEY_ID", "SECRET_ACCESS_KEY", + "SESSION_TOKEN"); + wireMockServer.stubFor(get(urlPathEqualTo(CREDENTIALS_RESOURCE_PATH + TEST_ROLE_NAME)) + .willReturn(aResponse().withBody(credentialsResponse))); + } + + private String createCredentialsResponse(String accessKeyId, String secretAccessKey, String sessionToken) { + StringBuilder response = new StringBuilder(); + response.append("{"); + response.append("\"AccessKeyId\":\"").append(accessKeyId).append("\","); + response.append("\"SecretAccessKey\":\"").append(secretAccessKey).append("\","); + if (sessionToken != null) { + response.append("\"Token\":\"").append(sessionToken).append("\","); + } + response.append("\"Expiration\":\"").append(DateUtils.formatIso8601Date(Instant.now().plus(Duration.ofHours(1)))) + .append("\""); + response.append("}"); + return response.toString(); + } + + @ParameterizedTest + @MethodSource("imdsCredentialProviders") + void userAgentString_containsImdsBusinessMetric_WhenUsingInstanceProfileCredentials( + IdentityProvider provider, String expected) throws Exception { + + 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); + } + + private static Stream imdsCredentialProviders() { + return Stream.of( + Arguments.of(InstanceProfileCredentialsProvider.create(), "m/D,0"), + + Arguments.of(InstanceProfileCredentialsProvider.builder() + .endpoint("http://localhost:" + wireMockServer.getPort()) + .build(), "m/D,0") + ); + } + + @ParameterizedTest + @MethodSource("imdsCredentialProvidersWithSessionToken") + void userAgentString_containsImdsBusinessMetric_WhenUsingInstanceProfileCredentialsWithSessionToken( + IdentityProvider provider, String expected) throws Exception { + + stubImdsResponsesWithSessionToken(); + + 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); + } + + private static Stream imdsCredentialProvidersWithSessionToken() { + return Stream.of( + Arguments.of(InstanceProfileCredentialsProvider.create(), "m/D,0") + ); + } + + 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/source/ProcessCredentialsProviderUserAgentTest.java b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/ProcessCredentialsProviderUserAgentTest.java new file mode 100644 index 000000000000..0731a48b071f --- /dev/null +++ b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/ProcessCredentialsProviderUserAgentTest.java @@ -0,0 +1,157 @@ +/* + * 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.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Arrays; +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.ProcessCredentialsProvider; +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.DateUtils; +import software.amazon.awssdk.utils.StringInputStream; + +/** + * Test class to verify that ProcessCredentialsProvider correctly includes + * business metrics in the User-Agent header. This test focuses specifically on the + * CREDENTIALS_PROCESS ("w") business metric feature ID. + */ +class ProcessCredentialsProviderUserAgentTest { + + private MockSyncHttpClient mockHttpClient; + + @BeforeEach + public void setup() { + mockHttpClient = new MockSyncHttpClient(); + mockHttpClient.stubNextResponse(mockStsResponse()); + } + + private static HttpExecuteResponse mockStsResponse() { + return HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder().statusCode(200).build()) + .responseBody(AbortableInputStream.create(new StringInputStream(""))) + .build(); + } + + @ParameterizedTest + @MethodSource("processCredentialProviders") + void userAgentString_containsProcessBusinessMetric_WhenUsingProcessCredentials( + IdentityProvider provider, String expected) throws Exception { + + 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); + } + + private static Stream processCredentialProviders() { + String mockCommand = createMockCredentialsCommand(false); + List mockCommandList = createMockCredentialsCommandList(false); + + return Stream.of( + Arguments.of(ProcessCredentialsProvider.builder() + .command(mockCommand) + .build(), "m/D,w"), + + Arguments.of(ProcessCredentialsProvider.builder() + .command(mockCommandList) + .build(), "m/D,w") + ); + } + + @ParameterizedTest + @MethodSource("processCredentialProvidersWithSessionToken") + void userAgentString_containsProcessBusinessMetric_WhenUsingProcessCredentialsWithSessionToken( + IdentityProvider provider, String expected) throws Exception { + + 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); + } + + private static Stream processCredentialProvidersWithSessionToken() { + String mockCommand = createMockCredentialsCommand(true); + + return Stream.of( + Arguments.of(ProcessCredentialsProvider.builder() + .command(mockCommand) + .build(), "m/D,w") + ); + } + + private static String createMockCredentialsCommand(boolean includeSessionToken) { + String credentialsJson = createCredentialsJson(includeSessionToken); + + return "echo '" + credentialsJson + "'"; + } + + private static List createMockCredentialsCommandList(boolean includeSessionToken) { + String credentialsJson = createCredentialsJson(includeSessionToken); + + // Use echo command as a list + return Arrays.asList("echo", credentialsJson); + } + + private static String createCredentialsJson(boolean includeSessionToken) { + StringBuilder json = new StringBuilder(); + json.append("{"); + 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(); + } + + 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/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/SystemPropertyCredentialsProviderUserAgentTest.java b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/SystemPropertyCredentialsProviderUserAgentTest.java new file mode 100644 index 000000000000..de57169198fb --- /dev/null +++ b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/SystemPropertyCredentialsProviderUserAgentTest.java @@ -0,0 +1,123 @@ +/* + * 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.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.AfterAll; +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.SystemPropertyCredentialsProvider; +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 SystemPropertyCredentialsProvider correctly includes + * business metrics in the User-Agent header. This test focuses specifically on the + * CREDENTIALS_JVM_SYSTEM_PROPERTIES ("f") business metric feature ID. + */ +class SystemPropertyCredentialsProviderUserAgentTest { + + private MockSyncHttpClient mockHttpClient; + + @BeforeEach + public void setup() { + + System.setProperty("aws.accessKeyId", "test-access-key"); + System.setProperty("aws.secretAccessKey", "test-secret-key"); + + // Setup mock HTTP client for STS calls + mockHttpClient = new MockSyncHttpClient(); + mockHttpClient.stubNextResponse(mockStsResponse()); + } + + @AfterAll + public static void teardown() { + System.clearProperty("aws.accessKeyId"); + System.clearProperty("aws.secretAccessKey"); + System.clearProperty("aws.sessionToken"); + } + + private static HttpExecuteResponse mockStsResponse() { + return HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder().statusCode(200).build()) + .responseBody(AbortableInputStream.create(new StringInputStream(""))) + .build(); + } + + @ParameterizedTest + @MethodSource("systemPropertyCredentialProviders") + void userAgentString_containsSystemPropertyBusinessMetric_WhenUsingSystemPropertyCredentials( + IdentityProvider provider, String expected) throws Exception { + + 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); + } + + private static Stream systemPropertyCredentialProviders() { + return Stream.of( + Arguments.of(SystemPropertyCredentialsProvider.create(), "m/D,f") + ); + } + + @ParameterizedTest + @MethodSource("systemPropertyCredentialProvidersWithSessionToken") + void userAgentString_containsSystemPropertyBusinessMetric_WhenUsingSystemPropertyCredentialsWithSessionToken( + IdentityProvider provider, String expected) throws Exception { + + System.setProperty("aws.sessionToken", "test-session-token"); + + 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); + } + + private static Stream systemPropertyCredentialProvidersWithSessionToken() { + return Stream.of( + Arguments.of(SystemPropertyCredentialsProvider.create(), "m/D,f") + ); + } + + 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/source/UserAgentProviderTest.java b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/UserAgentProviderTest.java index ffe82176afff..4ee93a9afd32 100644 --- a/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/UserAgentProviderTest.java +++ b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/source/UserAgentProviderTest.java @@ -46,7 +46,7 @@ class UserAgentProviderTest { private MockSyncHttpClient mockHttpClient; @BeforeEach - public void setup() throws UnsupportedEncodingException { + public void setup() { mockHttpClient = new MockSyncHttpClient(); mockHttpClient.stubNextResponse(mockResponse()); } @@ -74,8 +74,8 @@ void userAgentString_containsCredentialProviderNames_IfPresent(IdentityProvider< private static Stream credentialProviders() { return Stream.of( - Arguments.of(StaticCredentialsProvider.create(SESSION_IDENTITY), "stat"), - Arguments.of(StaticCredentialsProvider.create(BASIC_IDENTITY), "stat") + Arguments.of(StaticCredentialsProvider.create(SESSION_IDENTITY), "m/D,e"), + Arguments.of(StaticCredentialsProvider.create(BASIC_IDENTITY), "m/D,e") ); } 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..57c3d7aa1294 --- /dev/null +++ b/test/auth-tests/src/it/java/software/amazon/awssdk/auth/sts/StsCredentialsProviderUserAgentTest.java @@ -0,0 +1,245 @@ +/* + * 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.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +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")); + + AwsBasicCredentials staticCredentials = AwsBasicCredentials.create("AKIATEST", "test-secret"); + + StsClient stsClient = StsClient.builder() + .httpClient(mockHttpClient) + .credentialsProvider(StaticCredentialsProvider.create(staticCredentials)) + .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) + .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")); + + AwsBasicCredentials staticCredentials = AwsBasicCredentials.create("AKIATEST", "test-secret"); + + StsClient stsClient = StsClient.builder() + .httpClient(mockHttpClient) + .credentialsProvider(StaticCredentialsProvider.create(staticCredentials)) + .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")); + + AwsBasicCredentials staticCredentials = AwsBasicCredentials.create("AKIATEST", "test-secret"); + + StsClient stsClient = StsClient.builder() + .httpClient(mockHttpClient) + .credentialsProvider(StaticCredentialsProvider.create(staticCredentials)) + .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) + .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 = "\n" + + " \n" + + " \n" + + " AKIATEST\n" + + " test-secret\n" + + " test-session-token\n" + + " 2099-12-31T23:59:59Z\n" + + " \n" + + " \n" + + " arn:aws:sts::123456789012:assumed-role/TestRole/test-session\n" + + " AROATEST:test-session\n" + + " \n" + + " \n" + + ""; + + return HttpExecuteResponse.builder() + .response(SdkHttpResponse.builder().statusCode(200).build()) + .responseBody(AbortableInputStream.create(new StringInputStream(responseBody))) + .build(); + } +}