All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.aws.sdk.kotlin.runtime.auth.credentials.ProfileCredentialsProvider.kt Maven / Gradle / Ivy

There is a newer version: 1.3.76
Show newest version
/*
 * 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.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.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.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.collections.Attributes
import aws.smithy.kotlin.runtime.http.engine.HttpClientEngine
import aws.smithy.kotlin.runtime.io.closeIfCloseable
import aws.smithy.kotlin.runtime.telemetry.logging.logger
import aws.smithy.kotlin.runtime.time.TimestampFormat
import aws.smithy.kotlin.runtime.util.LazyAsyncValue
import aws.smithy.kotlin.runtime.util.PlatformProvider
import aws.smithy.kotlin.runtime.util.asyncLazy
import kotlin.coroutines.coroutineContext

/**
 * A [CredentialsProvider] that gets credentials from a profile in `~/.aws/config` or the shared credentials
 * file `~/.aws/credentials`. The locations of these files are configurable via environment or system property on
 * the JVM (see [AwsSdkSetting.AwsConfigFile] and [AwsSdkSetting.AwsSharedCredentialsFile]).
 *
 * This provider is part of the [DefaultChainCredentialsProvider] and usually consumed through that provider. However,
 * it can be instantiated and used standalone as well.
 *
 * NOTE: This provider does not implement any caching. It will reload and reparse the profile from the file system
 * when called. Use [CachedCredentialsProvider] to decorate the profile provider to get caching behavior.
 *
 * This provider supports several credentials formats:
 *
 * ### Credentials defined explicitly within the file
 * ```ini
 * [default]
 * aws_access_key_id = my-access-key
 * aws_secret_access_key = my-secret
 * ```
 *
 * ### Assumed role credentials loaded from a credential source
 * ```ini
 * [default]
 * role_arn = arn:aws:iam:123456789:role/RoleA
 * credential_source = Environment
 * ```
 *
 * ### Assumed role credentials from a source profile
 * ```ini
 * [default]
 * role_arn = arn:aws:iam:123456789:role/RoleA
 * source_profile = base
 *
 * [profile base]
 * aws_access_key_id = my-access-key
 * aws_secret_access_key = my-secret
 * ```
 *
 * Other more complex configurations are possible. See the [Configuration and credential file settings](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html)
 * documentation provided by the AWS CLI.
 *
 * @param profileName Override the profile name to use. If not provided it will be resolved internally
 * via environment (see [AwsSdkSetting.AwsProfile]) or defaulted to `default` if not configured.
 * @param region The AWS region to use, this will be resolved internally if not provided.
 * @param platformProvider The platform API provider
 * @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.
 * @param configurationSource An optional configuration source to use for loading shared config. If not provided,
 * it will be resolved from the environment.
 */
public class ProfileCredentialsProvider @InternalSdkApi constructor(
    public val profileName: String? = null,
    public val region: String? = null,
    public val platformProvider: PlatformProvider = PlatformProvider.System,
    public val httpClient: HttpClientEngine? = null,
    public val configurationSource: AwsConfigurationSource? = null,
) : CloseableCredentialsProvider {

    public constructor(
        profileName: String? = null,
        region: String? = null,
        platformProvider: PlatformProvider = PlatformProvider.System,
        httpClient: HttpClientEngine? = null,
    ) : this (
        profileName,
        region,
        platformProvider,
        httpClient,
        null,
    )

    private val namedProviders = mapOf(
        "Environment" to EnvironmentCredentialsProvider(platformProvider::getenv),
        "Ec2InstanceMetadata" to ImdsCredentialsProvider(
            profileOverride = profileName,
            client = lazy {
                ImdsClient {
                    platformProvider = [email protected]
                    engine = httpClient
                }
            },
            platformProvider = platformProvider,
        ),
        "EcsContainer" to EcsCredentialsProvider(platformProvider, httpClient),
    )

    override suspend fun resolve(attributes: Attributes): Credentials {
        val logger = coroutineContext.logger()
        val sharedConfig = loadAwsSharedConfig(platformProvider, profileName, configurationSource)
        logger.debug { "Loading credentials from profile `${sharedConfig.activeProfile.name}`" }
        val chain = ProfileChain.resolve(sharedConfig)

        // if profile is overridden for this provider, attempt to resolve it from there first
        val profileOverride = profileName?.let { sharedConfig.profiles[it] }
        val region = asyncLazy { region ?: profileOverride?.getOrNull("region") ?: attributes.getOrNull(AwsClientOption.Region) ?: resolveRegion(platformProvider) }

        val leaf = chain.leaf.toCredentialsProvider(region)
        logger.debug { "Resolving credentials from ${chain.leaf.description()}" }
        var creds = leaf.resolve(attributes)

        chain.roles.forEach { roleArn ->
            logger.debug { "Assuming role `${roleArn.roleArn}`" }
            val assumeProvider = roleArn.toCredentialsProvider(creds, region)
            creds = assumeProvider.resolve(attributes)
        }

        logger.debug { "Obtained credentials from profile; expiration=${creds.expiration?.format(TimestampFormat.ISO_8601)}" }
        return creds
    }

    override fun close() {
        namedProviders.forEach { entry ->
            entry.value.closeIfCloseable()
        }
    }

    private suspend fun LeafProvider.toCredentialsProvider(region: LazyAsyncValue): CredentialsProvider =
        when (this) {
            is LeafProvider.NamedSource -> namedProviders[name]
                ?: throw ProviderConfigurationException("unknown credentials source: $name")

            is LeafProvider.AccessKey -> StaticCredentialsProvider(credentials)

            is LeafProvider.WebIdentityTokenRole -> StsWebIdentityCredentialsProvider(
                roleArn,
                webIdentityTokenFile,
                region = region.get(),
                roleSessionName = sessionName,
                platformProvider = platformProvider,
                httpClient = httpClient,
            )

            is LeafProvider.SsoSession -> SsoCredentialsProvider(
                accountId = ssoAccountId,
                roleName = ssoRoleName,
                startUrl = ssoStartUrl,
                ssoRegion = ssoRegion,
                ssoSessionName = ssoSessionName,
                httpClient = httpClient,
                platformProvider = platformProvider,
            )

            is LeafProvider.LegacySso -> SsoCredentialsProvider(
                accountId = ssoAccountId,
                roleName = ssoRoleName,
                startUrl = ssoStartUrl,
                ssoRegion = ssoRegion,
                httpClient = httpClient,
                platformProvider = platformProvider,
            )

            is LeafProvider.Process -> ProcessCredentialsProvider(command)
        }

    private suspend fun RoleArn.toCredentialsProvider(
        creds: Credentials,
        region: LazyAsyncValue,
    ): CredentialsProvider = StsAssumeRoleCredentialsProvider(
        bootstrapCredentialsProvider = StaticCredentialsProvider(creds),
        roleArn = roleArn,
        region = region.get(),
        roleSessionName = sessionName,
        externalId = externalId,
        httpClient = httpClient,
    )

    private fun LeafProvider.description(): String = when (this) {
        is LeafProvider.NamedSource -> "named source $name"
        is LeafProvider.AccessKey -> "static credentials"
        is LeafProvider.WebIdentityTokenRole -> "web identity token"
        is LeafProvider.SsoSession -> "single sign-on (session)"
        is LeafProvider.LegacySso -> "single sign-on (legacy)"
        is LeafProvider.Process -> "process"
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy