Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions aws-runtime/aws-config/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ description = "Support for AWS configuration"
extra["moduleName"] = "aws.sdk.kotlin.runtime.config"

val smithyKotlinVersion: String by project
val crtKotlinVersion: String by project

val kotestVersion: String by project

kotlin {
Expand All @@ -32,10 +30,6 @@ kotlin {
implementation("aws.smithy.kotlin:serde-json:$smithyKotlinVersion")


// credential providers
implementation("aws.sdk.kotlin.crt:aws-crt-kotlin:$crtKotlinVersion")
implementation(project(":aws-runtime:crt-util"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Can any code be removed from crt-util now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe haven't checked. A lot of crt-util supports signing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see anything that should be removed at a glance


// additional dependencies required by generated sts provider
implementation("aws.smithy.kotlin:serde-form-url:$smithyKotlinVersion")
implementation("aws.smithy.kotlin:serde-xml:$smithyKotlinVersion")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package aws.sdk.kotlin.runtime.auth.credentials

import aws.sdk.kotlin.runtime.config.CachedValue
import aws.sdk.kotlin.runtime.config.ExpiringValue
import aws.smithy.kotlin.runtime.io.Closeable
import aws.smithy.kotlin.runtime.logging.Logger
import aws.smithy.kotlin.runtime.time.Clock
import kotlin.time.Duration
Expand Down Expand Up @@ -46,7 +47,7 @@ public class CachedCredentialsProvider(
private val expireCredentialsAfter: Duration = Duration.seconds(DEFAULT_CREDENTIALS_REFRESH_SECONDS),
refreshBufferWindow: Duration = Duration.seconds(DEFAULT_CREDENTIALS_REFRESH_BUFFER_SECONDS),
private val clock: Clock = Clock.System
) : CredentialsProvider {
) : CredentialsProvider, Closeable {

private var cachedCredentials = CachedValue<Credentials>(null, bufferTime = refreshBufferWindow, clock)

Expand All @@ -62,4 +63,8 @@ public class CachedCredentialsProvider(
ExpiringValue(creds, expiration)
}
}

override fun close() {
(source as? Closeable)?.close()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* 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.smithy.kotlin.runtime.io.Closeable
import aws.smithy.kotlin.runtime.logging.Logger

// TODO - support caching the provider that actually resolved credentials such that future calls don't involve going through the full chain

/**
* Composite [CredentialsProvider] that delegates to a chain of providers. When asked for credentials [providers]
* are consulted in the order given until one succeeds. If none of the providers in the chain can provide credentials
* then this class will throw an exception. The exception will include the providers tried in the message. Each
* individual exception is available as a suppressed exception.
*
* @param providers the list of providers to delegate to
*/
public open class CredentialsProviderChain(
protected vararg val providers: CredentialsProvider
) : CredentialsProvider, Closeable {
private val logger = Logger.getLogger<CredentialsProviderChain>()

init {
require(providers.isNotEmpty()) { "at least one provider must be in the chain" }
}

override fun toString(): String =
(listOf(this) + providers).map { it::class.simpleName }.joinToString(" -> ")

override suspend fun getCredentials(): Credentials {
val chainException = lazy { CredentialsProviderException("No credentials could be loaded from the chain: $this") }
for (provider in providers) {
try {
return provider.getCredentials()
} catch (ex: Exception) {
logger.debug { "unable to load credentials from $provider: ${ex.message}" }
chainException.value.addSuppressed(ex)
}
}

throw chainException.value
}

override fun close() {
val exceptions = providers.mapNotNull {
try {
(it as? Closeable)?.close()
null
} catch (ex: Exception) {
ex
}
}
if (exceptions.isNotEmpty()) {
val ex = exceptions.first()
exceptions.drop(1).forEach(ex::addSuppressed)
throw ex
}
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,83 @@

package aws.sdk.kotlin.runtime.auth.credentials

import aws.sdk.kotlin.crt.auth.credentials.build
import aws.sdk.kotlin.runtime.crt.SdkDefaultIO
import aws.sdk.kotlin.crt.auth.credentials.DefaultChainCredentialsProvider as DefaultChainCredentialsProviderCrt
import aws.sdk.kotlin.runtime.config.imds.ImdsClient
import aws.sdk.kotlin.runtime.http.engine.crt.CrtHttpEngine
import aws.smithy.kotlin.runtime.http.engine.HttpClientEngine
import aws.smithy.kotlin.runtime.io.Closeable
import aws.smithy.kotlin.runtime.util.Platform
import aws.smithy.kotlin.runtime.util.PlatformProvider
import kotlin.time.ExperimentalTime

// TODO - allow region, profile, etc to be passed in

/**
* Creates the default provider chain used by most AWS SDKs.
* Default AWS credential provider chain used by most AWS SDKs.
*
* Resolution order:
*
* Generally:
* 1. Environment variables ([EnvironmentCredentialsProvider])
* 2. Profile ([ProfileCredentialsProvider])
* 3. Web Identity Tokens ([StsWebIdentityCredentialsProvider]]
* 4. ECS (IAM roles for tasks) ([EcsCredentialsProvider])
* 5. EC2 Instance Metadata (IMDSv2) ([ImdsCredentialsProvider])
*
* (1) Environment
* (2) Profile
* (3) (conditional, off by default) ECS
* (4) (conditional, on by default) EC2 Instance Metadata
* The chain is decorated with a [CachedCredentialsProvider].
*
* Support for environmental control of the default provider chain is not yet implemented.
* Closing the chain will close all child providers that implement [Closeable].
*
* @return the newly-constructed credentials provider
*/
public class DefaultChainCredentialsProvider : CrtCredentialsProvider {
override val crtProvider: DefaultChainCredentialsProviderCrt by lazy {
DefaultChainCredentialsProviderCrt.build {
clientBootstrap = SdkDefaultIO.ClientBootstrap
public class DefaultChainCredentialsProvider internal constructor(
private val platformProvider: PlatformProvider = Platform,
httpClientEngine: HttpClientEngine? = null
) : CredentialsProvider, Closeable {

public constructor() : this(Platform)

private val manageEngine = httpClientEngine == null
private val httpClientEngine = httpClientEngine ?: CrtHttpEngine()

private val chain = CredentialsProviderChain(
EnvironmentCredentialsProvider(platformProvider::getenv),
ProfileCredentialsProvider(platformProvider = platformProvider, httpClientEngine = httpClientEngine),
// STS web identity provider can be constructed from either the profile OR 100% from the environment
StsWebIdentityProvider(platformProvider = platformProvider, httpClientEngine = httpClientEngine),
EcsCredentialsProvider(platformProvider, httpClientEngine),
ImdsCredentialsProvider(
client = lazy {
ImdsClient {
platformProvider = this@DefaultChainCredentialsProvider.platformProvider
engine = httpClientEngine
}
},
platformProvider = platformProvider
)
)

private val provider = CachedCredentialsProvider(chain)

override suspend fun getCredentials(): Credentials = provider.getCredentials()

override fun close() {
provider.close()
if (manageEngine) {
httpClientEngine.close()
}
}
}

/**
* Wrapper around [StsWebIdentityCredentialsProvider] that delays any exceptions until [getCredentials] 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.
*/
@OptIn(ExperimentalTime::class)
private class StsWebIdentityProvider(
val platformProvider: PlatformProvider,
val httpClientEngine: HttpClientEngine? = null
) : CredentialsProvider {
override suspend fun getCredentials(): Credentials {
val wrapped = StsWebIdentityCredentialsProvider.fromEnvironment(platformProvider = platformProvider, httpClientEngine = httpClientEngine)
return wrapped.getCredentials()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ private const val PROVIDER_NAME = "EcsContainer"
*
* For more information on configuring ECS credentials see [IAM Roles for tasks](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html)
*
* @param platform the platform provider
* @param platformProvider the platform provider
* @param httpClientEngine 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 EcsCredentialsProvider internal constructor(
private val platform: PlatformEnvironProvider,
private val platformProvider: PlatformEnvironProvider,
httpClientEngine: HttpClientEngine? = null
) : CredentialsProvider, Closeable {

Expand All @@ -73,9 +73,9 @@ public class EcsCredentialsProvider internal constructor(

override suspend fun getCredentials(): Credentials {
val logger = Logger.getLogger<EcsCredentialsProvider>()
val authToken = AwsSdkSetting.AwsContainerAuthorizationToken.resolve(platform)
val relativeUri = AwsSdkSetting.AwsContainerCredentialsRelativeUri.resolve(platform)
val fullUri = AwsSdkSetting.AwsContainerCredentialsFullUri.resolve(platform)
val authToken = AwsSdkSetting.AwsContainerAuthorizationToken.resolve(platformProvider)
val relativeUri = AwsSdkSetting.AwsContainerCredentialsRelativeUri.resolve(platformProvider)
val fullUri = AwsSdkSetting.AwsContainerCredentialsFullUri.resolve(platformProvider)

val url = when {
relativeUri?.isBlank() == false -> validateRelativeUri(relativeUri)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ private const val PROVIDER_NAME = "IMDSv2"
* @param profileOverride override the instance profile name. When retrieving credentials, a call must first be made to
* `<IMDS_BASE_URL>/latest/meta-data/iam/security-credentials`. This returns the instance profile used. If
* [profileOverride] is set, the initial call to retrieve the profile is skipped and the provided value is used instead.
* @param client the IMDS client to use to resolve credentials information with
* @param client the IMDS client to use to resolve credentials information with. This provider takes ownership over
* the lifetime of the given [ImdsClient] and will close it when the provider is closed.
* @param platformProvider the [PlatformEnvironProvider] instance
*/
public class ImdsCredentialsProvider(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,42 +64,42 @@ import aws.smithy.kotlin.runtime.util.PlatformProvider
* @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 platform The platform API provider
* @param platformProvider The platform API provider
* @param httpClientEngine 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 ProfileCredentialsProvider(
private val profileName: String? = null,
private val region: String? = null,
private val platform: PlatformProvider = Platform,
private val platformProvider: PlatformProvider = Platform,
private val httpClientEngine: HttpClientEngine? = null,
) : CredentialsProvider, Closeable {

private val namedProviders = mapOf(
"Environment" to EnvironmentCredentialsProvider(platform::getenv),
"Environment" to EnvironmentCredentialsProvider(platformProvider::getenv),
"Ec2InstanceMetadata" to ImdsCredentialsProvider(
profileOverride = profileName,
client = lazy {
ImdsClient {
platformProvider = platform
platformProvider = this@ProfileCredentialsProvider.platformProvider
engine = httpClientEngine
}
},
platformProvider = platform
platformProvider = platformProvider
),
"EcsContainer" to EcsCredentialsProvider(platform, httpClientEngine)
"EcsContainer" to EcsCredentialsProvider(platformProvider, httpClientEngine)
)

override suspend fun getCredentials(): Credentials {
val logger = Logger.getLogger<ProfileCredentialsProvider>()
val source = resolveConfigSource(platform, profileName)
val source = resolveConfigSource(platformProvider, profileName)
logger.debug { "Loading credentials from profile `${source.profile}`" }
val profiles = loadAwsProfiles(platform, source)
val profiles = loadAwsProfiles(platformProvider, source)
val chain = ProfileChain.resolve(profiles, source.profile)

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

val leaf = chain.leaf.toCredentialsProvider(region)
logger.debug { "Resolving credentials from ${chain.leaf.description()}" }
Expand Down Expand Up @@ -129,7 +129,7 @@ public class ProfileCredentialsProvider(
webIdentityTokenFile,
region = region,
roleSessionName = sessionName,
platformProvider = platform,
platformProvider = platformProvider,
httpClientEngine = httpClientEngine
)
is LeafProvider.Sso -> SsoCredentialsProvider(
Expand All @@ -138,7 +138,7 @@ public class ProfileCredentialsProvider(
startUrl = ssoStartUrl,
ssoRegion = ssoRegion,
httpClientEngine = httpClientEngine,
platformProvider = platform
platformProvider = platformProvider
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import aws.smithy.kotlin.runtime.http.engine.HttpClientEngine
import aws.smithy.kotlin.runtime.serde.json.*
import aws.smithy.kotlin.runtime.time.Clock
import aws.smithy.kotlin.runtime.time.Instant
import aws.smithy.kotlin.runtime.time.fromEpochMilliseconds
import aws.smithy.kotlin.runtime.util.*

private const val PROVIDER_NAME = "SSO"
Expand Down Expand Up @@ -116,7 +117,7 @@ public class SsoCredentialsProvider public constructor(
accessKeyId = checkNotNull(roleCredentials.accessKeyId) { "Expected accessKeyId in SSO roleCredentials response" },
secretAccessKey = checkNotNull(roleCredentials.secretAccessKey) { "Expected secretAccessKey in SSO roleCredentials response" },
sessionToken = roleCredentials.sessionToken,
expiration = Instant.fromEpochSeconds(roleCredentials.expiration),
expiration = Instant.fromEpochMilliseconds(roleCredentials.expiration),
PROVIDER_NAME
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class AwsClientConfigLoadOptions {
public var sdkLogMode: SdkLogMode = SdkLogMode.Default

// FIXME - expose profile name override and thread through region/cred provider chains
// FIXME - expose httpClientEngine and thread through region/cred provider chains
}

/**
Expand Down
Loading