Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add auth token generators for RDS and DSQL #1495

Merged
merged 27 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b2624ea
Implement DSQL auth token generator
lauzadis Jan 8, 2025
adb9d72
Abstract AuthTokenGenerator and implement RDS AuthTokenGenerator
lauzadis Jan 8, 2025
2631b3a
ktlint
lauzadis Jan 8, 2025
e7db5b6
changelog
lauzadis Jan 8, 2025
d46a242
Update test comments
lauzadis Jan 8, 2025
6a12913
Update test comments
lauzadis Jan 8, 2025
bbc53a5
Update test comments
lauzadis Jan 8, 2025
2c66500
Commit abstracted AuthTokenGenerator
lauzadis Jan 8, 2025
68fa563
Add API dump
lauzadis Jan 8, 2025
3b9b2b2
Rename service-specific token generators, add more optional parameter…
lauzadis Jan 9, 2025
cae7827
Merge branch 'main' of github.com:awslabs/aws-sdk-kotlin into feat-au…
lauzadis Jan 9, 2025
19466be
ktlint
lauzadis Jan 9, 2025
d3f877d
apiDump
lauzadis Jan 9, 2025
0e88c5a
Relocate AuthTokenGenerator to aws-signing-common
lauzadis Jan 9, 2025
cc6961f
apiDump
lauzadis Jan 9, 2025
1c391b8
Refactor based on upstream changes, simplify parameter addition, chan…
lauzadis Jan 9, 2025
30d625e
Remove `credentialsRefreshBuffer` parameter
lauzadis Jan 9, 2025
c1ddba9
`put` to `decodedParameters`
lauzadis Jan 9, 2025
d3e1cfb
ktlint
lauzadis Jan 9, 2025
d88a88d
Add tests comparing X-Amz-Date header to clock
lauzadis Jan 10, 2025
23146e7
Update to latest version of smithy-kotlin
lauzadis Jan 10, 2025
0808398
Merge branch 'main' of github.com:awslabs/aws-sdk-kotlin into feat-au…
lauzadis Jan 10, 2025
0c1b50d
Merge branch 'main' of github.com:awslabs/aws-sdk-kotlin into feat-au…
lauzadis Jan 16, 2025
4156091
Ensure `Host` header is signed
lauzadis Jan 16, 2025
755b7e2
Upgrade to v4
lauzadis Jan 16, 2025
13aea71
Bump to latest build plugin version
lauzadis Jan 16, 2025
bcece78
Bump smithy-kotlin
lauzadis Jan 16, 2025
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
5 changes: 5 additions & 0 deletions .changes/a2520ae2-1cba-49b1-b720-10b70e52f9e0.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"id": "a2520ae2-1cba-49b1-b720-10b70e52f9e0",
"type": "feature",
"description": "Add auth token generator for RDS and DSQL"
}
4 changes: 2 additions & 2 deletions .github/workflows/continuous-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ jobs:
./gradlew -Ptest.java.version=${{ matrix.java-version }} jvmTest --stacktrace
- name: Save Test Reports
if: failure()
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: test-reports
path: '**/build/reports'
Expand Down Expand Up @@ -80,7 +80,7 @@ jobs:
./gradlew testAllProtocols
- name: Save Test Reports
if: failure()
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: test-reports
path: '**/build/reports'
6 changes: 3 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ ksp-version = "2.1.0-1.0.29" # Keep in sync with kotlin-version

dokka-version = "1.9.10"

aws-kotlin-repo-tools-version = "0.4.17"
aws-kotlin-repo-tools-version = "0.4.18"

# libs
coroutines-version = "1.9.0"
atomicfu-version = "0.25.0"
binary-compatibility-validator-version = "0.16.3"

# smithy-kotlin codegen and runtime are versioned separately
smithy-kotlin-runtime-version = "1.4.0"
smithy-kotlin-codegen-version = "0.34.0"
smithy-kotlin-runtime-version = "1.4.1"
smithy-kotlin-codegen-version = "0.34.1"

# codegen
smithy-version = "1.53.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.sdk.kotlin.services.dsql

import aws.sdk.kotlin.runtime.auth.credentials.DefaultChainCredentialsProvider
import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider
import aws.smithy.kotlin.runtime.auth.awssigning.AuthTokenGenerator
import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigner
import aws.smithy.kotlin.runtime.auth.awssigning.DefaultAwsSigner
import aws.smithy.kotlin.runtime.net.url.Url
import aws.smithy.kotlin.runtime.time.Clock
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

