-
Notifications
You must be signed in to change notification settings - Fork 49
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
Changes from all commits
8b272e8
3e18f69
1b5daa5
22d1727
1dbcf5a
15d05ac
01ba0b7
84ab837
fadf63d
9f3ecc0
7d27b86
d1256c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
---|---|---|
@@ -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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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.
|
||
.build(), | ||
AuthTrait(mutableSetOf(SigV4ATrait.ID, SigV4Trait.ID)), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fix: This isn't right. (1) if the service has an See auth trait spec: |
||
), | ||
) | ||
.build() | ||
false -> shape | ||
} | ||
} | ||
} |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
---|---|---|
@@ -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) | ||
} | ||
} |
There was a problem hiding this comment.
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