Skip to content

Commit 3b9b2b2

Browse files
committed
Rename service-specific token generators, add more optional parameters, change [Credentials] param to [CredentialsProvider]
1 parent 68fa563 commit 3b9b2b2

File tree

5 files changed

+89
-45
lines changed

5 files changed

+89
-45
lines changed

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

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,43 +6,63 @@ package aws.sdk.kotlin.runtime.auth
66

77
import aws.sdk.kotlin.runtime.auth.credentials.DefaultChainCredentialsProvider
88
import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
9+
import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider
910
import aws.smithy.kotlin.runtime.auth.awssigning.AwsSignatureType
11+
import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigner
1012
import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigningConfig
1113
import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigningConfig.Companion.invoke
1214
import aws.smithy.kotlin.runtime.auth.awssigning.DefaultAwsSigner
1315
import aws.smithy.kotlin.runtime.http.HttpMethod
1416
import aws.smithy.kotlin.runtime.http.request.HttpRequest
1517
import aws.smithy.kotlin.runtime.net.url.Url
1618
import aws.smithy.kotlin.runtime.time.Clock
17-
import kotlinx.coroutines.runBlocking
19+
import aws.smithy.kotlin.runtime.util.ExpiringValue
1820
import kotlin.time.Duration
21+
import kotlin.time.Duration.Companion.minutes
22+
import kotlin.time.Duration.Companion.seconds
23+
24+
// The default expiration value to use for [Credentials] when none is provided.
25+
private val DEFAULT_CREDENTIALS_EXPIRATION = 10.minutes
1926

2027
/**
2128
* Generates an authentication token, which is a SigV4-signed URL with the HTTP scheme removed.
2229
* @param service The name of the service the token is being generated for
23-
* @param credentials The credentials to use when generating the auth token, defaults to resolving credentials from the [DefaultChainCredentialsProvider]
30+
* @param credentialsProvider The [CredentialsProvider] which will provide credentials to use when generating the auth token, defaults to [DefaultChainCredentialsProvider]
31+
* @param credentialsRefreshBuffer The amount of time before the resolved [Credentials] expire in which they are considered expired, defaults to 10 seconds.
32+
* @param signer The [AwsSigner] implementation to use when creating the authentication token, defaults to [DefaultAwsSigner]
33+
* @param clock The [Clock] implementation to use
2434
*/
2535
public class AuthTokenGenerator(
2636
public val service: String,
27-
public val credentials: Credentials? = runBlocking { DefaultChainCredentialsProvider().resolve() },
37+
public val credentialsProvider: CredentialsProvider = DefaultChainCredentialsProvider(),
38+
public val credentialsRefreshBuffer: Duration = 10.seconds,
39+
public val signer: AwsSigner = DefaultAwsSigner,
40+
public val clock: Clock = Clock.System
2841
) {
29-
private fun String.trimScheme() = removePrefix("http://").removePrefix("https://")
42+
private lateinit var credentials: ExpiringValue<Credentials>
43+
44+
private fun Url.trimScheme(): String = toString().removePrefix(scheme.protocolName).removePrefix("://")
3045

3146
public suspend fun generateAuthToken(endpoint: Url, region: String, expiration: Duration): String {
47+
if (!::credentials.isInitialized || (credentials.expiresAt - clock.now()).absoluteValue <= credentialsRefreshBuffer) {
48+
val resolved = credentialsProvider.resolve()
49+
credentials = ExpiringValue(resolved, resolved.expiration ?: (clock.now() + DEFAULT_CREDENTIALS_EXPIRATION))
50+
}
51+
3252
val req = HttpRequest(HttpMethod.GET, endpoint)
3353

34-
val creds = credentials
54+
val creds = credentials.value
3555
val serv = service
3656

3757
val config = AwsSigningConfig {
3858
credentials = creds
3959
this.region = region
4060
service = serv
41-
signingDate = Clock.System.now()
61+
signingDate = clock.now()
4262
expiresAfter = expiration
4363
signatureType = AwsSignatureType.HTTP_REQUEST_VIA_QUERY_PARAMS
4464
}
4565

46-
return DefaultAwsSigner.sign(req, config).output.url.toString().trimScheme()
66+
return signer.sign(req, config).output.url.trimScheme()
4767
}
4868
}

