Skip to content

Commit b2624ea

Browse files
committed
Implement DSQL auth token generator
1 parent 4d9ab65 commit b2624ea

File tree

2 files changed

+150
-0
lines changed

2 files changed

+150
-0
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.sdk.kotlin.services.dsql
6+
7+
import aws.sdk.kotlin.runtime.auth.credentials.DefaultChainCredentialsProvider
8+
import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
9+
import aws.smithy.kotlin.runtime.auth.awssigning.AwsSignatureType
10+
import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigningConfig
11+
import aws.smithy.kotlin.runtime.auth.awssigning.DefaultAwsSigner
12+
import aws.smithy.kotlin.runtime.http.HttpMethod
13+
import aws.smithy.kotlin.runtime.http.request.HttpRequest
14+
import aws.smithy.kotlin.runtime.net.url.Url
15+
import aws.smithy.kotlin.runtime.time.Clock
16+
import kotlinx.coroutines.runBlocking
17+
import kotlin.time.Duration
18+
import kotlin.time.Duration.Companion.seconds
19+
20+
/**
21+
* Generates an IAM authentication token for use with DSQL databases
22+
* @param credentials The credentials to use when generating the auth token, defaults to resolving credentials from the [DefaultChainCredentialsProvider]
23+
*/
24+
public class AuthTokenGenerator(
25+
public val credentials: Credentials? = runBlocking { DefaultChainCredentialsProvider().resolve() }
26+
) {
27+
private fun String.trimScheme() = removePrefix("http://").removePrefix("https://")
28+
29+
/**
30+
* Generates an auth token for the DbConnect action.
31+
* @param endpoint the endpoint of the database
32+
* @param region the region of the database
33+
* @param expiration how long the auth token should be valid for. Defaults to 900.seconds
34+
*/
35+
public suspend fun generateDbConnectAuthToken(endpoint: Url, region: String, expiration: Duration = 900.seconds): String {
36+
val dbConnectEndpoint = endpoint.toBuilder().apply {
37+
parameters.apply {
38+
decodedParameters {
39+
add("Action", "DbConnect")
40+
}
41+
}
42+
}.build()
43+
44+
return generateAuthToken(dbConnectEndpoint, region, expiration)
45+
}
46+
47+
/**
48+
* Generates an auth token for the DbConnectAdmin action.
49+
* @param endpoint the endpoint of the database
50+
* @param region the region of the database
51+
* @param expiration how long the auth token should be valid for. Defaults to 900.seconds
52+
*/
53+
public suspend fun generateDbConnectAdminAuthToken(endpoint: Url, region: String, expiration: Duration = 900.seconds): String {
54+
val dbConnectAdminEndpoint = endpoint.toBuilder().apply {
55+
parameters.apply {
56+
decodedParameters {
57+
add("Action", "DbConnectAdmin")
58+
}
59+
}
60+
}.build()
61+
62+
return generateAuthToken(dbConnectAdminEndpoint, region, expiration)
63+
}
64+
65+
private suspend fun generateAuthToken(endpoint: Url, region: String, expiration: Duration): String {
66+
val req = HttpRequest(HttpMethod.GET, endpoint)
67+
68+
val creds = credentials
69+
70+
val config = AwsSigningConfig {
71+
credentials = creds ?: DefaultChainCredentialsProvider().resolve()
72+
this.region = region
73+
service = "dsql"
74+
signingDate = Clock.System.now()
75+
expiresAfter = expiration
76+
signatureType = AwsSignatureType.HTTP_REQUEST_VIA_QUERY_PARAMS
77+
}
78+
79+
return DefaultAwsSigner.sign(req, config).output.url.toString().trimScheme()
80+
}
81+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.sdk.kotlin.services.dsql
6+
7+
import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
8+
import aws.smithy.kotlin.runtime.net.Host
9+
import aws.smithy.kotlin.runtime.net.url.Url
10+
import kotlinx.coroutines.test.runTest
11+
import kotlin.test.Test
12+
import kotlin.test.assertContains
13+
import kotlin.test.assertFalse
14+
import kotlin.test.assertTrue
15+
import kotlin.time.Duration.Companion.seconds
16+
17+
class AuthTokenGeneratorTest {
18+
@Test
19+
fun testGenerateDbConnectAuthToken() = runTest {
20+
val credentials = Credentials("akid", "secret")
21+
22+
val token = AuthTokenGenerator(credentials)
23+
.generateDbConnectAuthToken(
24+
endpoint = Url { host = Host.parse("peccy.dsql.us-east-1.on.aws") },
25+
region = "us-east-1",
26+
expiration = 450.seconds
27+
)
28+
29+
// Token should have a parameter Action=DbConnect
30+
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+
36+
assertContains(token, "X-Amz-Expires=450")
37+
38+
// Token should not contain a scheme
39+
listOf("http://", "https://").forEach {
40+
assertFalse(token.contains(it))
41+
}
42+
}
43+
44+
@Test
45+
fun testGenerateDbConnectAuthAdminToken() = runTest {
46+
val credentials = Credentials("akid", "secret")
47+
48+
val token = AuthTokenGenerator(credentials)
49+
.generateDbConnectAdminAuthToken(
50+
endpoint = Url { host = Host.parse("peccy.dsql.us-east-1.on.aws") },
51+
region = "us-east-1",
52+
expiration = 450.seconds
53+
)
54+
55+
// Token should have a parameter Action=DbConnect
56+
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-Expires=450")
63+
64+
// Token should not contain a scheme
65+
listOf("http://", "https://").forEach {
66+
assertFalse(token.contains(it))
67+
}
68+
}
69+
}

0 commit comments

Comments
 (0)