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 extends T> 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 extends AwsCredentialsIdentity> 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 extends AwsCredentialsIdentity> 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 extends AwsCredentialsIdentity> 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 extends AwsCredentialsIdentity> 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 extends AwsCredentialsIdentity> 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 extends AwsCredentialsIdentity> 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 extends AwsCredentialsIdentity> 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 extends AwsCredentialsIdentity> 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 extends AwsCredentialsIdentity> 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 extends AwsCredentialsIdentity> 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 extends AwsCredentialsIdentity> 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 extends AwsCredentialsIdentity> 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 extends AwsCredentialsIdentity> 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 extends AwsCredentialsIdentity> 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 extends AwsCredentialsIdentity> 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 extends AwsCredentialsIdentity> 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();
+ }
+}