diff --git a/aws-runtime/aws-config/api/aws-config.api b/aws-runtime/aws-config/api/aws-config.api index 15c22b39604..cf329e7c96e 100644 --- a/aws-runtime/aws-config/api/aws-config.api +++ b/aws-runtime/aws-config/api/aws-config.api @@ -97,13 +97,6 @@ public final class aws/sdk/kotlin/runtime/auth/credentials/InvalidSsoTokenExcept public synthetic fun (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V } -public final class aws/sdk/kotlin/runtime/auth/credentials/LazilyInitializedCredentialsProvider : aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProvider { - public fun (Ljava/lang/String;Lkotlin/jvm/functions/Function0;)V - public synthetic fun (Ljava/lang/String;Lkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V - public fun resolve (Laws/smithy/kotlin/runtime/collections/Attributes;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public fun toString ()Ljava/lang/String; -} - public final class aws/sdk/kotlin/runtime/auth/credentials/ProcessCredentialsProvider : aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProvider { public fun (Ljava/lang/String;Laws/smithy/kotlin/runtime/util/PlatformProvider;JJ)V public synthetic fun (Ljava/lang/String;Laws/smithy/kotlin/runtime/util/PlatformProvider;JJILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -216,6 +209,17 @@ public final class aws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityCredent public static synthetic fun fromEnvironment-TUY-ock$default (Laws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityCredentialsProvider$Companion;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;JLaws/smithy/kotlin/runtime/util/PlatformProvider;Laws/smithy/kotlin/runtime/http/engine/HttpClientEngine;ILjava/lang/Object;)Laws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityCredentialsProvider; } +public final class aws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityProvider : aws/smithy/kotlin/runtime/auth/awscredentials/CloseableCredentialsProvider { + public fun ()V + public fun (Laws/smithy/kotlin/runtime/util/PlatformProvider;Laws/smithy/kotlin/runtime/http/engine/HttpClientEngine;Ljava/lang/String;)V + public synthetic fun (Laws/smithy/kotlin/runtime/util/PlatformProvider;Laws/smithy/kotlin/runtime/http/engine/HttpClientEngine;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun close ()V + public final fun getHttpClient ()Laws/smithy/kotlin/runtime/http/engine/HttpClientEngine; + public final fun getPlatformProvider ()Laws/smithy/kotlin/runtime/util/PlatformProvider; + public final fun getRegion ()Ljava/lang/String; + public fun resolve (Laws/smithy/kotlin/runtime/collections/Attributes;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + public final class aws/sdk/kotlin/runtime/auth/credentials/SystemPropertyCredentialsProvider : aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProvider { public fun ()V public fun (Lkotlin/jvm/functions/Function1;)V diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/DefaultChainCredentialsProvider.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/DefaultChainCredentialsProvider.kt index 08b16646a05..5b849ec0a35 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/DefaultChainCredentialsProvider.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/DefaultChainCredentialsProvider.kt @@ -7,6 +7,8 @@ package aws.sdk.kotlin.runtime.auth.credentials import aws.sdk.kotlin.runtime.config.AwsSdkSetting import aws.sdk.kotlin.runtime.config.imds.ImdsClient +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetric import aws.smithy.kotlin.runtime.auth.awscredentials.* import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.http.engine.DefaultHttpEngine @@ -52,14 +54,8 @@ public class DefaultChainCredentialsProvider constructor( private val chain = CredentialsProviderChain( SystemPropertyCredentialsProvider(platformProvider::getProperty), EnvironmentCredentialsProvider(platformProvider::getenv), - LazilyInitializedCredentialsProvider("EnvironmentStsWebIdentityCredentialsProvider") { - // STS web identity provider can be constructed from either the profile OR 100% from the environment - StsWebIdentityCredentialsProvider.fromEnvironment( - platformProvider = platformProvider, - httpClient = httpClient, - region = region, - ) - }, + // STS web identity provider can be constructed from either the profile OR 100% from the environment + StsWebIdentityProvider(platformProvider = platformProvider, httpClient = engine, region = region), ProfileCredentialsProvider(profileName = profileName, platformProvider = platformProvider, httpClient = engine, region = region), EcsCredentialsProvider(platformProvider, engine), ImdsCredentialsProvider( @@ -86,3 +82,20 @@ public class DefaultChainCredentialsProvider constructor( override fun toString(): String = this.simpleClassName + ": " + this.chain } + +/** + * Wrapper around [StsWebIdentityCredentialsProvider] that delays any exceptions until [resolve] is invoked. + * This allows it to be part of the default chain and any failures result in the chain to move onto the next provider. + */ +public class StsWebIdentityProvider( + public val platformProvider: PlatformProvider = PlatformProvider.System, + public val httpClient: HttpClientEngine? = null, + public val region: String? = null, +) : CloseableCredentialsProvider { + override suspend fun resolve(attributes: Attributes): Credentials { + val wrapped = StsWebIdentityCredentialsProvider.fromEnvironment(platformProvider = platformProvider, httpClient = httpClient, region = region) + return wrapped.resolve(attributes).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_ENV_VARS_STS_WEB_ID_TOKEN) + } + + override fun close() { } +} diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/EcsCredentialsProvider.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/EcsCredentialsProvider.kt index ce0a4e76ef3..06afaf043c5 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/EcsCredentialsProvider.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/EcsCredentialsProvider.kt @@ -7,6 +7,8 @@ package aws.sdk.kotlin.runtime.auth.credentials import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.sdk.kotlin.runtime.config.AwsSdkSetting +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetric import aws.smithy.kotlin.runtime.ErrorMetadata import aws.smithy.kotlin.runtime.auth.awscredentials.* import aws.smithy.kotlin.runtime.client.endpoints.Endpoint @@ -76,6 +78,7 @@ public class EcsCredentialsProvider( override suspend fun resolve(attributes: Attributes): Credentials { val logger = coroutineContext.logger() + val authToken = loadAuthToken() val relativeUri = AwsSdkSetting.AwsContainerCredentialsRelativeUri.resolve(platformProvider) val fullUri = AwsSdkSetting.AwsContainerCredentialsFullUri.resolve(platformProvider) @@ -110,7 +113,7 @@ public class EcsCredentialsProvider( logger.debug { "obtained credentials from container metadata service; expiration=${creds.expiration?.format(TimestampFormat.ISO_8601)}" } - return creds + return creds.withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_HTTP) } private suspend fun loadAuthToken(): String? { diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/EnvironmentCredentialsProvider.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/EnvironmentCredentialsProvider.kt index b834fe31831..59a0114e235 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/EnvironmentCredentialsProvider.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/EnvironmentCredentialsProvider.kt @@ -7,6 +7,8 @@ package aws.sdk.kotlin.runtime.auth.credentials import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.sdk.kotlin.runtime.config.AwsSdkSetting +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetric import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider import aws.smithy.kotlin.runtime.auth.awscredentials.simpleClassName @@ -36,13 +38,14 @@ public class EnvironmentCredentialsProvider( coroutineContext.trace { "Attempting to load credentials from env vars $ACCESS_KEY_ID/$SECRET_ACCESS_KEY/$SESSION_TOKEN" } + return credentials( accessKeyId = requireEnv(ACCESS_KEY_ID), secretAccessKey = requireEnv(SECRET_ACCESS_KEY), sessionToken = getEnv(SESSION_TOKEN), providerName = PROVIDER_NAME, accountId = getEnv(ACCOUNT_ID), - ) + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_ENV_VARS) } override fun toString(): String = this.simpleClassName diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/ImdsCredentialsProvider.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/ImdsCredentialsProvider.kt index a27d1343d12..495e3d810d6 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/ImdsCredentialsProvider.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/ImdsCredentialsProvider.kt @@ -9,6 +9,8 @@ import aws.sdk.kotlin.runtime.config.AwsSdkSetting import aws.sdk.kotlin.runtime.config.imds.EC2MetadataError import aws.sdk.kotlin.runtime.config.imds.ImdsClient import aws.sdk.kotlin.runtime.config.imds.InstanceMetadataProvider +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetric import aws.smithy.kotlin.runtime.auth.awscredentials.* import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.config.resolve @@ -106,7 +108,7 @@ public class ImdsCredentialsProvider( resp.sessionToken, resp.expiration, PROVIDER_NAME, - ) + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_IMDS) creds.also { mu.withLock { previousCredentials = it } diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/LazilyInitializedCredentialsProvider.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/LazilyInitializedCredentialsProvider.kt deleted file mode 100644 index 0d3315a07ea..00000000000 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/LazilyInitializedCredentialsProvider.kt +++ /dev/null @@ -1,24 +0,0 @@ -package aws.sdk.kotlin.runtime.auth.credentials - -import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials -import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider -import aws.smithy.kotlin.runtime.collections.Attributes - -/** - * A [CredentialsProvider] implementation that delays the initialization of the underlying provider until - * the first call to [resolve]. This is useful when the initialization of the credentials provider is expensive - * or should be deferred until credentials are actually needed. - * - * @param providerName The name of the credentials provider that is being wrapped. Will default to "LazilyInitializedCredentialsProvider". - * @param initializer A lambda function that provides the actual [CredentialsProvider] to be initialized lazily. - */ -public class LazilyInitializedCredentialsProvider( - private val providerName: String = "LazilyInitializedCredentialsProvider", - initializer: () -> CredentialsProvider, -) : CredentialsProvider { - private val provider = lazy(initializer) - - override suspend fun resolve(attributes: Attributes): Credentials = provider.value.resolve(attributes) - - override fun toString(): String = providerName -} diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/ProcessCredentialsProvider.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/ProcessCredentialsProvider.kt index 1334eaff933..5a1c3a3761b 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/ProcessCredentialsProvider.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/ProcessCredentialsProvider.kt @@ -5,10 +5,9 @@ package aws.sdk.kotlin.runtime.auth.credentials import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials -import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials -import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider -import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProviderException -import aws.smithy.kotlin.runtime.auth.awscredentials.simpleClassName +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetric +import aws.smithy.kotlin.runtime.auth.awscredentials.* import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.serde.json.JsonDeserializer import aws.smithy.kotlin.runtime.telemetry.logging.logger @@ -68,14 +67,16 @@ public class ProcessCredentialsProvider( val deserializer = JsonDeserializer(payload) return when (val resp = deserializeJsonProcessCredentials(deserializer)) { - is JsonCredentialsResponse.SessionCredentials -> credentials( - resp.accessKeyId, - resp.secretAccessKey, - resp.sessionToken, - resp.expiration ?: Instant.MAX_VALUE, - PROVIDER_NAME, - resp.accountId, - ) + is JsonCredentialsResponse.SessionCredentials -> { + credentials( + resp.accessKeyId, + resp.secretAccessKey, + resp.sessionToken, + resp.expiration ?: Instant.MAX_VALUE, + PROVIDER_NAME, + resp.accountId, + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_PROCESS) + } else -> throw CredentialsProviderException("Credentials response was not of expected format") } } diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/ProfileCredentialsProvider.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/ProfileCredentialsProvider.kt index 0fc10b27ca0..81935c0be9e 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/ProfileCredentialsProvider.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/ProfileCredentialsProvider.kt @@ -9,16 +9,18 @@ import aws.sdk.kotlin.runtime.InternalSdkApi import aws.sdk.kotlin.runtime.auth.credentials.profile.LeafProvider import aws.sdk.kotlin.runtime.auth.credentials.profile.ProfileChain import aws.sdk.kotlin.runtime.auth.credentials.profile.RoleArn +import aws.sdk.kotlin.runtime.auth.credentials.profile.RoleArnSource import aws.sdk.kotlin.runtime.client.AwsClientOption import aws.sdk.kotlin.runtime.config.AwsSdkSetting import aws.sdk.kotlin.runtime.config.imds.ImdsClient import aws.sdk.kotlin.runtime.config.profile.AwsConfigurationSource import aws.sdk.kotlin.runtime.config.profile.loadAwsSharedConfig +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetrics import aws.sdk.kotlin.runtime.region.resolveRegion -import aws.smithy.kotlin.runtime.auth.awscredentials.CloseableCredentialsProvider -import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials -import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider -import aws.smithy.kotlin.runtime.auth.awscredentials.simpleClassName +import aws.smithy.kotlin.runtime.auth.awscredentials.* +import aws.smithy.kotlin.runtime.businessmetrics.BusinessMetric +import aws.smithy.kotlin.runtime.businessmetrics.BusinessMetrics import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.http.engine.HttpClientEngine import aws.smithy.kotlin.runtime.io.closeIfCloseable @@ -86,6 +88,7 @@ public class ProfileCredentialsProvider @InternalSdkApi constructor( public val httpClient: HttpClientEngine? = null, public val configurationSource: AwsConfigurationSource? = null, ) : CloseableCredentialsProvider { + private val credentialsBusinessMetrics: MutableSet = mutableSetOf() public constructor( profileName: String? = null, @@ -131,12 +134,21 @@ public class ProfileCredentialsProvider @InternalSdkApi constructor( chain.roles.forEach { roleArn -> logger.debug { "Assuming role `${roleArn.roleArn}`" } + if (roleArn.source == RoleArnSource.SOURCE_PROFILE) { + credentialsBusinessMetrics.add(AwsBusinessMetric.Credentials.CREDENTIALS_PROFILE_SOURCE_PROFILE) + } + val assumeProvider = roleArn.toCredentialsProvider(creds, region) + + creds.attributes.getOrNull(BusinessMetrics)?.forEach { metric -> + credentialsBusinessMetrics.add(metric) + } + creds = assumeProvider.resolve(attributes) } logger.debug { "Obtained credentials from profile; expiration=${creds.expiration?.format(TimestampFormat.ISO_8601)}" } - return creds + return creds.withBusinessMetrics(credentialsBusinessMetrics) } override fun close() { @@ -147,10 +159,13 @@ public class ProfileCredentialsProvider @InternalSdkApi constructor( private suspend fun LeafProvider.toCredentialsProvider(region: LazyAsyncValue): CredentialsProvider = when (this) { - is LeafProvider.NamedSource -> namedProviders[name] - ?: throw ProviderConfigurationException("unknown credentials source: $name") + is LeafProvider.NamedSource -> namedProviders[name].also { + credentialsBusinessMetrics.add(AwsBusinessMetric.Credentials.CREDENTIALS_PROFILE_NAMED_PROVIDER) + } ?: throw ProviderConfigurationException("unknown credentials source: $name") - is LeafProvider.AccessKey -> StaticCredentialsProvider(credentials) + is LeafProvider.AccessKey -> StaticCredentialsProvider(credentials).also { + credentialsBusinessMetrics.add(AwsBusinessMetric.Credentials.CREDENTIALS_PROFILE) + } is LeafProvider.WebIdentityTokenRole -> StsWebIdentityCredentialsProvider( roleArn, @@ -159,7 +174,9 @@ public class ProfileCredentialsProvider @InternalSdkApi constructor( roleSessionName = sessionName, platformProvider = platformProvider, httpClient = httpClient, - ) + ).also { + credentialsBusinessMetrics.add(AwsBusinessMetric.Credentials.CREDENTIALS_PROFILE_STS_WEB_ID_TOKEN) + } is LeafProvider.SsoSession -> SsoCredentialsProvider( accountId = ssoAccountId, @@ -169,7 +186,9 @@ public class ProfileCredentialsProvider @InternalSdkApi constructor( ssoSessionName = ssoSessionName, httpClient = httpClient, platformProvider = platformProvider, - ) + ).also { + credentialsBusinessMetrics.add(AwsBusinessMetric.Credentials.CREDENTIALS_PROFILE_SSO) + } is LeafProvider.LegacySso -> SsoCredentialsProvider( accountId = ssoAccountId, @@ -178,9 +197,13 @@ public class ProfileCredentialsProvider @InternalSdkApi constructor( ssoRegion = ssoRegion, httpClient = httpClient, platformProvider = platformProvider, - ) + ).also { + credentialsBusinessMetrics.add(AwsBusinessMetric.Credentials.CREDENTIALS_PROFILE_SSO_LEGACY) + } - is LeafProvider.Process -> ProcessCredentialsProvider(command) + is LeafProvider.Process -> ProcessCredentialsProvider(command).also { + credentialsBusinessMetrics.add(AwsBusinessMetric.Credentials.CREDENTIALS_PROFILE_PROCESS) + } } private suspend fun RoleArn.toCredentialsProvider( diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/SsoCredentialsProvider.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/SsoCredentialsProvider.kt index 43534179a16..3cb4304b5f8 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/SsoCredentialsProvider.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/SsoCredentialsProvider.kt @@ -8,14 +8,12 @@ package aws.sdk.kotlin.runtime.auth.credentials import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.sdk.kotlin.runtime.auth.credentials.internal.sso.SsoClient import aws.sdk.kotlin.runtime.auth.credentials.internal.sso.getRoleCredentials -import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials -import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider -import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProviderException -import aws.smithy.kotlin.runtime.auth.awscredentials.simpleClassName +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetric +import aws.smithy.kotlin.runtime.auth.awscredentials.* import aws.smithy.kotlin.runtime.client.SdkClientOption import aws.smithy.kotlin.runtime.collections.Attributes import aws.smithy.kotlin.runtime.http.engine.HttpClientEngine -import aws.smithy.kotlin.runtime.serde.json.* import aws.smithy.kotlin.runtime.telemetry.logging.logger import aws.smithy.kotlin.runtime.telemetry.telemetryProvider import aws.smithy.kotlin.runtime.time.Clock @@ -121,7 +119,7 @@ public class SsoCredentialsProvider public constructor( val roleCredentials = resp.roleCredentials ?: throw CredentialsProviderException("Expected SSO roleCredentials to not be null") - return credentials( + val creds = credentials( accessKeyId = checkNotNull(roleCredentials.accessKeyId) { "Expected accessKeyId in SSO roleCredentials response" }, secretAccessKey = checkNotNull(roleCredentials.secretAccessKey) { "Expected secretAccessKey in SSO roleCredentials response" }, sessionToken = roleCredentials.sessionToken, @@ -129,6 +127,12 @@ public class SsoCredentialsProvider public constructor( PROVIDER_NAME, accountId = accountId, ) + + return if (ssoTokenProvider != null) { + creds.withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_SSO) + } else { + creds.withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_SSO_LEGACY) + } } // non sso-session legacy token flow diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StsAssumeRoleCredentialsProvider.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StsAssumeRoleCredentialsProvider.kt index 83e306e794e..e84781e95ea 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StsAssumeRoleCredentialsProvider.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StsAssumeRoleCredentialsProvider.kt @@ -13,6 +13,8 @@ import aws.sdk.kotlin.runtime.auth.credentials.internal.sts.model.PolicyDescript import aws.sdk.kotlin.runtime.auth.credentials.internal.sts.model.RegionDisabledException import aws.sdk.kotlin.runtime.auth.credentials.internal.sts.model.Tag import aws.sdk.kotlin.runtime.config.AwsSdkSetting +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetric import aws.smithy.kotlin.runtime.auth.awscredentials.* import aws.smithy.kotlin.runtime.client.SdkClientOption import aws.smithy.kotlin.runtime.collections.Attributes @@ -146,7 +148,7 @@ public class StsAssumeRoleCredentialsProvider( expiration = roleCredentials.expiration, providerName = PROVIDER_NAME, accountId = accountId, - ) + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_STS_ASSUME_ROLE) } override fun toString(): String = this.simpleClassName diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityCredentialsProvider.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityCredentialsProvider.kt index 6a7b6056b88..3f5147f1968 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityCredentialsProvider.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityCredentialsProvider.kt @@ -11,6 +11,8 @@ import aws.sdk.kotlin.runtime.auth.credentials.internal.sts.StsClient import aws.sdk.kotlin.runtime.auth.credentials.internal.sts.assumeRoleWithWebIdentity import aws.sdk.kotlin.runtime.auth.credentials.internal.sts.model.PolicyDescriptorType import aws.sdk.kotlin.runtime.config.AwsSdkSetting +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetric import aws.smithy.kotlin.runtime.auth.awscredentials.* import aws.smithy.kotlin.runtime.client.SdkClientOption import aws.smithy.kotlin.runtime.collections.Attributes @@ -104,6 +106,7 @@ public class StsWebIdentityCredentialsProvider( override suspend fun resolve(attributes: Attributes): Credentials { val logger = coroutineContext.logger() logger.debug { "retrieving assumed credentials via web identity" } + val provider = this val params = provider.webIdentityParameters @@ -149,7 +152,7 @@ public class StsWebIdentityCredentialsProvider( expiration = roleCredentials.expiration, providerName = PROVIDER_NAME, accountId = accountId, - ) + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_STS_ASSUME_ROLE_WEB_ID) } override fun toString(): String = this.simpleClassName diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/SystemPropertyCredentialsProvider.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/SystemPropertyCredentialsProvider.kt index 4d68fe1ce87..db610022604 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/SystemPropertyCredentialsProvider.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/SystemPropertyCredentialsProvider.kt @@ -7,6 +7,8 @@ package aws.sdk.kotlin.runtime.auth.credentials import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.sdk.kotlin.runtime.config.AwsSdkSetting +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetric import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider import aws.smithy.kotlin.runtime.auth.awscredentials.simpleClassName @@ -36,13 +38,14 @@ public class SystemPropertyCredentialsProvider( coroutineContext.trace { "Attempting to load credentials from system properties $ACCESS_KEY_ID/$SECRET_ACCESS_KEY/$SESSION_TOKEN" } + return credentials( accessKeyId = requireProperty(ACCESS_KEY_ID), secretAccessKey = requireProperty(SECRET_ACCESS_KEY), sessionToken = getProperty(SESSION_TOKEN), providerName = PROVIDER_NAME, accountId = getProperty(ACCOUNT_ID), - ) + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_JVM_SYSTEM_PROPERTIES) } override fun toString(): String = this.simpleClassName diff --git a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/profile/ProfileChain.kt b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/profile/ProfileChain.kt index a9282c87886..b43f803341e 100644 --- a/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/profile/ProfileChain.kt +++ b/aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/profile/ProfileChain.kt @@ -108,6 +108,12 @@ internal data class RoleArn( * ARN of role to assume */ val roleArn: String, + + /** + * The source used to create the [RoleArn] + */ + val source: RoleArnSource? = null, + /** * Session name to pass to the assume role provider */ @@ -119,6 +125,14 @@ internal data class RoleArn( val externalId: String? = null, ) +/** + * Represents the possible sources for creating a [RoleArn]. + */ +internal enum class RoleArnSource { + SOURCE_PROFILE, + CREDENTIALS_SOURCE, +} + internal const val ROLE_ARN = "role_arn" internal const val EXTERNAL_ID = "external_id" internal const val ROLE_SESSION_NAME = "role_session_name" @@ -148,8 +162,15 @@ private fun AwsProfile.roleArnOrNull(): RoleArn? { val roleArn = getOrNull(ROLE_ARN) ?: return null + val roleArnSource = when { + contains(SOURCE_PROFILE) && !contains(CREDENTIAL_SOURCE) -> RoleArnSource.SOURCE_PROFILE + contains(CREDENTIAL_SOURCE) && !contains(SOURCE_PROFILE) -> RoleArnSource.CREDENTIALS_SOURCE + else -> null + } + return RoleArn( roleArn, + roleArnSource, sessionName = getOrNull(ROLE_SESSION_NAME), externalId = getOrNull(EXTERNAL_ID), ) diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/ecs_assume_role/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/ecs_assume_role/test-case.json index 34730c3ab04..e2c9a8dd33f 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/ecs_assume_role/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/ecs_assume_role/test-case.json @@ -7,7 +7,8 @@ "secret_access_key": "secretkeycorrect", "session_token": "tokencorrect", "expiry": 1632249686, - "accountId": "130633740322" + "accountId": "130633740322", + "business_metrics": ["p","z", "i"] } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/ecs_credentials/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/ecs_credentials/test-case.json index a401eea0aa4..de4693e51b6 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/ecs_credentials/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/ecs_credentials/test-case.json @@ -6,7 +6,8 @@ "access_key_id": "ASIARCORRECT", "secret_access_key": "secretkeycorrect", "session_token": "tokencorrect", - "expiry": 1234567890 + "expiry": 1234567890, + "business_metrics": ["z"] } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/imds_assume_role/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/imds_assume_role/test-case.json index 8f59ddf5c61..aea0e7d55c4 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/imds_assume_role/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/imds_assume_role/test-case.json @@ -1,13 +1,14 @@ { "name": "imds-token-fail", - "docs": "acquires credentials via IMDS and uses them to assume a role", + "docs": "acquires credentials from a profile with IMDS as a named provider and uses them to assume a role", "result": { "Ok": { "access_key_id": "ASIARCORRECT", "secret_access_key": "secretkeycorrect", "session_token": "tokencorrect", "expiry": 1632249686, - "accountId": "130633740322" + "accountId": "130633740322", + "business_metrics": ["p","0", "i"] } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/imds_config_with_no_creds/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/imds_config_with_no_creds/test-case.json index 73a9fe8bb21..988fb6690a4 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/imds_config_with_no_creds/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/imds_config_with_no_creds/test-case.json @@ -6,7 +6,8 @@ "access_key_id": "ASIARTEST", "secret_access_key": "testsecret", "session_token": "testtoken", - "expiry": 1632197813 + "expiry": 1632197813, + "business_metrics": ["0"] } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/imds_default_chain_retries/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/imds_default_chain_retries/test-case.json index b8048ebbf63..c5c57a67115 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/imds_default_chain_retries/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/imds_default_chain_retries/test-case.json @@ -6,7 +6,8 @@ "access_key_id": "ASIARTEST", "secret_access_key": "testsecret", "session_token": "testtoken", - "expiry": 1632270906 + "expiry": 1632270906, + "business_metrics": ["0"] } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/imds_default_chain_success/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/imds_default_chain_success/test-case.json index f951f70eea8..63ebc8b3118 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/imds_default_chain_success/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/imds_default_chain_success/test-case.json @@ -6,7 +6,8 @@ "access_key_id": "ASIARTEST", "secret_access_key": "testsecret", "session_token": "testtoken", - "expiry": 1632197813 + "expiry": 1632197813, + "business_metrics": ["0"] } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/legacy_sso_role/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/legacy_sso_role/test-case.json index 2406302d49c..083df326798 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/legacy_sso_role/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/legacy_sso_role/test-case.json @@ -7,7 +7,8 @@ "secret_access_key": "secretkeycorrect", "session_token": "tokencorrect", "expiry": 1641240833, - "accountId": "123456789" + "accountId": "123456789", + "business_metrics": ["t","u"] } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/prefer_environment/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/prefer_environment/test-case.json index 97751f5ef4e..5763fe0e8dc 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/prefer_environment/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/prefer_environment/test-case.json @@ -4,7 +4,8 @@ "result": { "Ok": { "access_key_id": "correct_key", - "secret_access_key": "correct_secret" + "secret_access_key": "correct_secret", + "business_metrics": ["g"] } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/prefer_system_properties/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/prefer_system_properties/test-case.json index 8a0f7984a63..5a0dffa6423 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/prefer_system_properties/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/prefer_system_properties/test-case.json @@ -4,7 +4,8 @@ "result": { "Ok": { "access_key_id": "correct_key", - "secret_access_key": "correct_secret" + "secret_access_key": "correct_secret", + "business_metrics": ["f"] } } } \ No newline at end of file diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/profile_name/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/profile_name/test-case.json index f7a1a28b8b8..5d4250229df 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/profile_name/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/profile_name/test-case.json @@ -4,7 +4,8 @@ "result": { "Ok": { "access_key_id": "correct_key", - "secret_access_key": "correct_secret" + "secret_access_key": "correct_secret", + "business_metrics": ["n"] } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/profile_static_keys/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/profile_static_keys/test-case.json index c56a02bac65..10d9c849225 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/profile_static_keys/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/profile_static_keys/test-case.json @@ -4,7 +4,8 @@ "result": { "Ok": { "access_key_id": "correct_key", - "secret_access_key": "correct_secret" + "secret_access_key": "correct_secret", + "business_metrics": ["n"] } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/retry_on_error/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/retry_on_error/test-case.json index 0eccf448608..dc73d64080d 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/retry_on_error/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/retry_on_error/test-case.json @@ -7,7 +7,8 @@ "secret_access_key": "TESTSECRETKEY", "session_token": "TESTSESSIONTOKEN", "expiry": 1628193482, - "accountId": "123456789012" + "accountId": "123456789012", + "business_metrics": ["o","n", "i"] } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/sso_session/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/sso_session/test-case.json index fdb622b1ff8..462c1fae0ba 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/sso_session/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/sso_session/test-case.json @@ -7,7 +7,8 @@ "secret_access_key": "secretkeycorrect", "session_token": "tokencorrect", "expiry": 1641240833, - "accountId": "123456789" + "accountId": "123456789", + "business_metrics": ["r","s"] } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_env/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_env/test-case.json index 511e780ad17..ce71bbc5209 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_env/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_env/test-case.json @@ -7,7 +7,8 @@ "secret_access_key": "SECRETKEYTEST", "session_token": "SESSIONTOKEN_TEST", "expiry": 1629147173, - "accountId": "123456789012" + "accountId": "123456789012", + "business_metrics": ["h", "k"] } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_profile/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_profile/test-case.json index 3d18b175471..939cdf9afcd 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_profile/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_profile/test-case.json @@ -7,7 +7,8 @@ "secret_access_key": "TESTSECRET", "session_token": "TESTSESSIONTOKEN", "expiry": 1629233704, - "accountId": "123456789012" + "accountId": "123456789012", + "business_metrics": ["q", "k"] } } } diff --git a/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_source_profile/test-case.json b/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_source_profile/test-case.json index b1b0ea1e38b..22ade3a6fea 100644 --- a/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_source_profile/test-case.json +++ b/aws-runtime/aws-config/common/test-resources/default-provider-chain/web_identity_token_source_profile/test-case.json @@ -7,7 +7,8 @@ "secret_access_key": "TESTSECRETKEY", "session_token": "TESTSESSIONTOKEN", "expiry": 1628193482, - "accountId": "123456789012" + "accountId": "123456789012", + "business_metrics": ["o","q", "k", "i"] } } } diff --git a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/EcsCredentialsProviderTest.kt b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/EcsCredentialsProviderTest.kt index 4aeb36c0097..8401b507b59 100644 --- a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/EcsCredentialsProviderTest.kt +++ b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/EcsCredentialsProviderTest.kt @@ -7,6 +7,8 @@ package aws.sdk.kotlin.runtime.auth.credentials import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.sdk.kotlin.runtime.config.AwsSdkSetting +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetric import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProviderException import aws.smithy.kotlin.runtime.http.Headers @@ -46,7 +48,7 @@ class EcsCredentialsProviderTest { "test-token", expectedExpiration, "EcsContainer", - ) + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_HTTP) private fun ecsResponse(accountId: String? = null): HttpResponse { val payload = buildJsonObject { @@ -576,7 +578,7 @@ class EcsCredentialsProviderTest { expectedExpiration, "EcsContainer", "12345", - ) + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_HTTP) assertEquals(expected, actual) engine.assertRequests() } diff --git a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/EnvironmentCredentialsProviderTest.kt b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/EnvironmentCredentialsProviderTest.kt index 09ac8d1392c..23605206185 100644 --- a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/EnvironmentCredentialsProviderTest.kt +++ b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/EnvironmentCredentialsProviderTest.kt @@ -7,6 +7,8 @@ package aws.sdk.kotlin.runtime.auth.credentials import aws.sdk.kotlin.runtime.client.AwsClientOption import aws.sdk.kotlin.runtime.config.AwsSdkSetting +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetric import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.collections.attributesOf import io.kotest.matchers.string.shouldContain @@ -25,7 +27,15 @@ class EnvironmentCredentialsProviderTest { AwsSdkSetting.AwsSecretAccessKey.envVar to "def", AwsSdkSetting.AwsSessionToken.envVar to "ghi", ) - assertEquals(provider.resolve(), Credentials("abc", "def", "ghi", providerName = "Environment")) + assertEquals( + provider.resolve(), + Credentials( + "abc", + "def", + "ghi", + providerName = "Environment", + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_ENV_VARS), + ) } @Test @@ -34,7 +44,15 @@ class EnvironmentCredentialsProviderTest { AwsSdkSetting.AwsAccessKeyId.envVar to "abc", AwsSdkSetting.AwsSecretAccessKey.envVar to "def", ) - assertEquals(provider.resolve(), Credentials("abc", "def", null, providerName = "Environment")) + assertEquals( + provider.resolve(), + Credentials( + "abc", + "def", + null, + providerName = "Environment", + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_ENV_VARS), + ) } @Test @@ -85,7 +103,7 @@ class EnvironmentCredentialsProviderTest { "def", providerName = "Environment", attributes = attributesOf { AwsClientOption.AccountId to "12345" }, - ) + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_ENV_VARS) assertEquals(expected, actual) } } diff --git a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/ImdsCredentialsProviderTest.kt b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/ImdsCredentialsProviderTest.kt index 26e8e8f517b..90a30e9e9c8 100644 --- a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/ImdsCredentialsProviderTest.kt +++ b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/ImdsCredentialsProviderTest.kt @@ -7,6 +7,8 @@ package aws.sdk.kotlin.runtime.auth.credentials import aws.sdk.kotlin.runtime.config.AwsSdkSetting import aws.sdk.kotlin.runtime.config.imds.* import aws.sdk.kotlin.runtime.config.imds.DEFAULT_TOKEN_TTL_SECONDS +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetric import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProviderException import aws.smithy.kotlin.runtime.http.* @@ -126,7 +128,7 @@ class ImdsCredentialsProviderTest { "IQote///test0", expiration0, "IMDSv2", - ) + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_IMDS) assertEquals(expected0, actual0) testClock.advance(1.seconds) @@ -138,7 +140,7 @@ class ImdsCredentialsProviderTest { "IQote///test1", expiration1, "IMDSv2", - ) + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_IMDS) assertEquals(expected1, actual1) connection.assertRequests() @@ -195,7 +197,7 @@ class ImdsCredentialsProviderTest { "IQote///test", expiration, "IMDSv2", - ) + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_IMDS) assertEquals(expected, actual) connection.assertRequests() @@ -334,7 +336,7 @@ class ImdsCredentialsProviderTest { sessionToken = "IQote///test", expiration = Instant.fromEpochSeconds(1631935916), providerName = "IMDSv2", - ) + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_IMDS) assertEquals(expected, actual) @@ -402,7 +404,7 @@ class ImdsCredentialsProviderTest { sessionToken = "IQote///test", expiration = Instant.fromEpochSeconds(1631935916), providerName = "IMDSv2", - ) + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_IMDS) val provider = ImdsCredentialsProvider( profileOverride = "imds-test-role", @@ -519,7 +521,7 @@ class ImdsCredentialsProviderTest { sessionToken = "IQote///test", expiration = Instant.fromEpochSeconds(1631935916), providerName = "IMDSv2", - ) + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_IMDS) val provider = ImdsCredentialsProvider( profileOverride = "imds-test-role", diff --git a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/ProfileChainTest.kt b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/ProfileChainTest.kt index 3dae8f5ea4e..90f1268ad8a 100644 --- a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/ProfileChainTest.kt +++ b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/ProfileChainTest.kt @@ -8,6 +8,7 @@ package aws.sdk.kotlin.runtime.auth.credentials import aws.sdk.kotlin.runtime.auth.credentials.profile.LeafProvider import aws.sdk.kotlin.runtime.auth.credentials.profile.ProfileChain import aws.sdk.kotlin.runtime.auth.credentials.profile.RoleArn +import aws.sdk.kotlin.runtime.auth.credentials.profile.RoleArnSource import aws.sdk.kotlin.runtime.client.AwsClientOption import aws.sdk.kotlin.runtime.config.profile.AwsConfigurationSource import aws.sdk.kotlin.runtime.config.profile.FileType @@ -51,7 +52,7 @@ class ProfileChainTest { """, chain( LeafProvider.AccessKey(Credentials("abc123", "def456")), - RoleArn("arn:aws:iam::123456789:role/RoleA"), + RoleArn("arn:aws:iam::123456789:role/RoleA", RoleArnSource.SOURCE_PROFILE), ), ), TestCase( @@ -85,7 +86,7 @@ class ProfileChainTest { """, chain( LeafProvider.AccessKey(Credentials("abc123", "def456")), - RoleArn("arn:aws:iam::123456789:role/RoleA", "my_session_name"), + RoleArn("arn:aws:iam::123456789:role/RoleA", RoleArnSource.SOURCE_PROFILE, "my_session_name"), ), ), TestCase( @@ -102,7 +103,7 @@ class ProfileChainTest { """, chain( LeafProvider.AccessKey(Credentials("abc123", "def456")), - RoleArn("arn:aws:iam::123456789:role/RoleA", externalId = "my_external_id"), + RoleArn("arn:aws:iam::123456789:role/RoleA", RoleArnSource.SOURCE_PROFILE, externalId = "my_external_id"), ), ), TestCase( @@ -127,7 +128,7 @@ class ProfileChainTest { """, chain( LeafProvider.NamedSource("Ec2InstanceMetadata"), - RoleArn("arn:aws:iam::123456789:role/RoleA"), + RoleArn("arn:aws:iam::123456789:role/RoleA", RoleArnSource.CREDENTIALS_SOURCE), ), ), TestCase( @@ -221,8 +222,8 @@ class ProfileChainTest { """, chain( LeafProvider.AccessKey(Credentials("mno456", "pqr789")), - RoleArn("arn:aws:iam::123456789:role/RoleB"), - RoleArn("arn:aws:iam::123456789:role/RoleA"), + RoleArn("arn:aws:iam::123456789:role/RoleB", RoleArnSource.SOURCE_PROFILE), + RoleArn("arn:aws:iam::123456789:role/RoleA", RoleArnSource.SOURCE_PROFILE), ), ), TestCase( @@ -245,7 +246,7 @@ class ProfileChainTest { """, chain( LeafProvider.AccessKey(Credentials("profile_b_key", "profile_b_secret")), - RoleArn("arn:aws:iam::123456789:role/RoleA"), + RoleArn("arn:aws:iam::123456789:role/RoleA", RoleArnSource.SOURCE_PROFILE), ), ), TestCase( @@ -320,7 +321,7 @@ class ProfileChainTest { """, chain( LeafProvider.WebIdentityTokenRole("arn:aws:iam::123456789:role/RoleB", "/var/token.jwt", "some_session_name"), - RoleArn("arn:aws:iam::123456789:role/RoleA"), + RoleArn("arn:aws:iam::123456789:role/RoleA", RoleArnSource.SOURCE_PROFILE), ), ), TestCase( @@ -352,7 +353,7 @@ class ProfileChainTest { """, chain( LeafProvider.LegacySso("https://d-92671207e4.awsapps.com/start", "us-east-2", "1234567", "RoleA"), - RoleArn("arn:aws:iam::123456789:role/RoleA"), + RoleArn("arn:aws:iam::123456789:role/RoleA", RoleArnSource.SOURCE_PROFILE), ), ), TestCase( @@ -434,7 +435,7 @@ class ProfileChainTest { """, chain( LeafProvider.SsoSession("my-session", "https://d-92671207e4.awsapps.com/start", "us-east-2", "1234567", "RoleA"), - RoleArn("arn:aws:iam::123456789:role/RoleA"), + RoleArn("arn:aws:iam::123456789:role/RoleA", RoleArnSource.SOURCE_PROFILE), ), ), TestCase( @@ -571,7 +572,7 @@ class ProfileChainTest { }, ), ), - RoleArn("some-arn"), + RoleArn("some-arn", RoleArnSource.SOURCE_PROFILE), ), ), TestCase( @@ -594,7 +595,7 @@ class ProfileChainTest { """, chain( LeafProvider.NamedSource("Ec2InstanceMetadata"), - RoleArn("some-arn"), + RoleArn("some-arn", RoleArnSource.CREDENTIALS_SOURCE), ), ), TestCase( diff --git a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/ProfileCredentialsProviderTest.kt b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/ProfileCredentialsProviderTest.kt index dd26b335337..d4f6eaf51c4 100644 --- a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/ProfileCredentialsProviderTest.kt +++ b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/ProfileCredentialsProviderTest.kt @@ -7,12 +7,20 @@ package aws.sdk.kotlin.runtime.auth.credentials import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials import aws.sdk.kotlin.runtime.client.AwsClientOption +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetric +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetrics +import aws.sdk.kotlin.runtime.util.testAttributes import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials +import aws.smithy.kotlin.runtime.auth.awscredentials.copy import aws.smithy.kotlin.runtime.collections.attributesOf import aws.smithy.kotlin.runtime.httptest.TestConnection import aws.smithy.kotlin.runtime.httptest.buildTestConnection import aws.smithy.kotlin.runtime.net.Host +import aws.smithy.kotlin.runtime.time.Instant import aws.smithy.kotlin.runtime.util.TestPlatformProvider +import io.mockk.coEvery +import io.mockk.mockkStatic import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals @@ -38,6 +46,7 @@ class ProfileCredentialsProviderTest { ) val actual = provider.resolve() val expected = Credentials("AKID-Default", "Default-Secret") + .withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_PROFILE) assertEquals(expected, actual) } @@ -66,6 +75,7 @@ class ProfileCredentialsProviderTest { ) val actual = provider.resolve() val expected = Credentials("AKID-Profile", "Profile-Secret") + .withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_PROFILE) assertEquals(expected, actual) } @@ -96,6 +106,7 @@ class ProfileCredentialsProviderTest { ) val actual = provider.resolve() val expected = Credentials("AKID-Profile", "Profile-Secret") + .withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_PROFILE) assertEquals(expected, actual) } @@ -131,7 +142,15 @@ class ProfileCredentialsProviderTest { httpClient = testEngine, ) val actual = provider.resolve() - assertEquals(StsTestUtils.CREDENTIALS, actual) + val expected = StsTestUtils.CREDENTIALS.copy( + attributes = testAttributes( + StsTestUtils.CREDENTIALS.attributes, + AwsBusinessMetric.Credentials.CREDENTIALS_PROFILE_SOURCE_PROFILE, + AwsBusinessMetric.Credentials.CREDENTIALS_PROFILE, + AwsBusinessMetric.Credentials.CREDENTIALS_STS_ASSUME_ROLE, + ), + ) + assertEquals(expected, actual) testEngine.assertRequests() val req = testEngine.requests().first() @@ -316,6 +335,99 @@ class ProfileCredentialsProviderTest { ) val actual = provider.resolve() val expected = credentials("AKID-Default", "Default-Secret", accountId = "12345") + .withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_PROFILE) + assertEquals(expected, actual) + } + + @Test + fun assumeRoleWithNamedProviderBusinessMetrics() = runTest { + val testArn = "arn:aws:iam::1234567:role/test-role" + val testProvider = TestPlatformProvider( + env = mapOf( + "AWS_CONFIG_FILE" to "config", + "AWS_REGION" to "us-west-2", + "AWS_ACCESS_KEY_ID" to "1", + "AWS_SECRET_ACCESS_KEY" to "2", + ), + fs = mapOf( + "config" to """ + [default] + role_arn = $testArn + credential_source = Environment + """.trimIndent(), + ), + ) + val testEngine = buildTestConnection { + expect(StsTestUtils.stsResponse(testArn)) + } + + val provider = ProfileCredentialsProvider( + platformProvider = testProvider, + httpClient = testEngine, + ) + + val actual = provider.resolve() + val expected = StsTestUtils.CREDENTIALS.copy( + attributes = testAttributes( + StsTestUtils.CREDENTIALS.attributes, + AwsBusinessMetric.Credentials.CREDENTIALS_PROFILE_NAMED_PROVIDER, + AwsBusinessMetric.Credentials.CREDENTIALS_ENV_VARS, + AwsBusinessMetric.Credentials.CREDENTIALS_STS_ASSUME_ROLE, + ), + ) + assertEquals(expected, actual) + } + + @Test + fun processBusinessMetrics() = runTest { + val testProvider = TestPlatformProvider( + env = mapOf( + "AWS_CONFIG_FILE" to "config", + ), + fs = mapOf( + "config" to """ + [default] + credential_process = awscreds-custom + """.trimIndent(), + "awscreds-custom" to "some-process", + ), + ) + val testEngine = TestConnection() + val provider = ProfileCredentialsProvider( + platformProvider = testProvider, + httpClient = testEngine, + ) + + mockkStatic(::executeCommand) + coEvery { executeCommand(any(), any(), any(), any(), any()) }.returns( + Pair( + 0, + """ + { + "Version": 1, + "AccessKeyId": "AKID-Default", + "SecretAccessKey": "Default-Secret", + "SessionToken": "SessionToken", + "Expiration" : "2019-05-29T00:21:43Z" + } + """.trimIndent(), + ), + ) + + val actual = provider.resolve() + val expected = credentials( + "AKID-Default", + "Default-Secret", + "SessionToken", + Instant.fromIso8601("2019-05-29T00:21:43Z"), + "Process", + ).withBusinessMetrics( + setOf( + AwsBusinessMetric.Credentials.CREDENTIALS_PROFILE_PROCESS, + AwsBusinessMetric.Credentials.CREDENTIALS_PROCESS, + ), + ) + assertEquals(expected, actual) } } diff --git a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/SsoCredentialsProviderTest.kt b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/SsoCredentialsProviderTest.kt index 37f2852ea7a..af0bf6acb45 100644 --- a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/SsoCredentialsProviderTest.kt +++ b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/SsoCredentialsProviderTest.kt @@ -6,6 +6,8 @@ package aws.sdk.kotlin.runtime.auth.credentials import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetric import aws.smithy.kotlin.runtime.http.Headers import aws.smithy.kotlin.runtime.http.HttpBody import aws.smithy.kotlin.runtime.http.HttpStatusCode @@ -201,7 +203,14 @@ class SsoCredentialsProviderTest { ) val actual = provider.resolve() - val expected = credentials("AKID", "secret", "session-token", expectedExpiration, "SSO", "123456789") + val expected = credentials( + "AKID", + "secret", + "session-token", + expectedExpiration, + "SSO", + "123456789", + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_SSO_LEGACY) assertEquals(expected, actual) } } diff --git a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/StsAssumeRoleCredentialsProviderTest.kt b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/StsAssumeRoleCredentialsProviderTest.kt index c7b5d13815d..520ecbd378d 100644 --- a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/StsAssumeRoleCredentialsProviderTest.kt +++ b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/StsAssumeRoleCredentialsProviderTest.kt @@ -6,7 +6,10 @@ package aws.sdk.kotlin.runtime.auth.credentials import aws.sdk.kotlin.runtime.auth.credentials.internal.sts.model.RegionDisabledException +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.sdk.kotlin.runtime.util.testAttributes import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProviderException +import aws.smithy.kotlin.runtime.auth.awscredentials.copy import aws.smithy.kotlin.runtime.http.Headers import aws.smithy.kotlin.runtime.http.HttpBody import aws.smithy.kotlin.runtime.http.HttpStatusCode @@ -49,7 +52,13 @@ class StsAssumeRoleCredentialsProviderTest { ) val actual = provider.resolve() - assertEquals(StsTestUtils.CREDENTIALS, actual) + val expected = StsTestUtils.CREDENTIALS.copy( + attributes = testAttributes( + StsTestUtils.CREDENTIALS.attributes, + AwsBusinessMetric.Credentials.CREDENTIALS_STS_ASSUME_ROLE, + ), + ) + assertEquals(expected, actual) testEngine.assertRequests(CallAsserter.MatchingBodies) } @@ -87,7 +96,13 @@ class StsAssumeRoleCredentialsProviderTest { ) val actual = provider.resolve() - assertEquals(StsTestUtils.CREDENTIALS, actual) + val expected = StsTestUtils.CREDENTIALS.copy( + attributes = testAttributes( + StsTestUtils.CREDENTIALS.attributes, + AwsBusinessMetric.Credentials.CREDENTIALS_STS_ASSUME_ROLE, + ), + ) + assertEquals(expected, actual) testEngine.assertRequests(CallAsserter.MatchingBodies) } @@ -163,7 +178,13 @@ class StsAssumeRoleCredentialsProviderTest { ) val actual = provider.resolve() - assertEquals(StsTestUtils.CREDENTIALS, actual) + val expected = StsTestUtils.CREDENTIALS.copy( + attributes = testAttributes( + StsTestUtils.CREDENTIALS.attributes, + AwsBusinessMetric.Credentials.CREDENTIALS_STS_ASSUME_ROLE, + ), + ) + assertEquals(expected, actual) val req = testEngine.requests().first() assertEquals(Host.Domain("sts.amazonaws.com"), req.actual.url.host) } @@ -182,7 +203,13 @@ class StsAssumeRoleCredentialsProviderTest { ) val actual = provider.resolve() - assertEquals(StsTestUtils.CREDENTIALS, actual) + val expected = StsTestUtils.CREDENTIALS.copy( + attributes = testAttributes( + StsTestUtils.CREDENTIALS.attributes, + AwsBusinessMetric.Credentials.CREDENTIALS_STS_ASSUME_ROLE, + ), + ) + assertEquals(expected, actual) val req = testEngine.requests().first() assertEquals(Host.Domain("sts.us-west-2.amazonaws.com"), req.actual.url.host) } diff --git a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityCredentialsProviderTest.kt b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityCredentialsProviderTest.kt index 2194df2593a..7f4df827213 100644 --- a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityCredentialsProviderTest.kt +++ b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/StsWebIdentityCredentialsProviderTest.kt @@ -6,6 +6,8 @@ package aws.sdk.kotlin.runtime.auth.credentials import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetric import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProviderException import aws.smithy.kotlin.runtime.http.Headers import aws.smithy.kotlin.runtime.http.HttpBody @@ -34,7 +36,7 @@ private val CREDENTIALS = credentials( StsTestUtils.EPOCH + 15.minutes, "WebIdentityToken", "1234567", -) +).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_STS_ASSUME_ROLE_WEB_ID) class StsWebIdentityCredentialsProviderTest { // see https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html#API_AssumeRoleWithWebIdentity_ResponseElements diff --git a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/SystemPropertyCredentialsProviderTest.kt b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/SystemPropertyCredentialsProviderTest.kt index 4c16609885f..16d287f0e80 100644 --- a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/SystemPropertyCredentialsProviderTest.kt +++ b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/SystemPropertyCredentialsProviderTest.kt @@ -6,6 +6,8 @@ package aws.sdk.kotlin.runtime.auth.credentials import aws.sdk.kotlin.runtime.config.AwsSdkSetting +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.sdk.kotlin.runtime.util.testAttributes import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import io.kotest.matchers.string.shouldContain import kotlinx.coroutines.test.runTest @@ -23,7 +25,18 @@ class SystemPropertyCredentialsProviderTest { AwsSdkSetting.AwsSecretAccessKey.sysProp to "def", AwsSdkSetting.AwsSessionToken.sysProp to "ghi", ) - assertEquals(provider.resolve(), Credentials("abc", "def", "ghi", providerName = "SystemProperties")) + assertEquals( + provider.resolve(), + Credentials( + "abc", + "def", + "ghi", + providerName = "SystemProperties", + attributes = testAttributes( + AwsBusinessMetric.Credentials.CREDENTIALS_JVM_SYSTEM_PROPERTIES, + ), + ), + ) } @Test @@ -32,7 +45,18 @@ class SystemPropertyCredentialsProviderTest { AwsSdkSetting.AwsAccessKeyId.sysProp to "abc", AwsSdkSetting.AwsSecretAccessKey.sysProp to "def", ) - assertEquals(provider.resolve(), Credentials("abc", "def", null, providerName = "SystemProperties")) + assertEquals( + provider.resolve(), + Credentials( + "abc", + "def", + null, + providerName = "SystemProperties", + attributes = testAttributes( + AwsBusinessMetric.Credentials.CREDENTIALS_JVM_SYSTEM_PROPERTIES, + ), + ), + ) } @Test diff --git a/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/util/AwsBusinessMetricsTestUtils.kt b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/util/AwsBusinessMetricsTestUtils.kt new file mode 100644 index 00000000000..71467475b34 --- /dev/null +++ b/aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/util/AwsBusinessMetricsTestUtils.kt @@ -0,0 +1,36 @@ +package aws.sdk.kotlin.runtime.util + +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.smithy.kotlin.runtime.businessmetrics.BusinessMetric +import aws.smithy.kotlin.runtime.businessmetrics.emitBusinessMetric +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.smithy.kotlin.runtime.collections.mutableAttributes +import aws.smithy.kotlin.runtime.collections.toMutableAttributes + +/** + * [Attributes] and any [BusinessMetric] that should be included. + */ +internal fun testAttributes(attributes: Attributes? = null, vararg metrics: BusinessMetric): Attributes { + val testAttributes = attributes?.toMutableAttributes() ?: mutableAttributes() + metrics.forEach { metric -> + testAttributes.emitBusinessMetric(metric) + } + return testAttributes +} + +/** + * [Attributes] that only contain the specified [BusinessMetric]. + */ +internal fun testAttributes(vararg metrics: BusinessMetric): Attributes { + val testAttributes = mutableAttributes() + metrics.forEach { metric -> + testAttributes.emitBusinessMetric(metric) + } + return testAttributes +} + +/** + * Converts a [String] into an [AwsBusinessMetric.Credentials] if the identifier matches + */ +internal fun String.toAwsCredentialsBusinessMetric(): BusinessMetric = + AwsBusinessMetric.Credentials.entries.find { it.identifier == this } ?: throw Exception("String '$this' is not an AWS business metric") diff --git a/aws-runtime/aws-config/jvm/test/aws/sdk/kotlin/runtime/auth/credentials/DefaultChainCredentialsProviderTest.kt b/aws-runtime/aws-config/jvm/test/aws/sdk/kotlin/runtime/auth/credentials/DefaultChainCredentialsProviderTest.kt index 93bddd342f4..d56b63cd225 100644 --- a/aws-runtime/aws-config/jvm/test/aws/sdk/kotlin/runtime/auth/credentials/DefaultChainCredentialsProviderTest.kt +++ b/aws-runtime/aws-config/jvm/test/aws/sdk/kotlin/runtime/auth/credentials/DefaultChainCredentialsProviderTest.kt @@ -6,6 +6,8 @@ package aws.sdk.kotlin.runtime.auth.credentials import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetrics +import aws.sdk.kotlin.runtime.util.toAwsCredentialsBusinessMetric import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.auth.awscredentials.copy import aws.smithy.kotlin.runtime.httptest.TestConnection @@ -17,10 +19,7 @@ import aws.smithy.kotlin.runtime.util.PlatformProvider import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withContext -import kotlinx.serialization.json.Json -import kotlinx.serialization.json.jsonObject -import kotlinx.serialization.json.jsonPrimitive -import kotlinx.serialization.json.longOrNull +import kotlinx.serialization.json.* import java.io.File import java.nio.file.Paths import kotlin.test.Test @@ -96,14 +95,17 @@ class DefaultChainCredentialsProviderTest { return when { "Ok" in result -> { val o = checkNotNull(result["Ok"]).jsonObject - val creds = credentials( + val expectedBusinessMetrics = o["business_metrics"]?.jsonArray?.map { it.jsonPrimitive.content }?.toMutableSet() ?: mutableSetOf() + val expectedCreds = credentials( checkNotNull(o["access_key_id"]).jsonPrimitive.content, checkNotNull(o["secret_access_key"]).jsonPrimitive.content, o["session_token"]?.jsonPrimitive?.content, o["expiry"]?.jsonPrimitive?.longOrNull?.let { Instant.fromEpochSeconds(it) }, accountId = o["accountId"]?.jsonPrimitive?.content, + ).withBusinessMetrics( + expectedBusinessMetrics.map { it.toAwsCredentialsBusinessMetric() }.toSet(), ) - Ok(name, docs, creds) + Ok(name, docs, expectedCreds) } "ErrorContains" in result -> ErrorContains(name, docs, checkNotNull(result["ErrorContains"]).jsonPrimitive.content) else -> error("unrecognized result object: $result") diff --git a/aws-runtime/aws-config/jvm/test/aws/sdk/kotlin/runtime/auth/credentials/ProcessCredentialsProviderTest.kt b/aws-runtime/aws-config/jvm/test/aws/sdk/kotlin/runtime/auth/credentials/ProcessCredentialsProviderTest.kt index 6752529dd13..da740a32c74 100644 --- a/aws-runtime/aws-config/jvm/test/aws/sdk/kotlin/runtime/auth/credentials/ProcessCredentialsProviderTest.kt +++ b/aws-runtime/aws-config/jvm/test/aws/sdk/kotlin/runtime/auth/credentials/ProcessCredentialsProviderTest.kt @@ -5,6 +5,8 @@ package aws.sdk.kotlin.runtime.auth.credentials import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetric import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProviderException import aws.smithy.kotlin.runtime.time.Instant @@ -41,7 +43,7 @@ class ProcessCredentialsProviderTest { sessionToken = "SessionToken", expiration = Instant.fromEpochSeconds(1665705600), providerName = "Process", - ) + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_PROCESS) val processCredentialsProvider = ProcessCredentialsProvider("anyString") val actualCredentials = processCredentialsProvider.resolve() @@ -71,7 +73,7 @@ class ProcessCredentialsProviderTest { sessionToken = "SessionToken", expiration = Instant.MAX_VALUE, providerName = "Process", - ) + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_PROCESS) val processCredentialsProvider = ProcessCredentialsProvider("anyString") val actualCredentials = processCredentialsProvider.resolve() @@ -164,7 +166,7 @@ class ProcessCredentialsProviderTest { expiration = Instant.fromEpochSeconds(1665705600), providerName = "Process", accountId = "12345", - ) + ).withBusinessMetric(AwsBusinessMetric.Credentials.CREDENTIALS_PROCESS) val processCredentialsProvider = ProcessCredentialsProvider("anyString") val actualCredentials = processCredentialsProvider.resolve() diff --git a/aws-runtime/aws-http/api/aws-http.api b/aws-runtime/aws-http/api/aws-http.api index e5808232a81..8f9fe76fe31 100644 --- a/aws-runtime/aws-http/api/aws-http.api +++ b/aws-runtime/aws-http/api/aws-http.api @@ -140,14 +140,6 @@ public final class aws/sdk/kotlin/runtime/http/interceptors/AddUserAgentMetadata public fun readBeforeTransmit (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V } -public final class aws/sdk/kotlin/runtime/http/interceptors/AwsBusinessMetric : java/lang/Enum, aws/smithy/kotlin/runtime/businessmetrics/BusinessMetric { - public static final field S3_EXPRESS_BUCKET Laws/sdk/kotlin/runtime/http/interceptors/AwsBusinessMetric; - public static fun getEntries ()Lkotlin/enums/EnumEntries; - public fun getIdentifier ()Ljava/lang/String; - public static fun valueOf (Ljava/lang/String;)Laws/sdk/kotlin/runtime/http/interceptors/AwsBusinessMetric; - public static fun values ()[Laws/sdk/kotlin/runtime/http/interceptors/AwsBusinessMetric; -} - public final class aws/sdk/kotlin/runtime/http/interceptors/AwsSpanInterceptor : aws/smithy/kotlin/runtime/client/Interceptor { public static final field INSTANCE Laws/sdk/kotlin/runtime/http/interceptors/AwsSpanInterceptor; public fun modifyBeforeAttemptCompletion-gIAlu-s (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -171,7 +163,7 @@ public final class aws/sdk/kotlin/runtime/http/interceptors/AwsSpanInterceptor : public fun readBeforeTransmit (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V } -public final class aws/sdk/kotlin/runtime/http/interceptors/BusinessMetricsInterceptor : aws/smithy/kotlin/runtime/client/Interceptor { +public final class aws/sdk/kotlin/runtime/http/interceptors/UnsupportedSigningAlgorithmInterceptor : aws/smithy/kotlin/runtime/client/Interceptor { public fun ()V public fun modifyBeforeAttemptCompletion-gIAlu-s (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun modifyBeforeCompletion-gIAlu-s (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -194,7 +186,45 @@ public final class aws/sdk/kotlin/runtime/http/interceptors/BusinessMetricsInter public fun readBeforeTransmit (Laws/smithy/kotlin/runtime/client/ProtocolRequestInterceptorContext;)V } -public final class aws/sdk/kotlin/runtime/http/interceptors/UnsupportedSigningAlgorithmInterceptor : aws/smithy/kotlin/runtime/client/Interceptor { +public final class aws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric : java/lang/Enum, aws/smithy/kotlin/runtime/businessmetrics/BusinessMetric { + public static final field S3_EXPRESS_BUCKET Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public fun getIdentifier ()Ljava/lang/String; + public static fun valueOf (Ljava/lang/String;)Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric; + public static fun values ()[Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric; +} + +public final class aws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials : java/lang/Enum, aws/smithy/kotlin/runtime/businessmetrics/BusinessMetric { + public static final field CREDENTIALS_CODE Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials; + public static final field CREDENTIALS_ENV_VARS Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials; + public static final field CREDENTIALS_ENV_VARS_STS_WEB_ID_TOKEN Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials; + public static final field CREDENTIALS_HTTP Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials; + public static final field CREDENTIALS_IMDS Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials; + public static final field CREDENTIALS_JVM_SYSTEM_PROPERTIES Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials; + public static final field CREDENTIALS_PROCESS Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials; + public static final field CREDENTIALS_PROFILE Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials; + public static final field CREDENTIALS_PROFILE_NAMED_PROVIDER Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials; + public static final field CREDENTIALS_PROFILE_PROCESS Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials; + public static final field CREDENTIALS_PROFILE_SOURCE_PROFILE Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials; + public static final field CREDENTIALS_PROFILE_SSO Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials; + public static final field CREDENTIALS_PROFILE_SSO_LEGACY Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials; + public static final field CREDENTIALS_PROFILE_STS_WEB_ID_TOKEN Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials; + public static final field CREDENTIALS_SSO Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials; + public static final field CREDENTIALS_SSO_LEGACY Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials; + public static final field CREDENTIALS_STS_ASSUME_ROLE Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials; + public static final field CREDENTIALS_STS_ASSUME_ROLE_WEB_ID Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public fun getIdentifier ()Ljava/lang/String; + public static fun valueOf (Ljava/lang/String;)Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials; + public static fun values ()[Laws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetric$Credentials; +} + +public final class aws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetricsUtilsKt { + public static final fun withBusinessMetric (Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials;Laws/smithy/kotlin/runtime/businessmetrics/BusinessMetric;)Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials; + public static final fun withBusinessMetrics (Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials;Ljava/util/Set;)Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials; +} + +public final class aws/sdk/kotlin/runtime/http/interceptors/businessmetrics/BusinessMetricsInterceptor : aws/smithy/kotlin/runtime/client/Interceptor { public fun ()V public fun modifyBeforeAttemptCompletion-gIAlu-s (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun modifyBeforeCompletion-gIAlu-s (Laws/smithy/kotlin/runtime/client/ResponseInterceptorContext;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetricsUtils.kt b/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetricsUtils.kt new file mode 100644 index 00000000000..cf3d8586b3e --- /dev/null +++ b/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/interceptors/businessmetrics/AwsBusinessMetricsUtils.kt @@ -0,0 +1,98 @@ +package aws.sdk.kotlin.runtime.http.interceptors.businessmetrics + +import aws.sdk.kotlin.runtime.http.BUSINESS_METRICS_MAX_LENGTH +import aws.smithy.kotlin.runtime.InternalApi +import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials +import aws.smithy.kotlin.runtime.auth.awscredentials.copy +import aws.smithy.kotlin.runtime.businessmetrics.BusinessMetric +import aws.smithy.kotlin.runtime.businessmetrics.emitBusinessMetric +import aws.smithy.kotlin.runtime.collections.MutableAttributes +import aws.smithy.kotlin.runtime.collections.toMutableAttributes + +/** + * Makes sure the metrics do not exceed the maximum size and truncates them if so. + */ +internal fun formatMetrics(metrics: MutableSet): String { + if (metrics.isEmpty()) return "" + val metricsString = metrics.joinToString(",", "m/") { it.identifier } + val metricsByteArray = metricsString.encodeToByteArray() + + if (metricsByteArray.size <= BUSINESS_METRICS_MAX_LENGTH) return metricsString + + val lastCommaIndex = metricsByteArray + .sliceArray(0 until 1024) + .indexOfLast { it == ','.code.toByte() } + .takeIf { it != -1 } + + lastCommaIndex?.let { + return metricsByteArray.decodeToString( + 0, + lastCommaIndex, + true, + ) + } + + throw IllegalStateException("Business metrics are incorrectly formatted: $metricsString") +} + +/** + * AWS SDK specific business metrics + */ +@InternalApi +public enum class AwsBusinessMetric(public override val identifier: String) : BusinessMetric { + S3_EXPRESS_BUCKET("J"), + ; + + @InternalApi + public enum class Credentials(public override val identifier: String) : BusinessMetric { + 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_WEB_ID("k"), + 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"), + } +} + +/** + * Emits a business metric into [Credentials.attributes] + * @param metric The [BusinessMetric] to be emitted. + */ +@InternalApi +public fun Credentials.withBusinessMetric(metric: BusinessMetric): Credentials = + when (val credentialsAttributes = this.attributes) { + is MutableAttributes -> { + credentialsAttributes.emitBusinessMetric(metric) + this + } + else -> { + val newCredentialsAttributes = credentialsAttributes.toMutableAttributes() + newCredentialsAttributes.emitBusinessMetric(metric) + this.copy(attributes = newCredentialsAttributes) + } + } + +/** + * Emits business metrics into [Credentials.attributes] + * @param metrics The [BusinessMetric]s to be emitted. + */ +@InternalApi +public fun Credentials.withBusinessMetrics(metrics: Set): Credentials { + var credentials = this + metrics.forEach { metric -> + credentials = this.withBusinessMetric(metric) + } + return credentials +} diff --git a/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/interceptors/BusinessMetricsInterceptor.kt b/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/interceptors/businessmetrics/BusinessMetricsInterceptor.kt similarity index 50% rename from aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/interceptors/BusinessMetricsInterceptor.kt rename to aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/interceptors/businessmetrics/BusinessMetricsInterceptor.kt index 8a0917e05cf..43bed2aecf1 100644 --- a/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/interceptors/BusinessMetricsInterceptor.kt +++ b/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/interceptors/businessmetrics/BusinessMetricsInterceptor.kt @@ -2,12 +2,9 @@ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. * SPDX-License-Identifier: Apache-2.0 */ -package aws.sdk.kotlin.runtime.http.interceptors +package aws.sdk.kotlin.runtime.http.interceptors.businessmetrics -import aws.sdk.kotlin.runtime.http.BUSINESS_METRICS_MAX_LENGTH import aws.sdk.kotlin.runtime.http.middleware.USER_AGENT -import aws.smithy.kotlin.runtime.InternalApi -import aws.smithy.kotlin.runtime.businessmetrics.BusinessMetric import aws.smithy.kotlin.runtime.businessmetrics.BusinessMetrics import aws.smithy.kotlin.runtime.client.ProtocolRequestInterceptorContext import aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor @@ -31,37 +28,3 @@ public class BusinessMetricsInterceptor : HttpInterceptor { return context.protocolRequest } } - -/** - * Makes sure the metrics do not exceed the maximum size and truncates them if so. - */ -private fun formatMetrics(metrics: MutableSet): String { - if (metrics.isEmpty()) return "" - val metricsString = metrics.joinToString(",", "m/") - val metricsByteArray = metricsString.encodeToByteArray() - - if (metricsByteArray.size <= BUSINESS_METRICS_MAX_LENGTH) return metricsString - - val lastCommaIndex = metricsByteArray - .sliceArray(0 until 1024) - .indexOfLast { it == ','.code.toByte() } - .takeIf { it != -1 } - - lastCommaIndex?.let { - return metricsByteArray.decodeToString( - 0, - lastCommaIndex, - true, - ) - } - - throw IllegalStateException("Business metrics are incorrectly formatted: $metricsString") -} - -/** - * AWS SDK specific business metrics - */ -@InternalApi -public enum class AwsBusinessMetric(public override val identifier: String) : BusinessMetric { - S3_EXPRESS_BUCKET("J"), -} diff --git a/aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/interceptors/BusinessMetricsInterceptorTest.kt b/aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/interceptors/BusinessMetricsInterceptorTest.kt index 8469d6fd4ea..b38bdc86732 100644 --- a/aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/interceptors/BusinessMetricsInterceptorTest.kt +++ b/aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/interceptors/BusinessMetricsInterceptorTest.kt @@ -5,7 +5,10 @@ package aws.sdk.kotlin.runtime.http.interceptors import aws.sdk.kotlin.runtime.http.BUSINESS_METRICS_MAX_LENGTH +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric +import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.BusinessMetricsInterceptor import aws.sdk.kotlin.runtime.http.middleware.USER_AGENT +import aws.smithy.kotlin.runtime.businessmetrics.BusinessMetric import aws.smithy.kotlin.runtime.businessmetrics.SmithyBusinessMetric import aws.smithy.kotlin.runtime.businessmetrics.emitBusinessMetric import aws.smithy.kotlin.runtime.client.ProtocolRequestInterceptorContext @@ -67,10 +70,13 @@ class BusinessMetricsInterceptorTest { @Test fun truncateBusinessMetrics() = runTest { val executionContext = ExecutionContext() - executionContext.attributes[aws.smithy.kotlin.runtime.businessmetrics.BusinessMetrics] = mutableSetOf() for (i in 0..1024) { - executionContext.attributes[aws.smithy.kotlin.runtime.businessmetrics.BusinessMetrics].add(i.toString()) + executionContext.emitBusinessMetric( + object : BusinessMetric { + override val identifier: String = i.toString() + }, + ) } val rawMetrics = executionContext[aws.smithy.kotlin.runtime.businessmetrics.BusinessMetrics] @@ -91,9 +97,12 @@ class BusinessMetricsInterceptorTest { @Test fun malformedBusinessMetrics() = runTest { val executionContext = ExecutionContext() + val reallyLongMetric = "All work and no play makes Jack a dull boy".repeat(1000) - executionContext.attributes[aws.smithy.kotlin.runtime.businessmetrics.BusinessMetrics] = mutableSetOf( - "A".repeat(BUSINESS_METRICS_MAX_LENGTH), + executionContext.attributes.emitBusinessMetric( + object : BusinessMetric { + override val identifier: String = reallyLongMetric + }, ) val interceptor = BusinessMetricsInterceptor() diff --git a/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsRuntimeTypes.kt b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsRuntimeTypes.kt index b4debfc4bd8..2246bdd13f7 100644 --- a/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsRuntimeTypes.kt +++ b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsRuntimeTypes.kt @@ -60,8 +60,11 @@ object AwsRuntimeTypes { object Interceptors : RuntimeTypePackage(AwsKotlinDependency.AWS_HTTP, "interceptors") { val AddUserAgentMetadataInterceptor = symbol("AddUserAgentMetadataInterceptor") val UnsupportedSigningAlgorithmInterceptor = symbol("UnsupportedSigningAlgorithmInterceptor") - val BusinessMetricsInterceptor = symbol("BusinessMetricsInterceptor") - val AwsBusinessMetric = symbol("AwsBusinessMetric") + + object BusinessMetrics : RuntimeTypePackage(AwsKotlinDependency.AWS_HTTP, "interceptors.businessmetrics") { + val BusinessMetricsInterceptor = symbol("BusinessMetricsInterceptor") + val AwsBusinessMetric = symbol("AwsBusinessMetric") + } } object Retries { diff --git a/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/businessmetrics/BusinessMetricsIntegration.kt b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/businessmetrics/BusinessMetricsIntegration.kt new file mode 100644 index 00000000000..2a64d1a96bd --- /dev/null +++ b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/businessmetrics/BusinessMetricsIntegration.kt @@ -0,0 +1,32 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.codegen.businessmetrics + +import aws.sdk.kotlin.codegen.AwsRuntimeTypes +import software.amazon.smithy.kotlin.codegen.core.KotlinWriter +import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration +import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator +import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware +import software.amazon.smithy.model.shapes.OperationShape + +/** + * Renders the addition of the [aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.BusinessMetricsInterceptor] + */ +class BusinessMetricsInterceptorIntegration : KotlinIntegration { + override fun customizeMiddleware( + ctx: ProtocolGenerator.GenerationContext, + resolved: List, + ): List = resolved + userAgentBusinessMetricsMiddleware + + private val userAgentBusinessMetricsMiddleware = object : ProtocolMiddleware { + override val name: String = "UserAgentBusinessMetrics" + override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) { + writer.write( + "op.interceptors.add(#T())", + AwsRuntimeTypes.Http.Interceptors.BusinessMetrics.BusinessMetricsInterceptor, + ) + } + } +} diff --git a/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/businessmetrics/CredentialsBusinessMetricsIntegration.kt b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/businessmetrics/CredentialsBusinessMetricsIntegration.kt new file mode 100644 index 00000000000..d59a029ced2 --- /dev/null +++ b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/businessmetrics/CredentialsBusinessMetricsIntegration.kt @@ -0,0 +1,37 @@ +package aws.sdk.kotlin.codegen.businessmetrics + +import aws.sdk.kotlin.codegen.AwsRuntimeTypes +import software.amazon.smithy.kotlin.codegen.core.KotlinWriter +import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes +import software.amazon.smithy.kotlin.codegen.core.withBlock +import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration +import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator +import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware +import software.amazon.smithy.model.shapes.OperationShape + +/** + * Renders the addition of some of the credentials related business metrics. + */ +class CredentialsBusinessMetricsIntegration : KotlinIntegration { + override fun customizeMiddleware( + ctx: ProtocolGenerator.GenerationContext, + resolved: List, + ): List = resolved + credentialsBusinessMetricsMiddleware + + private val credentialsBusinessMetricsMiddleware = object : ProtocolMiddleware { + override val name: String = "credentialsOverrideBusinessMetricsMiddleware" + override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) { + writer.withBlock( + "if (config.credentialsProvider is #T) {", + "}", + AwsRuntimeTypes.Config.Credentials.StaticCredentialsProvider, + ) { + write( + "op.context.#T(#T.Credentials.CREDENTIALS_CODE)", + RuntimeTypes.Core.BusinessMetrics.emitBusinessMetric, + AwsRuntimeTypes.Http.Interceptors.BusinessMetrics.AwsBusinessMetric, + ) + } + } + } +} diff --git a/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/BusinessMetricsIntegration.kt b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/businessmetrics/EndpointBusinessMetricsIntegration.kt similarity index 57% rename from codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/BusinessMetricsIntegration.kt rename to codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/businessmetrics/EndpointBusinessMetricsIntegration.kt index 73575ddf075..5150c8d7167 100644 --- a/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/BusinessMetricsIntegration.kt +++ b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/businessmetrics/EndpointBusinessMetricsIntegration.kt @@ -1,27 +1,17 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. - * SPDX-License-Identifier: Apache-2.0 - */ -package aws.sdk.kotlin.codegen +package aws.sdk.kotlin.codegen.businessmetrics -import software.amazon.smithy.kotlin.codegen.core.KotlinWriter +import aws.sdk.kotlin.codegen.AwsRuntimeTypes import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes.Auth.Signing.AwsSigningCommon.AwsSigningAttributes import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration import software.amazon.smithy.kotlin.codegen.integration.SectionWriter import software.amazon.smithy.kotlin.codegen.integration.SectionWriterBinding import software.amazon.smithy.kotlin.codegen.rendering.endpoints.EndpointBusinessMetrics -import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator -import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware -import software.amazon.smithy.model.shapes.OperationShape /** - * Renders the addition of the [BusinessMetricsInterceptor] and endpoint business metrics emitters + * Renders the addition of endpoint & endpoint adjacent business metrics. */ -class BusinessMetricsIntegration : KotlinIntegration { - override val order: Byte - get() = super.order - +class EndpointBusinessMetricsIntegration : KotlinIntegration { override val sectionWriters: List get() = listOf( SectionWriterBinding(EndpointBusinessMetrics, endpointBusinessMetricsSectionWriter), @@ -46,23 +36,8 @@ class BusinessMetricsIntegration : KotlinIntegration { AwsSigningAttributes, AwsSigningAttributes, RuntimeTypes.Core.BusinessMetrics.emitBusinessMetric, - AwsRuntimeTypes.Http.Interceptors.AwsBusinessMetric, + AwsRuntimeTypes.Http.Interceptors.BusinessMetrics.AwsBusinessMetric, ) writer.write("") } - - override fun customizeMiddleware( - ctx: ProtocolGenerator.GenerationContext, - resolved: List, - ): List = resolved + userAgentBusinessMetricsMiddleware - - private val userAgentBusinessMetricsMiddleware = object : ProtocolMiddleware { - override val name: String = "UserAgentBusinessMetrics" - override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) { - writer.write( - "op.interceptors.add(#T())", - AwsRuntimeTypes.Http.Interceptors.BusinessMetricsInterceptor, - ) - } - } } diff --git a/codegen/aws-sdk-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration b/codegen/aws-sdk-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration index fba8df4f45e..288fad2a34a 100644 --- a/codegen/aws-sdk-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration +++ b/codegen/aws-sdk-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration @@ -42,5 +42,7 @@ aws.sdk.kotlin.codegen.customization.cloudfrontkeyvaluestore.BackfillSigV4ACusto aws.sdk.kotlin.codegen.customization.s3.express.SigV4S3ExpressAuthSchemeIntegration aws.sdk.kotlin.codegen.customization.s3.express.S3ExpressIntegration aws.sdk.kotlin.codegen.customization.s3.S3ExpiresIntegration -aws.sdk.kotlin.codegen.BusinessMetricsIntegration +aws.sdk.kotlin.codegen.businessmetrics.BusinessMetricsInterceptorIntegration +aws.sdk.kotlin.codegen.businessmetrics.CredentialsBusinessMetricsIntegration +aws.sdk.kotlin.codegen.businessmetrics.EndpointBusinessMetricsIntegration aws.sdk.kotlin.codegen.smoketests.SmokeTestsDenyListIntegration diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2084e480fbc..76a3ee1acbe 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,8 +9,8 @@ coroutines-version = "1.9.0" atomicfu-version = "0.24.0" # smithy-kotlin codegen and runtime are versioned separately -smithy-kotlin-runtime-version = "1.3.14" -smithy-kotlin-codegen-version = "0.33.14" +smithy-kotlin-runtime-version = "1.3.17-SNAPSHOT" +smithy-kotlin-codegen-version = "0.33.17-SNAPSHOT" # codegen smithy-version = "1.50.0"