Skip to content

Commit cdbfec9

Browse files
authored
feat: consume smithy-kotlin SigV4a implementation (#1536)
* Remove UnsupportedSigningAlgorithmIntegration * ktlint * Remove unused runtime type * Deprecate UnsupportedSigningAlgorithmInterceptor instead of removing it * Changelog * Add DefaultAwsSigner to MRAP test * ktlint * Fix file name * Re-enable SESv2 SigV4a test * ktlint * Upgrade to latest smithy-kotlin version
1 parent ed64779 commit cdbfec9

File tree

10 files changed

+204
-280
lines changed

10 files changed

+204
-280
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "9636f12b-5612-41ec-a9ae-9434724e97a5",
3+
"type": "feature",
4+
"description": "Add SigV4a support to the default AWS signer"
5+
}

aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/interceptors/UnsupportedSigningAlgorithmInterceptor.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import aws.smithy.kotlin.runtime.http.response.HttpResponse
1818
* If so it sends users to a section in the AWS SDK for Kotlin documentation on how to fix it.
1919
*/
2020
@InternalSdkApi
21+
@Deprecated("This interceptor is no longer used. It will be removed in the next minor version, v1.5.x.")
2122
public class UnsupportedSigningAlgorithmInterceptor : HttpInterceptor {
2223
override suspend fun modifyBeforeCompletion(context: ResponseInterceptorContext<Any, Any, HttpRequest?, HttpResponse?>): Result<Any> {
2324
context.response.exceptionOrNull()?.let {

codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsRuntimeTypes.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ object AwsRuntimeTypes {
5959
object Http : RuntimeTypePackage(AwsKotlinDependency.AWS_HTTP) {
6060
object Interceptors : RuntimeTypePackage(AwsKotlinDependency.AWS_HTTP, "interceptors") {
6161
val AddUserAgentMetadataInterceptor = symbol("AddUserAgentMetadataInterceptor")
62-
val UnsupportedSigningAlgorithmInterceptor = symbol("UnsupportedSigningAlgorithmInterceptor")
6362
val IgnoreCompositeFlexibleChecksumResponseInterceptor = symbol("IgnoreCompositeFlexibleChecksumResponseInterceptor")
6463

6564
object BusinessMetrics : RuntimeTypePackage(AwsKotlinDependency.AWS_HTTP, "interceptors.businessmetrics") {

codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/UnsupportedSigningAlgorithmIntegration.kt

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

codegen/aws-sdk-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ aws.sdk.kotlin.codegen.customization.machinelearning.MachineLearningEndpointCust
3535
aws.sdk.kotlin.codegen.customization.route53.TrimResourcePrefix
3636
aws.sdk.kotlin.codegen.customization.ec2.EC2MakePrimitivesOptional
3737
aws.sdk.kotlin.codegen.customization.RemoveDefaults
38-
aws.sdk.kotlin.codegen.customization.s3.UnsupportedSigningAlgorithmIntegration
3938
aws.sdk.kotlin.codegen.customization.SigV4AsymmetricTraitCustomization
4039
aws.sdk.kotlin.codegen.customization.cloudfrontkeyvaluestore.BackfillSigV4ACustomization
4140
aws.sdk.kotlin.codegen.customization.s3.express.SigV4S3ExpressAuthSchemeIntegration

gradle/libs.versions.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ atomicfu-version = "0.25.0"
1212
binary-compatibility-validator-version = "0.16.3"
1313

1414
# smithy-kotlin codegen and runtime are versioned separately
15-
smithy-kotlin-runtime-version = "1.4.3"
16-
smithy-kotlin-codegen-version = "0.34.3"
15+
smithy-kotlin-runtime-version = "1.4.5"
16+
smithy-kotlin-codegen-version = "0.34.5"
1717

1818
# codegen
1919
smithy-version = "1.53.0"
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
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.e2etest
6+
7+
import aws.sdk.kotlin.e2etest.S3TestUtils.deleteBucketAndAllContents
8+
import aws.sdk.kotlin.e2etest.S3TestUtils.getAccountId
9+
import aws.sdk.kotlin.e2etest.S3TestUtils.getBucketWithPrefix
10+
import aws.sdk.kotlin.services.s3.S3Client
11+
import aws.sdk.kotlin.services.s3.deleteObject
12+
import aws.sdk.kotlin.services.s3.putObject
13+
import aws.sdk.kotlin.services.s3.withConfig
14+
import aws.sdk.kotlin.services.s3control.S3ControlClient
15+
import aws.sdk.kotlin.services.s3control.createMultiRegionAccessPoint
16+
import aws.sdk.kotlin.services.s3control.deleteMultiRegionAccessPoint
17+
import aws.sdk.kotlin.services.s3control.describeMultiRegionAccessPointOperation
18+
import aws.sdk.kotlin.services.s3control.getMultiRegionAccessPoint
19+
import aws.sdk.kotlin.services.s3control.model.Region
20+
import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigner
21+
import aws.smithy.kotlin.runtime.auth.awssigning.DefaultAwsSigner
22+
import aws.smithy.kotlin.runtime.auth.awssigning.crt.CrtAwsSigner
23+
import aws.smithy.kotlin.runtime.http.auth.SigV4AsymmetricAuthScheme
24+
import kotlinx.coroutines.delay
25+
import kotlinx.coroutines.runBlocking
26+
import kotlinx.coroutines.withTimeout
27+
import org.junit.jupiter.api.AfterAll
28+
import org.junit.jupiter.api.BeforeAll
29+
import org.junit.jupiter.api.TestInstance
30+
import org.junit.jupiter.params.ParameterizedTest
31+
import org.junit.jupiter.params.provider.Arguments
32+
import org.junit.jupiter.params.provider.MethodSource
33+
import java.util.stream.Stream
34+
import kotlin.time.Duration
35+
import kotlin.time.Duration.Companion.minutes
36+
import kotlin.time.Duration.Companion.seconds
37+
38+
private const val MRAP_BUCKET_PREFIX = "s3-mrap-test-bucket-"
39+
private const val MULTI_REGION_ACCESS_POINT_NAME = "aws-sdk-for-kotlin-test-multi-region-access-point"
40+
private const val TEST_OBJECT_KEY = "test.txt"
41+
42+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
43+
class MutliRegionAccessPointTest {
44+
private lateinit var s3West: S3Client
45+
private lateinit var s3East: S3Client
46+
private lateinit var s3Control: S3ControlClient
47+
48+
private lateinit var accountId: String
49+
private lateinit var multiRegionAccessPointArn: String
50+
private lateinit var usWestBucket: String
51+
private lateinit var usEastBucket: String
52+
53+
@BeforeAll
54+
fun setup(): Unit = runBlocking {
55+
s3West = S3Client { region = "us-west-2" }
56+
s3East = S3Client { region = "us-east-2" }
57+
s3Control = S3ControlClient { region = "us-west-2" }
58+
59+
accountId = getAccountId()
60+
usWestBucket = getBucketWithPrefix(s3West, MRAP_BUCKET_PREFIX, "us-west-2", accountId)
61+
usEastBucket = getBucketWithPrefix(s3East, MRAP_BUCKET_PREFIX, "us-east-2", accountId)
62+
63+
multiRegionAccessPointArn = s3Control.createMultiRegionAccessPoint(
64+
MULTI_REGION_ACCESS_POINT_NAME,
65+
accountId,
66+
listOf(usWestBucket, usEastBucket),
67+
)
68+
}
69+
70+
@AfterAll
71+
fun cleanup(): Unit = runBlocking {
72+
s3Control.deleteMultiRegionAccessPoint(MULTI_REGION_ACCESS_POINT_NAME, accountId)
73+
74+
deleteBucketAndAllContents(s3West, usWestBucket)
75+
deleteBucketAndAllContents(s3East, usEastBucket)
76+
77+
s3West.close()
78+
s3East.close()
79+
s3Control.close()
80+
}
81+
82+
@ParameterizedTest
83+
@MethodSource("signerProvider")
84+
fun testMultiRegionAccessPointOperation(signer: AwsSigner): Unit = runBlocking {
85+
println("Testing multi-region access point operations with $signer")
86+
87+
val s3SigV4a = s3West.withConfig {
88+
authSchemes = listOf(SigV4AsymmetricAuthScheme(signer))
89+
}
90+
91+
s3SigV4a.putObject {
92+
bucket = multiRegionAccessPointArn
93+
key = TEST_OBJECT_KEY
94+
}
95+
96+
s3SigV4a.deleteObject {
97+
bucket = multiRegionAccessPointArn
98+
key = TEST_OBJECT_KEY
99+
}
100+
}
101+
102+
fun signerProvider(): Stream<Arguments> = Stream.of(
103+
Arguments.of(DefaultAwsSigner),
104+
Arguments.of(CrtAwsSigner),
105+
)
106+
}
107+
108+
/**
109+
* Create a multi-region access point named [name] in account [accountId] with [buckets] buckets.
110+
* @return the ARN of the multi-region access point that was created
111+
*/
112+
private suspend fun S3ControlClient.createMultiRegionAccessPoint(
113+
name: String,
114+
accountId: String,
115+
buckets: List<String>,
116+
): String {
117+
println("Creating multi-region access point: $name")
118+
119+
val requestTokenArn = checkNotNull(
120+
createMultiRegionAccessPoint {
121+
this.accountId = accountId
122+
details {
123+
this.name = name
124+
this.regions = buckets.map { Region { bucket = it } }
125+
}
126+
}.requestTokenArn,
127+
) { "createMultiRegionAccessPoint requestTokenArn was unexpectedly null" }
128+
129+
waitUntilOperationCompletes("createMultiRegionAccessPoint", accountId, requestTokenArn, 10.minutes)
130+
131+
return getMultiRegionAccessPointArn(name, accountId)
132+
}
133+
134+
private suspend fun S3ControlClient.getMultiRegionAccessPointArn(
135+
name: String,
136+
accountId: String,
137+
): String = getMultiRegionAccessPoint {
138+
this.name = name
139+
this.accountId = accountId
140+
}.accessPoint?.alias?.let {
141+
"arn:aws:s3::$accountId:accesspoint/$it"
142+
} ?: throw IllegalStateException("Failed to get ARN for multi-region access point $name")
143+
144+
private suspend fun S3ControlClient.deleteMultiRegionAccessPoint(
145+
name: String,
146+
accountId: String,
147+
) {
148+
println("Deleting multi-region access point $name")
149+
150+
val requestTokenArn = checkNotNull(
151+
deleteMultiRegionAccessPoint {
152+
this.accountId = accountId
153+
details {
154+
this.name = name
155+
}
156+
}.requestTokenArn,
157+
) { "deleteMultiRegionAccessPoint requestTokenArn was unexpectedly null" }
158+
159+
waitUntilOperationCompletes("deleteMultiRegionAccessPoint", accountId, requestTokenArn, 5.minutes)
160+
}
161+
162+
/**
163+
* Continuously poll the status of [requestTokenArn] until its status is "SUCCEEDED" or [timeout] duration has passed.
164+
*/
165+
private suspend fun S3ControlClient.waitUntilOperationCompletes(
166+
operation: String,
167+
accountId: String,
168+
requestTokenArn: String,
169+
timeout: Duration,
170+
) = withTimeout(timeout) {
171+
var status: String? = null
172+
173+
while (true) {
174+
val latestStatus = describeMultiRegionAccessPointOperation {
175+
this.accountId = accountId
176+
this.requestTokenArn = requestTokenArn
177+
}.asyncOperation?.requestStatus
178+
179+
when (latestStatus) {
180+
"SUCCEEDED" -> {
181+
println("$operation operation succeeded.")
182+
return@withTimeout
183+
}
184+
"FAILED" -> throw IllegalStateException("$operation operation failed")
185+
else -> {
186+
if (status == null || latestStatus != status) {
187+
println("Waiting for $operation to complete. Status: $latestStatus ")
188+
status = latestStatus
189+
}
190+
}
191+
}
192+
193+
delay(10.seconds) // Avoid constant status checks
194+
}
195+
}

services/s3/e2eTest/src/MutliRegionAccessPointTest.kt

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

0 commit comments

Comments
 (0)