
commonMain.aws.sdk.kotlin.runtime.auth.credentials.StsAssumeRoleCredentialsProvider.kt Maven / Gradle / Ivy
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.sdk.kotlin.runtime.auth.credentials
import aws.sdk.kotlin.runtime.auth.credentials.internal.sts.StsClient
import aws.sdk.kotlin.runtime.auth.credentials.internal.sts.assumeRole
import aws.sdk.kotlin.runtime.auth.credentials.internal.sts.model.RegionDisabledException
import aws.sdk.kotlin.runtime.config.AwsSdkSetting
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.DEFAULT_CREDENTIALS_REFRESH_SECONDS
import aws.smithy.kotlin.runtime.config.resolve
import aws.smithy.kotlin.runtime.http.engine.HttpClientEngine
import aws.smithy.kotlin.runtime.telemetry.logging.logger
import aws.smithy.kotlin.runtime.telemetry.telemetryProvider
import aws.smithy.kotlin.runtime.time.Instant
import aws.smithy.kotlin.runtime.time.TimestampFormat
import aws.smithy.kotlin.runtime.time.epochMilliseconds
import aws.smithy.kotlin.runtime.util.Attributes
import aws.smithy.kotlin.runtime.util.PlatformEnvironProvider
import aws.smithy.kotlin.runtime.util.PlatformProvider
import kotlin.coroutines.coroutineContext
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
private const val GLOBAL_STS_PARTITION_ENDPOINT = "aws-global"
private const val PROVIDER_NAME = "AssumeRoleProvider"
/**
* A [CredentialsProvider] that uses another provider to assume a role from the AWS Security Token Service (STS).
*
* When asked to provide credentials, this provider will first invoke the inner credentials provider
* to get AWS credentials for STS. Then, it will call STS to get assumed credentials for the desired role.
*
* @param credentialsProvider The underlying provider to use for source credentials
* @param roleArn The ARN of the target role to assume, e.g. `arn:aws:iam:123456789:role/example`
* @param region The AWS region to assume the role in. If not set then the global STS endpoint will be used.
* @param roleSessionName The name to associate with the session. Use the role session name to uniquely identify a session
* when the same role is assumed by different principals or for different reasons. In cross-account scenarios, the
* role session name is visible to, and can be logged by the account that owns the role. The role session name is also
* in the ARN of the assumed role principal.
* @param externalId A unique identifier that might be required when you assume a role in another account. If the
* administrator of the account to which the role belongs provided you with an external ID, then provide that value
* in this parameter.
* @param duration The expiry duration of the STS credentials. Defaults to 15 minutes if not set.
* @param httpClient the [HttpClientEngine] instance to use to make requests. NOTE: This engine's resources and lifetime
* are NOT managed by the provider. Caller is responsible for closing.
*/
public class StsAssumeRoleCredentialsProvider(
private val credentialsProvider: CredentialsProvider,
private val roleArn: String,
private val region: String? = null,
private val roleSessionName: String? = null,
private val externalId: String? = null,
private val duration: Duration = DEFAULT_CREDENTIALS_REFRESH_SECONDS.seconds,
private val httpClient: HttpClientEngine? = null,
) : CredentialsProvider {
override suspend fun resolve(attributes: Attributes): Credentials {
val logger = coroutineContext.logger()
logger.debug { "retrieving assumed credentials" }
// NOTE: multi region access points require regional STS endpoints
val provider = this
val telemetry = coroutineContext.telemetryProvider
val client = StsClient {
region = provider.region ?: GLOBAL_STS_PARTITION_ENDPOINT
credentialsProvider = provider.credentialsProvider
httpClient = provider.httpClient
telemetryProvider = telemetry
}
val resp = try {
client.assumeRole {
roleArn = provider.roleArn
externalId = provider.externalId
roleSessionName = provider.roleSessionName ?: defaultSessionName()
durationSeconds = provider.duration.inWholeSeconds.toInt()
}
} catch (ex: Exception) {
logger.debug { "sts refused to grant assumed role credentials" }
when (ex) {
is RegionDisabledException -> throw ProviderConfigurationException(
"STS is not activated in the requested region (${client.config.region}). Please check your configuration and activate STS in the target region if necessary",
ex,
)
else -> throw CredentialsProviderException("failed to assume role from STS", ex)
}
} finally {
client.close()
}
val roleCredentials = resp.credentials ?: throw CredentialsProviderException("STS credentials must not be null")
logger.debug { "obtained assumed credentials; expiration=${roleCredentials.expiration?.format(TimestampFormat.ISO_8601)}" }
return Credentials(
accessKeyId = checkNotNull(roleCredentials.accessKeyId) { "Expected accessKeyId in STS assumeRole response" },
secretAccessKey = checkNotNull(roleCredentials.secretAccessKey) { "Expected secretAccessKey in STS assumeRole response" },
sessionToken = roleCredentials.sessionToken,
expiration = roleCredentials.expiration,
providerName = PROVIDER_NAME,
)
}
}
// role session name must be provided to assume a role, when the user doesn't provide one we choose a name for them
internal fun defaultSessionName(platformEnvironProvider: PlatformEnvironProvider = PlatformProvider.System): String =
AwsSdkSetting.AwsRoleSessionName.resolve(platformEnvironProvider) ?: "aws-sdk-kotlin-${Instant.now().epochMilliseconds}"
© 2015 - 2025 Weber Informatics LLC | Privacy Policy