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

fix: add customization for models missing SIGV4A trait #1171

Merged
merged 12 commits into from
Jan 17, 2024
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to have some tests for this interceptor

Original file line number Diff line number Diff line change
@@ -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<Any, Any, HttpRequest?, HttpResponse?>): Result<Any> {
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)
}
}
Original file line number Diff line number Diff line change
@@ -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<UnsupportedSigningAlgorithmException>(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<UnsupportedSigningAlgorithmException>(exception)
assertEquals(exception.signingAlgorithm, AwsSigningAlgorithm.SIGV4)
assertEquals("SIGV4 support is not yet implemented for the default signer.", exception.message)
}
}

private fun context(response: Result<Any>) =
object : ResponseInterceptorContext<Any, Any, HttpRequest?, HttpResponse?> {
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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ServiceTrait>().arnNamespace)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix: This should fallback to the sigv4 trait if the arnNamespace isn't present/doesn't match the sigv4 trait.

This value SHOULD match the arnNamespace property of the aws.api#service trait if present and the name property of the aws.auth#sigv4 trait.

.build(),
AuthTrait(mutableSetOf(SigV4ATrait.ID, SigV4Trait.ID)),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix: This isn't right. (1) if the service has an auth trait applied we need to respect it and modify it rather than replace it. (2) the auth trait is a prioritized list, this places sigv4a in front of sigv4 which if we configure sigv4a by default then every operation would fail if endpoint rules doesn't return anything for an auth option. The reason it doesn't seem to for S3 right now is they are returning sigv4 and/or sigv4a from their endpoint provider for pretty much every branch. Nothing in endpoint rules dictates this and indeed Eventbridge doesn't appear to return sigv4 anywhere just sigv4a for specific branches (which we appear to have already triggered a customer issue).

See auth trait spec:

),
)
.build()
false -> shape
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ServiceShape>(settings.service).isS3
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix: This doesn't match the set of sdkIds where we apply the sigv4a trait customization


override fun customizeMiddleware(
ctx: ProtocolGenerator.GenerationContext,
resolved: List<ProtocolMiddleware>,
): List<ProtocolMiddleware> = 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,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -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<SigV4ATrait>()
val authTrait = service.expectTrait<AuthTrait>()

assertTrue(authTrait.valueSet.contains(SigV4Trait.ID))
assertTrue(authTrait.valueSet.contains(SigV4ATrait.ID))
assertEquals("exampleservice", sigV4ATrait.name)
}
}
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading