diff --git a/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/interceptors/UnsupportedSigningAlgorithmInterceptor.kt b/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/interceptors/UnsupportedSigningAlgorithmInterceptor.kt new file mode 100644 index 00000000000..953d4400722 --- /dev/null +++ b/aws-runtime/aws-http/common/src/aws/sdk/kotlin/runtime/http/interceptors/UnsupportedSigningAlgorithmInterceptor.kt @@ -0,0 +1,32 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.runtime.http.interceptors + +import aws.sdk.kotlin.runtime.InternalSdkApi +import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigningAlgorithm +import aws.smithy.kotlin.runtime.auth.awssigning.UnsupportedSigningAlgorithmException +import aws.smithy.kotlin.runtime.client.ResponseInterceptorContext +import aws.smithy.kotlin.runtime.http.interceptors.HttpInterceptor +import aws.smithy.kotlin.runtime.http.request.HttpRequest +import aws.smithy.kotlin.runtime.http.response.HttpResponse + +// FIXME: Remove this once sigV4a is supported by default AWS signer +/** + * Looks for an unsupported signing algorithm error caused by sigV4a. + * If so it sends users to a section in the AWS SDK for Kotlin documentation on how to fix it. + */ +@InternalSdkApi +public class UnsupportedSigningAlgorithmInterceptor : HttpInterceptor { + override suspend fun modifyBeforeCompletion(context: ResponseInterceptorContext): Result { + context.response.exceptionOrNull()?.let { + if (it is UnsupportedSigningAlgorithmException && it.signingAlgorithm == AwsSigningAlgorithm.SIGV4_ASYMMETRIC) { + return Result.failure( + it, // TODO: Add a message and link pointing to AWS SDK for Kotlin developer guide. + ) + } + } + return super.modifyBeforeCompletion(context) + } +} diff --git a/aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/interceptors/UnsupportedSigningAlgorithmInterceptorTest.kt b/aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/interceptors/UnsupportedSigningAlgorithmInterceptorTest.kt new file mode 100644 index 00000000000..7a5020aab9f --- /dev/null +++ b/aws-runtime/aws-http/common/test/aws/sdk/kotlin/runtime/http/interceptors/UnsupportedSigningAlgorithmInterceptorTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.runtime.http.interceptors + +import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigningAlgorithm +import aws.smithy.kotlin.runtime.auth.awssigning.UnsupportedSigningAlgorithmException +import aws.smithy.kotlin.runtime.client.ResponseInterceptorContext +import aws.smithy.kotlin.runtime.client.SdkClientOption +import aws.smithy.kotlin.runtime.http.request.HttpRequest +import aws.smithy.kotlin.runtime.http.response.HttpResponse +import aws.smithy.kotlin.runtime.operation.ExecutionContext +import kotlinx.coroutines.test.runTest +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertTrue + +class UnsupportedSigningAlgorithmInterceptorTest { + @Test + fun testUnsupportedSigningAlgorithmSigV4a() = runTest { + val result = + UnsupportedSigningAlgorithmInterceptor() + .modifyBeforeCompletion( + context( + Result.failure( + UnsupportedSigningAlgorithmException( + "SIGV4A support is not yet implemented for the default signer.", + AwsSigningAlgorithm.SIGV4_ASYMMETRIC, + ), + ), + ), + ) + + val exception = result.exceptionOrNull() + + assertTrue(result.isFailure) + assertIs(exception) + assertEquals(exception.signingAlgorithm, AwsSigningAlgorithm.SIGV4_ASYMMETRIC) + assertEquals("SIGV4A support is not yet implemented for the default signer.", exception.message) + } + + @Test + fun testUnsupportedSigningAlgorithmNotSigV4a() = runTest { + val result = + UnsupportedSigningAlgorithmInterceptor() + .modifyBeforeCompletion( + context( + Result.failure( + UnsupportedSigningAlgorithmException( + "SIGV4 support is not yet implemented for the default signer.", + AwsSigningAlgorithm.SIGV4, + ), + ), + ), + ) + + val exception = result.exceptionOrNull() + + assertTrue(result.isFailure) + assertIs(exception) + assertEquals(exception.signingAlgorithm, AwsSigningAlgorithm.SIGV4) + assertEquals("SIGV4 support is not yet implemented for the default signer.", exception.message) + } +} + +private fun context(response: Result) = + object : ResponseInterceptorContext { + override val executionContext = ExecutionContext.build { attributes[SdkClientOption.OperationName] = "test" } + override val request = Unit + override val response = response + override val protocolRequest = HttpRequest { } + override val protocolResponse = null + } diff --git a/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsRuntimeTypes.kt b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsRuntimeTypes.kt index 438eef7a95e..e36fb00cd69 100644 --- a/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsRuntimeTypes.kt +++ b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/AwsRuntimeTypes.kt @@ -59,6 +59,7 @@ object AwsRuntimeTypes { object Http : RuntimeTypePackage(AwsKotlinDependency.AWS_HTTP) { object Interceptors : RuntimeTypePackage(AwsKotlinDependency.AWS_HTTP, "interceptors") { val AddUserAgentMetadataInterceptor = symbol("AddUserAgentMetadataInterceptor") + val UnsupportedSigningAlgorithmInterceptor = symbol("UnsupportedSigningAlgorithmInterceptor") } object Retries { diff --git a/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/SigV4AsymmetricTraitCustomization.kt b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/SigV4AsymmetricTraitCustomization.kt new file mode 100644 index 00000000000..0fdabe539dd --- /dev/null +++ b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/SigV4AsymmetricTraitCustomization.kt @@ -0,0 +1,52 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.codegen.customization + +import software.amazon.smithy.aws.traits.ServiceTrait +import software.amazon.smithy.aws.traits.auth.SigV4ATrait +import software.amazon.smithy.aws.traits.auth.SigV4Trait +import software.amazon.smithy.kotlin.codegen.KotlinSettings +import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration +import software.amazon.smithy.kotlin.codegen.model.expectTrait +import software.amazon.smithy.model.Model +import software.amazon.smithy.model.shapes.ServiceShape +import software.amazon.smithy.model.traits.AuthTrait +import software.amazon.smithy.model.transform.ModelTransformer + +// FIXME: Remove services from customization or customization entirely when/if services add sigV4a trait to models +/** + * Adds the sigV4A trait to services that don't model their sigV4A usage + * NOTE: Won't add sigV4 trait (services that support sigV4A MUST support sigV4) + */ +class SigV4AsymmetricTraitCustomization : KotlinIntegration { + // Needs to happen before the `SigV4AsymmetricAuthSchemeIntegration` & `SigV4AuthSchemeIntegration` (-50 & -50) + override val order: Byte = -60 + + override fun enabledForService(model: Model, settings: KotlinSettings): Boolean = + when (settings.sdkId.lowercase()) { + "s3", "eventbridge", "cloudfront keyvaluestore" -> true + else -> false + } + + override fun preprocessModel(model: Model, settings: KotlinSettings): Model = + ModelTransformer.create().mapShapes(model) { shape -> + when (shape.isServiceShape) { + true -> + (shape as ServiceShape) + .toBuilder() + .addTraits( + mutableSetOf( + SigV4ATrait + .builder() + .name(shape.expectTrait().arnNamespace) + .build(), + AuthTrait(mutableSetOf(SigV4ATrait.ID, SigV4Trait.ID)), + ), + ) + .build() + false -> shape + } + } +} diff --git a/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/ClientConfigIntegration.kt b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/ClientConfigIntegration.kt index 5926c47e98e..d7bdb15dd05 100644 --- a/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/ClientConfigIntegration.kt +++ b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/ClientConfigIntegration.kt @@ -20,7 +20,7 @@ import software.amazon.smithy.model.transform.ModelTransformer import software.amazon.smithy.rulesengine.traits.ClientContextParamsTrait /** - * Integration to inject s3-related client config builtins for endpoint resolution in place of the corresponding client + * Integration to inject s3-related client config builtins for endpoint resolution & multi-region access points in place of the corresponding client * context params. */ class ClientConfigIntegration : KotlinIntegration { @@ -54,10 +54,9 @@ class ClientConfigIntegration : KotlinIntegration { """.trimIndent() } - // FIXME: default signer doesn't yet implement sigv4a, default to mrap OFF until it does val DisableMrapProp: ConfigProperty = ConfigProperty { name = "disableMrap" - useSymbolWithNullableBuilder(KotlinTypes.Boolean, "true") + useSymbolWithNullableBuilder(KotlinTypes.Boolean, "false") documentation = """ Flag to disable [S3 multi-region access points](https://docs.aws.amazon.com/AmazonS3/latest/userguide/MultiRegionAccessPoints.html). """.trimIndent() diff --git a/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/UnsupportedSigningAlgorithmIntegration.kt b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/UnsupportedSigningAlgorithmIntegration.kt new file mode 100644 index 00000000000..24dc78e7e07 --- /dev/null +++ b/codegen/aws-sdk-codegen/src/main/kotlin/aws/sdk/kotlin/codegen/customization/s3/UnsupportedSigningAlgorithmIntegration.kt @@ -0,0 +1,42 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.codegen.customization.s3 + +import aws.sdk.kotlin.codegen.AwsRuntimeTypes +import software.amazon.smithy.kotlin.codegen.KotlinSettings +import software.amazon.smithy.kotlin.codegen.core.KotlinWriter +import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration +import software.amazon.smithy.kotlin.codegen.model.expectShape +import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator +import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolMiddleware +import software.amazon.smithy.model.Model +import software.amazon.smithy.model.shapes.OperationShape +import software.amazon.smithy.model.shapes.ServiceShape + +// FIXME: Remove this once sigV4a is supported by default AWS signer +/** + * Registers an interceptor for S3 to deal with the default signer not supporting sigV4a + * See: [aws.sdk.kotlin.runtime.http.interceptors.UnsupportedSigningAlgorithmInterceptor] + */ +class UnsupportedSigningAlgorithmIntegration : KotlinIntegration { + override fun enabledForService(model: Model, settings: KotlinSettings): Boolean = + model.expectShape(settings.service).isS3 + + override fun customizeMiddleware( + ctx: ProtocolGenerator.GenerationContext, + resolved: List, + ): List = resolved + UnsupportedSigningAlgorithmMiddleware +} + +private val UnsupportedSigningAlgorithmMiddleware = object : ProtocolMiddleware { + override val name: String = "UnsupportedSigningAlgorithmMiddleware" + + override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) { + writer.write( + "op.interceptors.add(#T())", + AwsRuntimeTypes.Http.Interceptors.UnsupportedSigningAlgorithmInterceptor, + ) + } +} diff --git a/codegen/aws-sdk-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration b/codegen/aws-sdk-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration index 9c1bcbd6748..f0f4c2e49d6 100644 --- a/codegen/aws-sdk-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration +++ b/codegen/aws-sdk-codegen/src/main/resources/META-INF/services/software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration @@ -36,3 +36,5 @@ aws.sdk.kotlin.codegen.customization.route53.TrimResourcePrefix aws.sdk.kotlin.codegen.customization.route53.ChangeResourceRecordSetsUnmarshallingIntegration aws.sdk.kotlin.codegen.customization.ec2.EC2MakePrimitivesOptional aws.sdk.kotlin.codegen.customization.RemoveDefaults +aws.sdk.kotlin.codegen.customization.s3.UnsupportedSigningAlgorithmIntegration +aws.sdk.kotlin.codegen.customization.SigV4AsymmetricTraitCustomization diff --git a/codegen/aws-sdk-codegen/src/test/kotlin/aws/sdk/kotlin/codegen/customization/SigV4AsymmetricTraitCustomizationTest.kt b/codegen/aws-sdk-codegen/src/test/kotlin/aws/sdk/kotlin/codegen/customization/SigV4AsymmetricTraitCustomizationTest.kt new file mode 100644 index 00000000000..870c0441934 --- /dev/null +++ b/codegen/aws-sdk-codegen/src/test/kotlin/aws/sdk/kotlin/codegen/customization/SigV4AsymmetricTraitCustomizationTest.kt @@ -0,0 +1,73 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +package aws.sdk.kotlin.codegen.customization + +import software.amazon.smithy.aws.traits.auth.SigV4ATrait +import software.amazon.smithy.aws.traits.auth.SigV4Trait +import software.amazon.smithy.kotlin.codegen.KotlinSettings +import software.amazon.smithy.kotlin.codegen.model.expectTrait +import software.amazon.smithy.kotlin.codegen.test.toSmithyModel +import software.amazon.smithy.model.shapes.ShapeId +import software.amazon.smithy.model.traits.AuthTrait +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class SigV4AsymmetricTraitCustomizationTest { + private val testModel = """ + namespace smithy.example + + use aws.protocols#awsJson1_0 + use aws.auth#sigv4 + use aws.api#service + + @awsJson1_0 + @sigv4(name: "exampleservice") + @service( + sdkId: "example" + arnNamespace: "exampleservice" + ) + service Example { + version: "1.0.0", + operations: [GetFoo] + } + + operation GetFoo { + input: GetFooInput + } + + operation GetNotFoo { + input: GetFooInput + } + + structure GetFooInput { + payload: String + } + """.toSmithyModel() + + @Test + fun testCustomizationAppliedCorrectly() { + val customizedModel = SigV4AsymmetricTraitCustomization() + .preprocessModel( + testModel, + KotlinSettings( + ShapeId.from("smithy.example#Example"), + KotlinSettings.PackageSettings("example", "1.0.0"), + "example", + ), + ) + + assertTrue(customizedModel.appliedTraits.contains(SigV4ATrait.ID)) + assertTrue(customizedModel.appliedTraits.contains(AuthTrait.ID)) + + val service = customizedModel.getShape(customizedModel.serviceShapes.first().id).get() + val sigV4ATrait = service.expectTrait() + val authTrait = service.expectTrait() + + assertTrue(authTrait.valueSet.contains(SigV4Trait.ID)) + assertTrue(authTrait.valueSet.contains(SigV4ATrait.ID)) + assertEquals("exampleservice", sigV4ATrait.name) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c1f5fd87f66..b4e2804ce73 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,8 +9,8 @@ coroutines-version = "1.7.3" atomicfu-version = "0.23.1" # smithy-kotlin codegen and runtime are versioned separately -smithy-kotlin-runtime-version = "1.0.9" -smithy-kotlin-codegen-version = "0.30.10" +smithy-kotlin-runtime-version = "1.0.10" +smithy-kotlin-codegen-version = "0.30.11" # codegen smithy-version = "1.42.0"