/**
* Generates an IAM authentication token for use with DSQL databases
* @param credentialsProvider The [CredentialsProvider] which will provide credentials to use when generating the auth token, defaults to [DefaultChainCredentialsProvider]
* @param signer The [AwsSigner] implementation to use when creating the authentication token, defaults to [DefaultAwsSigner]
* @param clock The [Clock] implementation to use
*/
public class DsqlAuthTokenGenerator(
public val credentialsProvider: CredentialsProvider = DefaultChainCredentialsProvider(),
public val signer: AwsSigner = DefaultAwsSigner,
public val clock: Clock = Clock.System,
) {
private val generator = AuthTokenGenerator("dsql", credentialsProvider, signer, clock)

/**
* Generates an auth token for the DbConnect action.
* @param endpoint the endpoint of the database
* @param region the region of the database
* @param expiration how long the auth token should be valid for. Defaults to 900.seconds
*/
public suspend fun generateDbConnectAuthToken(endpoint: Url, region: String, expiration: Duration = 900.seconds): String {
val dbConnectEndpoint = endpoint.toBuilder().apply {
parameters.decodedParameters.put("Action", "DbConnect")
}.build()

return generator.generateAuthToken(dbConnectEndpoint, region, expiration)
}

/**
* Generates an auth token for the DbConnectAdmin action.
* @param endpoint the endpoint of the database
* @param region the region of the database
* @param expiration how long the auth token should be valid for. Defaults to 900.seconds
*/
public suspend fun generateDbConnectAdminAuthToken(endpoint: Url, region: String, expiration: Duration = 900.seconds): String {
val dbConnectAdminEndpoint = endpoint.toBuilder().apply {
parameters.decodedParameters.put("Action", "DbConnectAdmin")
}.build()

return generator.generateAuthToken(dbConnectAdminEndpoint, region, expiration)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.sdk.kotlin.services.dsql

import aws.sdk.kotlin.runtime.auth.credentials.StaticCredentialsProvider
import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
import aws.smithy.kotlin.runtime.net.Host
import aws.smithy.kotlin.runtime.net.url.Url
import aws.smithy.kotlin.runtime.time.Instant
import aws.smithy.kotlin.runtime.time.ManualClock
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.time.Duration.Companion.seconds

class DsqlAuthTokenGeneratorTest {
@Test
fun testGenerateDbConnectAuthToken() = runTest {
val clock = ManualClock(Instant.fromEpochSeconds(1724716800))

val credentials = Credentials("akid", "secret")
val credentialsProvider = StaticCredentialsProvider(credentials)

val token = DsqlAuthTokenGenerator(credentialsProvider, clock = clock)
.generateDbConnectAuthToken(
endpoint = Url { host = Host.parse("peccy.dsql.us-east-1.on.aws") },
region = "us-east-1",
expiration = 450.seconds,
)

// Token should have a parameter Action=DbConnect
assertContains(token, "peccy.dsql.us-east-1.on.aws?Action=DbConnect")
assertContains(token, "X-Amz-Credential=akid%2F20240827%2Fus-east-1%2Fdsql%2Faws4_request")
assertContains(token, "X-Amz-Expires=450")
assertContains(token, "X-Amz-SignedHeaders=host")

// Token should not contain a scheme
listOf("http://", "https://").forEach {
assertFalse(token.contains(it))
}

val urlToken = Url.parse("https://$token")
val xAmzDate = urlToken.parameters.decodedParameters.getValue("X-Amz-Date").single()
assertEquals(clock.now(), Instant.fromIso8601(xAmzDate))
}

@Test
fun testGenerateDbConnectAuthAdminToken() = runTest {
val clock = ManualClock(Instant.fromEpochSeconds(1724716800))

val credentials = Credentials("akid", "secret")
val credentialsProvider = StaticCredentialsProvider(credentials)

val token = DsqlAuthTokenGenerator(credentialsProvider, clock = clock)
.generateDbConnectAdminAuthToken(
endpoint = Url { host = Host.parse("peccy.dsql.us-east-1.on.aws") },
region = "us-east-1",
expiration = 450.seconds,
)

// Token should have a parameter Action=DbConnectAdmin
assertContains(token, "peccy.dsql.us-east-1.on.aws?Action=DbConnectAdmin")
assertContains(token, "X-Amz-Credential=akid%2F20240827%2Fus-east-1%2Fdsql%2Faws4_request")
assertContains(token, "X-Amz-Expires=450")
assertContains(token, "X-Amz-SignedHeaders=host")

// Token should not contain a scheme
listOf("http://", "https://").forEach {
assertFalse(token.contains(it))
}

val urlToken = Url.parse("https://$token")
val xAmzDate = urlToken.parameters.decodedParameters.getValue("X-Amz-Date").single()
assertEquals(clock.now(), Instant.fromIso8601(xAmzDate))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.sdk.kotlin.services.rds

import aws.sdk.kotlin.runtime.auth.credentials.DefaultChainCredentialsProvider
import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider
import aws.smithy.kotlin.runtime.auth.awssigning.AuthTokenGenerator
import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigner
import aws.smithy.kotlin.runtime.auth.awssigning.DefaultAwsSigner
import aws.smithy.kotlin.runtime.net.url.Url
import aws.smithy.kotlin.runtime.time.Clock
import kotlin.apply
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

/**
* Generates an IAM authentication token for use with RDS databases
* @param credentialsProvider The [CredentialsProvider] which will provide credentials to use when generating the auth token, defaults to [DefaultChainCredentialsProvider]
* @param signer The [AwsSigner] implementation to use when creating the authentication token, defaults to [DefaultAwsSigner]
* @param clock The [Clock] implementation to use
*/
public class RdsAuthTokenGenerator(
public val credentialsProvider: CredentialsProvider = DefaultChainCredentialsProvider(),
public val signer: AwsSigner = DefaultAwsSigner,
public val clock: Clock = Clock.System,
) {
private val generator = AuthTokenGenerator("rds-db", credentialsProvider, signer, clock)

/**
* Generates an auth token for the `connect` action.
* @param endpoint the endpoint of the database
* @param region the region of the database
* @param username the username to authenticate with
* @param expiration how long the auth token should be valid for. Defaults to 900.seconds
*/
public suspend fun generateAuthToken(endpoint: Url, region: String, username: String, expiration: Duration = 900.seconds): String {
val endpoint = endpoint.toBuilder().apply {
parameters.decodedParameters.apply {
put("Action", "connect")
put("DBUser", username)
}
}.build()

return generator.generateAuthToken(endpoint, region, expiration)
}
}
55 changes: 55 additions & 0 deletions services/rds/common/test/RdsAuthTokenGeneratorTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.sdk.kotlin.services.rds

import aws.sdk.kotlin.runtime.auth.credentials.StaticCredentialsProvider
import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
import aws.smithy.kotlin.runtime.net.Host
import aws.smithy.kotlin.runtime.net.url.Url
import aws.smithy.kotlin.runtime.time.Instant
import aws.smithy.kotlin.runtime.time.ManualClock
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.time.Duration.Companion.seconds

class RdsAuthTokenGeneratorTest {
@Test
fun testGenerateAuthToken() = runTest {
val clock = ManualClock(Instant.fromEpochSeconds(1724716800))

val credentials = Credentials("akid", "secret")
val credentialsProvider = StaticCredentialsProvider(credentials)

val generator = RdsAuthTokenGenerator(credentialsProvider, clock = clock)

val token = generator.generateAuthToken(
endpoint = Url {
host = Host.parse("prod-instance.us-east-1.rds.amazonaws.com")
port = 3306
},
region = "us-east-1",
username = "peccy",
expiration = 450.seconds,
)

// Token should have a parameter Action=connect, DBUser=peccy
assertContains(token, "prod-instance.us-east-1.rds.amazonaws.com:3306?Action=connect&DBUser=peccy")
assertContains(token, "X-Amz-Credential=akid%2F20240827%2Fus-east-1%2Frds-db%2Faws4_request")
assertContains(token, "X-Amz-Expires=450")
assertContains(token, "X-Amz-SignedHeaders=host")

// Token should not contain a scheme
listOf("http://", "https://").forEach {
assertFalse(token.contains(it))
}

val urlToken = Url.parse("https://$token")
val xAmzDate = urlToken.parameters.decodedParameters.getValue("X-Amz-Date").single()
assertEquals(clock.now(), Instant.fromIso8601(xAmzDate))
}
}
Loading