Skip to content

Commit ba47048

Browse files
aajtoddianbotsf
andauthored
feat(rt)!: add sso credential provider; make all providers kmp compatible (#469)
Refactor credential providers to remove CRT dependency and make them KMP compatible. Added SSO provider to default chain. Lots of misc cleanup and improvements. * feat(rt): standalone sso credentials provider (#462) * refactor(rt)!: generated sts and sts web identity credential providers (#470) * refactor(rt)!: implement kmp ecs provider (#475) * feat(rt)!: implement kmp profile credentials provider (#478) * feat(rt)!: kmp default credentials provider chain (#491) * fix: work around machine-specific Gradle bug with aws-config variants (#496) * fix: credentials provider ownership (#498) Co-authored-by: Ian Botsford <[email protected]>
1 parent 2c8eb00 commit ba47048

File tree

155 files changed

+11505
-481
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

155 files changed

+11505
-481
lines changed

.github/workflows/continuous-integration.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ on:
44
push:
55
branches: [ main ]
66
pull_request:
7-
branches: [ main ]
7+
branches:
8+
- main
9+
- 'feat-*'
810
workflow_dispatch:
911

1012
env:

aws-runtime/aws-config/build.gradle.kts

Lines changed: 137 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,25 @@
22
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
33
* SPDX-License-Identifier: Apache-2.0.
44
*/
5+
import aws.sdk.kotlin.gradle.codegen.dsl.smithyKotlinPlugin
6+
7+
plugins {
8+
id("aws.sdk.kotlin.codegen")
9+
}
510

611
description = "Support for AWS configuration"
712
extra["moduleName"] = "aws.sdk.kotlin.runtime.config"
813

9-
val coroutinesVersion: String by project
10-
val crtKotlinVersion: String by project
11-
val kotestVersion: String by project
1214
val smithyKotlinVersion: String by project
15+
val kotestVersion: String by project
16+
val coroutinesVersion: String by project
1317

1418
kotlin {
1519
sourceSets {
1620
commonMain {
1721
dependencies {
1822
api(project(":aws-runtime:aws-core"))
1923
api(project(":aws-runtime:aws-types"))
20-
2124
implementation("aws.smithy.kotlin:logging:$smithyKotlinVersion")
2225
implementation("aws.smithy.kotlin:http:$smithyKotlinVersion")
2326
implementation("aws.smithy.kotlin:utils:$smithyKotlinVersion")
@@ -27,9 +30,16 @@ kotlin {
2730
// parsing common JSON credentials responses
2831
implementation("aws.smithy.kotlin:serde-json:$smithyKotlinVersion")
2932

30-
// credential providers
31-
implementation("aws.sdk.kotlin.crt:aws-crt-kotlin:$crtKotlinVersion")
32-
implementation(project(":aws-runtime:crt-util"))
33+
34+
// additional dependencies required by generated sts provider
35+
implementation("aws.smithy.kotlin:serde-form-url:$smithyKotlinVersion")
36+
implementation("aws.smithy.kotlin:serde-xml:$smithyKotlinVersion")
37+
implementation(project(":aws-runtime:protocols:aws-xml-protocols"))
38+
implementation(project(":aws-runtime:aws-endpoint"))
39+
implementation(project(":aws-runtime:aws-signing"))
40+
41+
// additional dependencies required by generated sso provider
42+
implementation(project(":aws-runtime:protocols:aws-json-protocols"))
3343
}
3444
}
3545
commonTest {
@@ -55,3 +65,123 @@ kotlin {
5565
}
5666
}
5767
}
68+
69+
fun awsModelFile(name: String): String =
70+
rootProject.file("codegen/sdk/aws-models/$name").absolutePath
71+
72+
codegen {
73+
val basePackage = "aws.sdk.kotlin.runtime.auth.credentials.internal"
74+
75+
projections {
76+
77+
// generate an sts client
78+
create("sts-credentials-provider") {
79+
imports = listOf(
80+
awsModelFile("sts.2011-06-15.json")
81+
)
82+
83+
smithyKotlinPlugin {
84+
serviceShapeId = "com.amazonaws.sts#AWSSecurityTokenServiceV20110615"
85+
packageName = "${basePackage}.sts"
86+
packageVersion = project.version.toString()
87+
packageDescription = "Internal STS credentials provider"
88+
sdkId = "STS"
89+
buildSettings {
90+
generateDefaultBuildFiles = false
91+
generateFullProject = false
92+
}
93+
}
94+
95+
// TODO - could we add a trait such that we change visibility to `internal` or a build setting...?
96+
transforms = listOf(
97+
"""
98+
{
99+
"name": "awsSdkKotlinIncludeOperations",
100+
"args": {
101+
"operations": [
102+
"com.amazonaws.sts#AssumeRole",
103+
"com.amazonaws.sts#AssumeRoleWithWebIdentity"
104+
]
105+
}
106+
}
107+
"""
108+
)
109+
}
110+
111+
// generate an sso client
112+
create("sso-credentials-provider") {
113+
imports = listOf(
114+
awsModelFile("sso.2019-06-10.json")
115+
)
116+
117+
val serviceShape = "com.amazonaws.sso#SWBPortalService"
118+
smithyKotlinPlugin {
119+
serviceShapeId = serviceShape
120+
packageName = "${basePackage}.sso"
121+
packageVersion = project.version.toString()
122+
packageDescription = "Internal SSO credentials provider"
123+
sdkId = "SSO"
124+
buildSettings {
125+
generateDefaultBuildFiles = false
126+
generateFullProject = false
127+
}
128+
}
129+
130+
transforms = listOf(
131+
"""
132+
{
133+
"name": "awsSdkKotlinIncludeOperations",
134+
"args": {
135+
"operations": [
136+
"com.amazonaws.sso#GetRoleCredentials"
137+
]
138+
}
139+
}
140+
"""
141+
)
142+
}
143+
}
144+
}
145+
146+
/*
147+
NOTE: We need the following tasks to depend on codegen for gradle caching/up-to-date checks to work correctly:
148+
149+
* `compileKotlinJvm` (Type=KotlinCompile)
150+
* `compileKotlinMetadata` (Type=KotlinCompileCommon)
151+
* `sourcesJar` and `jvmSourcesJar` (Type=org.gradle.jvm.tasks.Jar)
152+
*/
153+
val codegenTask = tasks.named("generateSmithyProjections")
154+
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
155+
dependsOn(codegenTask)
156+
157+
// generated sts/sso credential providers have quite a few warnings
158+
kotlinOptions.allWarningsAsErrors = false
159+
}
160+
161+
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompileCommon> {
162+
dependsOn(codegenTask)
163+
}
164+
165+
tasks.withType<org.gradle.jvm.tasks.Jar> {
166+
dependsOn(codegenTask)
167+
}
168+
169+
codegen.projections.all {
170+
// add this projected source dir to the common sourceSet
171+
// NOTE - build.gradle.kts is still being generated, it's NOT used though
172+
// TODO - we should probably either have a postProcessing spec or a plugin setting to not generate it to avoid confusion
173+
val projectedSrcDir = projectionRootDir.resolve("src/main/kotlin")
174+
kotlin.sourceSets.commonMain {
175+
println("added $projectedSrcDir to common sourceSet")
176+
kotlin.srcDir(projectedSrcDir)
177+
}
178+
}
179+
180+
// Necessary to avoid Gradle problems identifying correct variant of aws-config. This stems from the smithy-gradle
181+
// plugin (used by codegen plugin) applying the Java plugin which creates these configurations.
182+
listOf("apiElements", "runtimeElements").forEach {
183+
configurations.named(it) {
184+
isCanBeConsumed = false
185+
}
186+
}
187+

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/CachedCredentialsProvider.kt

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,23 @@
55

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

8-
import aws.sdk.kotlin.crt.auth.credentials.build
8+
import aws.sdk.kotlin.runtime.config.CachedValue
9+
import aws.sdk.kotlin.runtime.config.ExpiringValue
10+
import aws.smithy.kotlin.runtime.io.Closeable
11+
import aws.smithy.kotlin.runtime.logging.Logger
12+
import aws.smithy.kotlin.runtime.time.Clock
913
import kotlin.time.Duration
10-
import aws.sdk.kotlin.crt.auth.credentials.CachedCredentialsProvider as CachedCredentialsProviderCrt
14+
import kotlin.time.Duration.Companion.seconds
1115

16+
private const val DEFAULT_CREDENTIALS_REFRESH_BUFFER_SECONDS = 10
1217
/**
13-
* Creates a provider that functions as a caching decorating of another provider.
18+
* The amount of time credentials are valid for before being refreshed when an explicit value
19+
* is not given to/from a provider
20+
*/
21+
public const val DEFAULT_CREDENTIALS_REFRESH_SECONDS: Int = 60 * 15
22+
23+
/**
24+
* Creates a provider that functions as a caching decorator of another provider.
1425
*
1526
* Credentials sourced through this provider will be cached within it until their expiration time.
1627
* When the cached credentials expire, new credentials will be fetched when next queried.
@@ -19,41 +30,40 @@ import aws.sdk.kotlin.crt.auth.credentials.CachedCredentialsProvider as CachedCr
1930
*
2031
* CachedProvider -> ProviderChain(EnvironmentProvider -> ProfileProvider -> ECS/EC2IMD etc...)
2132
*
33+
* @param source the provider to cache credentials results from
34+
* @param expireCredentialsAfter the default expiration time period for sourced credentials. For a given set of
35+
* cached credentials, the refresh time period will be the minimum of this time and any expiration timestamp on
36+
* the credentials themselves.
37+
* @param refreshBufferWindow amount of time before the actual credential expiration time when credentials are
38+
* considered expired. For example, if credentials are expiring in 15 minutes, and the buffer time is 10 seconds,
39+
* then any requests made after 14 minutes and 50 seconds will load new credentials. Defaults to 10 seconds.
40+
* @param clock the source of time for this provider
41+
*
2242
* @return the newly-constructed credentials provider
2343
*/
24-
public class CachedCredentialsProvider private constructor(builder: Builder) : CrtCredentialsProvider {
25-
26-
override val crtProvider: CachedCredentialsProviderCrt = CachedCredentialsProviderCrt.build {
27-
refreshTimeInMilliseconds = builder.refreshTime.inWholeMilliseconds
44+
public class CachedCredentialsProvider(
45+
private val source: CredentialsProvider,
46+
private val expireCredentialsAfter: Duration = DEFAULT_CREDENTIALS_REFRESH_SECONDS.seconds,
47+
refreshBufferWindow: Duration = DEFAULT_CREDENTIALS_REFRESH_BUFFER_SECONDS.seconds,
48+
private val clock: Clock = Clock.System
49+
) : CredentialsProvider, Closeable {
2850

29-
// FIXME - note this won't work until https://github.com/awslabs/aws-crt-java/issues/252 is resolved
30-
source = builder.source?.let { CredentialsProviderCrtProxy(it) }
31-
}
51+
private val cachedCredentials = CachedValue<Credentials>(null, bufferTime = refreshBufferWindow, clock)
3252

33-
public companion object {
34-
/**
35-
* Construct a new [CachedCredentialsProvider] using the given [block]
36-
*/
37-
public fun build(block: Builder.() -> Unit): CachedCredentialsProvider = Builder().apply(block).build()
53+
override suspend fun getCredentials(): Credentials = cachedCredentials.getOrLoad {
54+
val logger = Logger.getLogger<CachedCredentialsProvider>()
55+
logger.trace { "refreshing credentials cache" }
56+
val providerCreds = source.getCredentials()
57+
if (providerCreds.expiration != null) {
58+
ExpiringValue(providerCreds, providerCreds.expiration!!)
59+
} else {
60+
val expiration = clock.now() + expireCredentialsAfter
61+
val creds = providerCreds.copy(expiration = expiration)
62+
ExpiringValue(creds, expiration)
63+
}
3864
}
3965

40-
public class Builder {
41-
/**
42-
* The provider to cache credentials query results from
43-
*/
44-
public var source: CredentialsProvider? = null
45-
46-
/**
47-
* An optional expiration time period for sourced credentials. For a given set of cached credentials,
48-
* the refresh time period will be the minimum of this time and any expiration timestamp on the credentials
49-
* themselves.
50-
*/
51-
public var refreshTime: Duration = Duration.ZERO
52-
53-
public fun build(): CachedCredentialsProvider {
54-
55-
requireNotNull(source) { "CachedCredentialsProvider requires a source provider to wrap" }
56-
return CachedCredentialsProvider(this)
57-
}
66+
override fun close() {
67+
(source as? Closeable)?.close()
5868
}
5969
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0.
4+
*/
5+
6+
package aws.sdk.kotlin.runtime.auth.credentials
7+
8+
import aws.smithy.kotlin.runtime.io.Closeable
9+
import aws.smithy.kotlin.runtime.logging.Logger
10+
11+
// TODO - support caching the provider that actually resolved credentials such that future calls don't involve going through the full chain
12+
13+
/**
14+
* Composite [CredentialsProvider] that delegates to a chain of providers. When asked for credentials [providers]
15+
* are consulted in the order given until one succeeds. If none of the providers in the chain can provide credentials
16+
* then this class will throw an exception. The exception will include the providers tried in the message. Each
17+
* individual exception is available as a suppressed exception.
18+
*
19+
* @param providers the list of providers to delegate to
20+
*/
21+
public open class CredentialsProviderChain(
22+
protected vararg val providers: CredentialsProvider
23+
) : CredentialsProvider, Closeable {
24+
private val logger = Logger.getLogger<CredentialsProviderChain>()
25+
26+
init {
27+
require(providers.isNotEmpty()) { "at least one provider must be in the chain" }
28+
}
29+
30+
override fun toString(): String =
31+
(listOf(this) + providers).map { it::class.simpleName }.joinToString(" -> ")
32+
33+
override suspend fun getCredentials(): Credentials {
34+
val chainException = lazy { CredentialsProviderException("No credentials could be loaded from the chain: $this") }
35+
for (provider in providers) {
36+
try {
37+
return provider.getCredentials()
38+
} catch (ex: Exception) {
39+
logger.debug { "unable to load credentials from $provider: ${ex.message}" }
40+
chainException.value.addSuppressed(ex)
41+
}
42+
}
43+
44+
throw chainException.value
45+
}
46+
47+
override fun close() {
48+
val exceptions = providers.mapNotNull {
49+
try {
50+
(it as? Closeable)?.close()
51+
null
52+
} catch (ex: Exception) {
53+
ex
54+
}
55+
}
56+
if (exceptions.isNotEmpty()) {
57+
val ex = exceptions.first()
58+
exceptions.drop(1).forEach(ex::addSuppressed)
59+
throw ex
60+
}
61+
}
62+
}

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/CrtBasedCredentialsProvider.kt

Lines changed: 0 additions & 14 deletions
This file was deleted.

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/CrtCredentialUtils.kt

Lines changed: 0 additions & 36 deletions
This file was deleted.

0 commit comments

Comments
 (0)