services/dsql/common/src/aws/sdk/kotlin/services/dsql/AuthTokenGenerator.kt renamed to services/dsql/common/src/aws/sdk/kotlin/services/dsql/DsqlAuthTokenGenerator.kt

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,28 @@ package aws.sdk.kotlin.services.dsql
77
import aws.sdk.kotlin.runtime.auth.AuthTokenGenerator
88
import aws.sdk.kotlin.runtime.auth.credentials.DefaultChainCredentialsProvider
99
import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
10+
import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider
11+
import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigner
12+
import aws.smithy.kotlin.runtime.auth.awssigning.DefaultAwsSigner
1013
import aws.smithy.kotlin.runtime.net.url.Url
11-
import kotlinx.coroutines.runBlocking
14+
import aws.smithy.kotlin.runtime.time.Clock
1215
import kotlin.time.Duration
1316
import kotlin.time.Duration.Companion.seconds
1417

1518
/**
1619
* Generates an IAM authentication token for use with DSQL databases
17-
* @param credentials The credentials to use when generating the auth token, defaults to resolving credentials from the [DefaultChainCredentialsProvider]
20+
* @param credentialsProvider The [CredentialsProvider] which will provide credentials to use when generating the auth token, defaults to [DefaultChainCredentialsProvider]
21+
* @param credentialsRefreshBuffer The amount of time before the resolved [Credentials] expire in which they are considered expired, defaults to 10 seconds.
22+
* @param signer The [AwsSigner] implementation to use when creating the authentication token, defaults to [DefaultAwsSigner]
23+
* @param clock The [Clock] implementation to use
1824
*/
19-
public class AuthTokenGenerator(
20-
public val credentials: Credentials? = runBlocking { DefaultChainCredentialsProvider().resolve() },
25+
public class DsqlAuthTokenGenerator(
26+
public val credentialsProvider: CredentialsProvider = DefaultChainCredentialsProvider(),
27+
public val credentialsRefreshBuffer: Duration = 10.seconds,
28+
public val signer: AwsSigner = DefaultAwsSigner,
29+
public val clock: Clock = Clock.System
2130
) {
22-
private val generator = AuthTokenGenerator("dsql", credentials)
31+
private val generator = AuthTokenGenerator("dsql", credentialsProvider, credentialsRefreshBuffer, signer, clock)
2332

2433
/**
2534
* Generates an auth token for the DbConnect action.

services/dsql/common/test/aws/sdk/kotlin/services/dsql/AuthTokenGeneratorTest.kt renamed to services/dsql/common/test/aws/sdk/kotlin/services/dsql/DsqlAuthTokenGeneratorTest.kt

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,28 @@
44
*/
55
package aws.sdk.kotlin.services.dsql
66

7+
import aws.sdk.kotlin.runtime.auth.credentials.StaticCredentialsProvider
78
import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
89
import aws.smithy.kotlin.runtime.net.Host
910
import aws.smithy.kotlin.runtime.net.url.Url
11+
import aws.smithy.kotlin.runtime.time.Instant
12+
import aws.smithy.kotlin.runtime.time.ManualClock
1013
import kotlinx.coroutines.test.runTest
1114
import kotlin.test.Test
1215
import kotlin.test.assertContains
1316
import kotlin.test.assertFalse
1417
import kotlin.test.assertTrue
1518
import kotlin.time.Duration.Companion.seconds
1619

17-
class AuthTokenGeneratorTest {
20+
class DsqlAuthTokenGeneratorTest {
1821
@Test
1922
fun testGenerateDbConnectAuthToken() = runTest {
23+
val clock = ManualClock(Instant.fromEpochSeconds(1724716800))
24+
2025
val credentials = Credentials("akid", "secret")
26+
val credentialsProvider = StaticCredentialsProvider(credentials)
2127

22-
val token = AuthTokenGenerator(credentials)
28+
val token = DsqlAuthTokenGenerator(credentialsProvider, clock = clock)
2329
.generateDbConnectAuthToken(
2430
endpoint = Url { host = Host.parse("peccy.dsql.us-east-1.on.aws") },
2531
region = "us-east-1",
@@ -28,11 +34,7 @@ class AuthTokenGeneratorTest {
2834

2935
// Token should have a parameter Action=DbConnect
3036
assertContains(token, "peccy.dsql.us-east-1.on.aws?Action=DbConnect")
31-
32-
// Match the X-Amz-Credential parameter for any signing date
33-
val credentialRegex = Regex("X-Amz-Credential=akid%2F(\\d{8})%2Fus-east-1%2Fdsql%2Faws4_request")
34-
assertTrue(token.contains(credentialRegex))
35-
37+
assertContains(token, "X-Amz-Credential=akid%2F20240827%2Fus-east-1%2Fdsql%2Faws4_request")
3638
assertContains(token, "X-Amz-Expires=450")
3739

3840
// Token should not contain a scheme
@@ -43,9 +45,12 @@ class AuthTokenGeneratorTest {
4345

4446
@Test
4547
fun testGenerateDbConnectAuthAdminToken() = runTest {
48+
val clock = ManualClock(Instant.fromEpochSeconds(1724716800))
49+
4650
val credentials = Credentials("akid", "secret")
51+
val credentialsProvider = StaticCredentialsProvider(credentials)
4752

48-
val token = AuthTokenGenerator(credentials)
53+
val token = DsqlAuthTokenGenerator(credentialsProvider, clock = clock)
4954
.generateDbConnectAdminAuthToken(
5055
endpoint = Url { host = Host.parse("peccy.dsql.us-east-1.on.aws") },
5156
region = "us-east-1",
@@ -54,11 +59,7 @@ class AuthTokenGeneratorTest {
5459

5560
// Token should have a parameter Action=DbConnectAdmin
5661
assertContains(token, "peccy.dsql.us-east-1.on.aws?Action=DbConnectAdmin")
57-
58-
// Match the X-Amz-Credential parameter for any signing date
59-
val credentialRegex = Regex("X-Amz-Credential=akid%2F(\\d{8})%2Fus-east-1%2Fdsql%2Faws4_request")
60-
assertTrue(token.contains(credentialRegex))
61-
62+
assertContains(token, "X-Amz-Credential=akid%2F20240827%2Fus-east-1%2Fdsql%2Faws4_request")
6263
assertContains(token, "X-Amz-Expires=450")
6364

6465
// Token should not contain a scheme

services/rds/common/src/aws/sdk/kotlin/services/rds/AuthTokenGenerator.kt renamed to services/rds/common/src/aws/sdk/kotlin/services/rds/RdsAuthTokenGenerator.kt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,30 @@ package aws.sdk.kotlin.services.rds
77
import aws.sdk.kotlin.runtime.auth.AuthTokenGenerator
88
import aws.sdk.kotlin.runtime.auth.credentials.DefaultChainCredentialsProvider
99
import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
10+
import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider
11+
import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigner
12+
import aws.smithy.kotlin.runtime.auth.awssigning.DefaultAwsSigner
1013
import aws.smithy.kotlin.runtime.net.url.Url
14+
import aws.smithy.kotlin.runtime.time.Clock
1115
import kotlinx.coroutines.runBlocking
1216
import kotlin.apply
1317
import kotlin.time.Duration
1418
import kotlin.time.Duration.Companion.seconds
1519

1620
/**
1721
* Generates an IAM authentication token for use with RDS databases
18-
* @param credentials The credentials to use when generating the auth token, defaults to resolving credentials from the [DefaultChainCredentialsProvider]
22+
* @param credentialsProvider The [CredentialsProvider] which will provide credentials to use when generating the auth token, defaults to [DefaultChainCredentialsProvider]
23+
* @param credentialsRefreshBuffer The amount of time before the resolved [Credentials] expire in which they are considered expired, defaults to 10 seconds.
24+
* @param signer The [AwsSigner] implementation to use when creating the authentication token, defaults to [DefaultAwsSigner]
25+
* @param clock The [Clock] implementation to use
1926
*/
20-
public class AuthTokenGenerator(
21-
public val credentials: Credentials? = runBlocking { DefaultChainCredentialsProvider().resolve() },
27+
public class RdsAuthTokenGenerator(
28+
public val credentialsProvider: CredentialsProvider = DefaultChainCredentialsProvider(),
29+
public val credentialsRefreshBuffer: Duration = 10.seconds,
30+
public val signer: AwsSigner = DefaultAwsSigner,
31+
public val clock: Clock = Clock.System
2232
) {
23-
private val generator = AuthTokenGenerator("rds-db", credentials)
33+
private val generator = AuthTokenGenerator("rds-db", credentialsProvider, credentialsRefreshBuffer, signer, clock)
2434

2535
/**
2636
* Generates an auth token for the `connect` action.

services/rds/common/test/AuthTokenGeneratorTest.kt renamed to services/rds/common/test/RdsAuthTokenGeneratorTest.kt

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,39 +4,43 @@
44
*/
55
package aws.sdk.kotlin.services.rds
66

7+
import aws.sdk.kotlin.runtime.auth.credentials.StaticCredentialsProvider
78
import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
89
import aws.smithy.kotlin.runtime.net.Host
910
import aws.smithy.kotlin.runtime.net.url.Url
11+
import aws.smithy.kotlin.runtime.time.Instant
12+
import aws.smithy.kotlin.runtime.time.ManualClock
1013
import kotlinx.coroutines.test.runTest
1114
import kotlin.test.Test
1215
import kotlin.test.assertContains
1316
import kotlin.test.assertFalse
1417
import kotlin.test.assertTrue
1518
import kotlin.time.Duration.Companion.seconds
1619

17-
class AuthTokenGeneratorTest {
20+
class RdsAuthTokenGeneratorTest {
1821
@Test
1922
fun testGenerateAuthToken() = runTest {
23+
val clock = ManualClock(Instant.fromEpochSeconds(1724716800))
24+
println(clock.now())
25+
2026
val credentials = Credentials("akid", "secret")
27+
val credentialsProvider = StaticCredentialsProvider(credentials)
28+
29+
val generator = RdsAuthTokenGenerator(credentialsProvider, clock = clock)
2130

22-
val token = AuthTokenGenerator(credentials)
23-
.generateAuthToken(
24-
endpoint = Url {
25-
host = Host.parse("prod-instance.us-east-1.rds.amazonaws.com")
26-
port = 3306
27-
},
28-
region = "us-east-1",
29-
username = "peccy",
30-
expiration = 450.seconds,
31-
)
31+
val token = generator.generateAuthToken(
32+
endpoint = Url {
33+
host = Host.parse("prod-instance.us-east-1.rds.amazonaws.com")
34+
port = 3306
35+
},
36+
region = "us-east-1",
37+
username = "peccy",
38+
expiration = 450.seconds,
39+
)
3240

3341
// Token should have a parameter Action=connect, DBUser=peccy
3442
assertContains(token, "prod-instance.us-east-1.rds.amazonaws.com:3306?Action=connect&DBUser=peccy")
35-
36-
// Match the X-Amz-Credential parameter for any signing date
37-
val credentialRegex = Regex("X-Amz-Credential=akid%2F(\\d{8})%2Fus-east-1%2Frds-db%2Faws4_request")
38-
assertTrue(token.contains(credentialRegex))
39-
43+
assertContains(token, "X-Amz-Credential=akid%2F20240827%2Fus-east-1%2Frds-db%2Faws4_request")
4044
assertContains(token, "X-Amz-Expires=450")
4145

4246
// Token should not contain a scheme

0 commit comments

Comments
 (0)