Skip to content

Commit 6017f93

Browse files
authored
fix: add customization for models missing SIGV4A trait (#1171)
1 parent abceb1a commit 6017f93

File tree

9 files changed

+281
-5
lines changed

9 files changed

+281
-5
lines changed
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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.runtime.http.interceptors
6+
7+
import aws.sdk.kotlin.runtime.InternalSdkApi
8+
import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigningAlgorithm
9+
import aws.smithy.kotlin.runtime.auth.awssigning.UnsupportedSigningAlgorithmException
10+
import aws.smithy.kotlin.runtime.client.ResponseInterceptorContext
11+
import aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor
12+
import aws.smithy.kotlin.runtime.http.request.HttpRequest
13+
import aws.smithy.kotlin.runtime.http.response.HttpResponse
14+
15+
// FIXME: Remove this once sigV4a is supported by default AWS signer
16+
/**
17+
* Looks for an unsupported signing algorithm error caused by sigV4a.
18+
* If so it sends users to a section in the AWS SDK for Kotlin documentation on how to fix it.
19+
*/
20+
@InternalSdkApi
21+
public class UnsupportedSigningAlgorithmInterceptor : HttpInterceptor {
22+
override suspend fun modifyBeforeCompletion(context: ResponseInterceptorContext<Any, Any, HttpRequest?, HttpResponse?>): Result<Any> {
23+
context.response.exceptionOrNull()?.let {
24+
if (it is UnsupportedSigningAlgorithmException && it.signingAlgorithm == AwsSigningAlgorithm.SIGV4_ASYMMETRIC) {
25+
return Result.failure(
26+
it, // TODO: Add a message and link pointing to AWS SDK for Kotlin developer guide.
27+
)
28+
}
29+
}
30+
return super.modifyBeforeCompletion(context)
31+
}
32+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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.runtime.http.interceptors
6+
7+
import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigningAlgorithm
8+
import aws.smithy.kotlin.runtime.auth.awssigning.UnsupportedSigningAlgorithmException
9+
import aws.smithy.kotlin.runtime.client.ResponseInterceptorContext
10+
import aws.smithy.kotlin.runtime.client.SdkClientOption
11+
import aws.smithy.kotlin.runtime.http.request.HttpRequest
12+
import aws.smithy.kotlin.runtime.http.response.HttpResponse
13+
import aws.smithy.kotlin.runtime.operation.ExecutionContext
14+
import kotlinx.coroutines.test.runTest
15+
import kotlin.test.Test
16+
import kotlin.test.assertEquals
17+
import kotlin.test.assertIs
18+
import kotlin.test.assertTrue
19+
20+
class UnsupportedSigningAlgorithmInterceptorTest {
21+
@Test
22+
fun testUnsupportedSigningAlgorithmSigV4a() = runTest {
23+
val result =
24+
UnsupportedSigningAlgorithmInterceptor()
25+
.modifyBeforeCompletion(
26+
context(
27+
Result.failure(
28+
UnsupportedSigningAlgorithmException(
29+
"SIGV4A support is not yet implemented for the default signer.",
30+
AwsSigningAlgorithm.SIGV4_ASYMMETRIC,
31+
),
32+
),
33+
),
34+
)
35+
36+
val exception = result.exceptionOrNull()
37+
38+
assertTrue(result.isFailure)
39+
assertIs<UnsupportedSigningAlgorithmException>(exception)
40+
assertEquals(exception.signingAlgorithm, AwsSigningAlgorithm.SIGV4_ASYMMETRIC)
41+
assertEquals("SIGV4A support is not yet implemented for the default signer.", exception.message)
42+
}
43+
44+
@Test
45+
fun testUnsupportedSigningAlgorithmNotSigV4a() = runTest {
46+
val result =
47+
UnsupportedSigningAlgorithmInterceptor()
48+
.modifyBeforeCompletion(
49+
context(
50+
Result.failure(
51+
UnsupportedSigningAlgorithmException(
52+
"SIGV4 support is not yet implemented for the default signer.",
53+
AwsSigningAlgorithm.SIGV4,
54+
),
55+
),
56+
),
57+
)
58+
59+
val exception = result.exceptionOrNull()
60+
61+
assertTrue(result.isFailure)
62+
assertIs<UnsupportedSigningAlgorithmException>(exception)
63+
assertEquals(exception.signingAlgorithm, AwsSigningAlgorithm.SIGV4)
64+
assertEquals("SIGV4 support is not yet implemented for the default signer.", exception.message)
65+
}
66+
}
67+
68+
private fun context(response: Result<Any>) =
69+
object : ResponseInterceptorContext<Any, Any, HttpRequest?, HttpResponse?> {
70+
override val executionContext = ExecutionContext.build { attributes[SdkClientOption.OperationName] = "test" }
71+
override val request = Unit
72+
override val response = response
73+
override val protocolRequest = HttpRequest { }
74+
override val protocolResponse = null
75+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ 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")
6263
}
6364

6465
object Retries {
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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.codegen.customization
6+
7+
import software.amazon.smithy.aws.traits.ServiceTrait
8+
import software.amazon.smithy.aws.traits.auth.SigV4ATrait
9+
import software.amazon.smithy.aws.traits.auth.SigV4Trait
10+
import software.amazon.smithy.kotlin.codegen.KotlinSettings
11+
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
12+
import software.amazon.smithy.kotlin.codegen.model.expectTrait
13+
import software.amazon.smithy.model.Model
14+
import software.amazon.smithy.model.shapes.ServiceShape
15+
import software.amazon.smithy.model.traits.AuthTrait
16+
import software.amazon.smithy.model.transform.ModelTransformer
17+
18+
// FIXME: Remove services from customization or customization entirely when/if services add sigV4a trait to models
19+
/**
20+
* Adds the sigV4A trait to services that don't model their sigV4A usage
21+
* NOTE: Won't add sigV4 trait (services that support sigV4A MUST support sigV4)
22+
*/
23+
class SigV4AsymmetricTraitCustomization : KotlinIntegration {
24+
// Needs to happen before the `SigV4AsymmetricAuthSchemeIntegration` & `SigV4AuthSchemeIntegration` (-50 & -50)
25+
override val order: Byte = -60
26+
27+
override fun enabledForService(model: Model, settings: KotlinSettings): Boolean =
28+
when (settings.sdkId.lowercase()) {
29+
"s3", "eventbridge", "cloudfront keyvaluestore" -> true
30+
else -> false
31+
}
32+
33+
override fun preprocessModel(model: Model, settings: KotlinSettings): Model =
34+
ModelTransformer.create().mapShapes(model) { shape ->
35+
when (shape.isServiceShape) {
36+
true ->
37+
(shape as ServiceShape)
38+
.toBuilder()
39+
.addTraits(
40+
mutableSetOf(
41+
SigV4ATrait
42+
.builder()
43+
.name(shape.expectTrait<ServiceTrait>().arnNamespace)
44+
.build(),
45+
AuthTrait(mutableSetOf(SigV4ATrait.ID, SigV4Trait.ID)),
46+
),
47+
)
48+
.build()
49+
false -> shape
50+
}
51+
}
52+
}

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import software.amazon.smithy.model.transform.ModelTransformer
2020
import software.amazon.smithy.rulesengine.traits.ClientContextParamsTrait
2121

2222
/**
23-
* Integration to inject s3-related client config builtins for endpoint resolution in place of the corresponding client
23+
* Integration to inject s3-related client config builtins for endpoint resolution & multi-region access points in place of the corresponding client
2424
* context params.
2525
*/
2626
class ClientConfigIntegration : KotlinIntegration {
@@ -54,10 +54,9 @@ class ClientConfigIntegration : KotlinIntegration {
5454
""".trimIndent()
5555
}
5656

57-
// FIXME: default signer doesn't yet implement sigv4a, default to mrap OFF until it does
5857
val DisableMrapProp: ConfigProperty = ConfigProperty {
5958
name = "disableMrap"
60-
useSymbolWithNullableBuilder(KotlinTypes.Boolean, "true")
59+
useSymbolWithNullableBuilder(KotlinTypes.Boolean, "false")
6160
documentation = """
6261
Flag to disable [S3 multi-region access points](https://docs.aws.amazon.com/AmazonS3/latest/userguide/MultiRegionAccessPoints.html).
6362
""".trimIndent()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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.codegen.customization.s3
6+
7+
import aws.sdk.kotlin.codegen.AwsRuntimeTypes
8+
import software.amazon.smithy.kotlin.codegen.KotlinSettings
9+
import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
10+
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
11+
import software.amazon.smithy.kotlin.codegen.model.expectShape
12+
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator
13+
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware
14+
import software.amazon.smithy.model.Model
15+
import software.amazon.smithy.model.shapes.OperationShape
16+
import software.amazon.smithy.model.shapes.ServiceShape
17+
18+
// FIXME: Remove this once sigV4a is supported by default AWS signer
19+
/**
20+
* Registers an interceptor for S3 to deal with the default signer not supporting sigV4a
21+
* See: [aws.sdk.kotlin.runtime.http.interceptors.UnsupportedSigningAlgorithmInterceptor]
22+
*/
23+
class UnsupportedSigningAlgorithmIntegration : KotlinIntegration {
24+
override fun enabledForService(model: Model, settings: KotlinSettings): Boolean =
25+
model.expectShape<ServiceShape>(settings.service).isS3
26+
27+
override fun customizeMiddleware(
28+
ctx: ProtocolGenerator.GenerationContext,
29+
resolved: List<ProtocolMiddleware>,
30+
): List<ProtocolMiddleware> = resolved + UnsupportedSigningAlgorithmMiddleware
31+
}
32+
33+
private val UnsupportedSigningAlgorithmMiddleware = object : ProtocolMiddleware {
34+
override val name: String = "UnsupportedSigningAlgorithmMiddleware"
35+
36+
override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) {
37+
writer.write(
38+
"op.interceptors.add(#T())",
39+
AwsRuntimeTypes.Http.Interceptors.UnsupportedSigningAlgorithmInterceptor,
40+
)
41+
}
42+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,5 @@ aws.sdk.kotlin.codegen.customization.route53.TrimResourcePrefix
3636
aws.sdk.kotlin.codegen.customization.route53.ChangeResourceRecordSetsUnmarshallingIntegration
3737
aws.sdk.kotlin.codegen.customization.ec2.EC2MakePrimitivesOptional
3838
aws.sdk.kotlin.codegen.customization.RemoveDefaults
39+
aws.sdk.kotlin.codegen.customization.s3.UnsupportedSigningAlgorithmIntegration
40+
aws.sdk.kotlin.codegen.customization.SigV4AsymmetricTraitCustomization
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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.codegen.customization
6+
7+
import software.amazon.smithy.aws.traits.auth.SigV4ATrait
8+
import software.amazon.smithy.aws.traits.auth.SigV4Trait
9+
import software.amazon.smithy.kotlin.codegen.KotlinSettings
10+
import software.amazon.smithy.kotlin.codegen.model.expectTrait
11+
import software.amazon.smithy.kotlin.codegen.test.toSmithyModel
12+
import software.amazon.smithy.model.shapes.ShapeId
13+
import software.amazon.smithy.model.traits.AuthTrait
14+
import kotlin.test.Test
15+
import kotlin.test.assertEquals
16+
import kotlin.test.assertTrue
17+
18+
class SigV4AsymmetricTraitCustomizationTest {
19+
private val testModel = """
20+
namespace smithy.example
21+
22+
use aws.protocols#awsJson1_0
23+
use aws.auth#sigv4
24+
use aws.api#service
25+
26+
@awsJson1_0
27+
@sigv4(name: "exampleservice")
28+
@service(
29+
sdkId: "example"
30+
arnNamespace: "exampleservice"
31+
)
32+
service Example {
33+
version: "1.0.0",
34+
operations: [GetFoo]
35+
}
36+
37+
operation GetFoo {
38+
input: GetFooInput
39+
}
40+
41+
operation GetNotFoo {
42+
input: GetFooInput
43+
}
44+
45+
structure GetFooInput {
46+
payload: String
47+
}
48+
""".toSmithyModel()
49+
50+
@Test
51+
fun testCustomizationAppliedCorrectly() {
52+
val customizedModel = SigV4AsymmetricTraitCustomization()
53+
.preprocessModel(
54+
testModel,
55+
KotlinSettings(
56+
ShapeId.from("smithy.example#Example"),
57+
KotlinSettings.PackageSettings("example", "1.0.0"),
58+
"example",
59+
),
60+
)
61+
62+
assertTrue(customizedModel.appliedTraits.contains(SigV4ATrait.ID))
63+
assertTrue(customizedModel.appliedTraits.contains(AuthTrait.ID))
64+
65+
val service = customizedModel.getShape(customizedModel.serviceShapes.first().id).get()
66+
val sigV4ATrait = service.expectTrait<SigV4ATrait>()
67+
val authTrait = service.expectTrait<AuthTrait>()
68+
69+
assertTrue(authTrait.valueSet.contains(SigV4Trait.ID))
70+
assertTrue(authTrait.valueSet.contains(SigV4ATrait.ID))
71+
assertEquals("exampleservice", sigV4ATrait.name)
72+
}
73+
}

gradle/libs.versions.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ coroutines-version = "1.7.3"
99
atomicfu-version = "0.23.1"
1010

1111
# smithy-kotlin codegen and runtime are versioned separately
12-
smithy-kotlin-runtime-version = "1.0.9"
13-
smithy-kotlin-codegen-version = "0.30.10"
12+
smithy-kotlin-runtime-version = "1.0.10"
13+
smithy-kotlin-codegen-version = "0.30.11"
1414

1515
# codegen
1616
smithy-version = "1.42.0"

0 commit comments

Comments
 (0)