Skip to content

Commit 47a84c5

Browse files
authored
feat: add SIGV4A auth scheme integration (#1023)
1 parent 6d2c1bc commit 47a84c5

File tree

10 files changed

+319
-44
lines changed

10 files changed

+319
-44
lines changed

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/CodegenVisitor.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,15 @@ package software.amazon.smithy.kotlin.codegen
88
import software.amazon.smithy.build.FileManifest
99
import software.amazon.smithy.build.PluginContext
1010
import software.amazon.smithy.codegen.core.SymbolProvider
11-
import software.amazon.smithy.kotlin.codegen.core.*
11+
import software.amazon.smithy.kotlin.codegen.core.GenerationContext
12+
import software.amazon.smithy.kotlin.codegen.core.KotlinDelegator
13+
import software.amazon.smithy.kotlin.codegen.core.KotlinDependency
14+
import software.amazon.smithy.kotlin.codegen.core.toRenderingContext
1215
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
13-
import software.amazon.smithy.kotlin.codegen.model.*
16+
import software.amazon.smithy.kotlin.codegen.model.OperationNormalizer
17+
import software.amazon.smithy.kotlin.codegen.model.getEndpointRules
18+
import software.amazon.smithy.kotlin.codegen.model.getEndpointTests
19+
import software.amazon.smithy.kotlin.codegen.model.hasTrait
1420
import software.amazon.smithy.kotlin.codegen.rendering.*
1521
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ApplicationProtocol
1622
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator
@@ -44,10 +50,9 @@ class CodegenVisitor(context: PluginContext) : ShapeVisitor.Default<Unit>() {
4450
LOGGER.info("Discovering KotlinIntegration providers...")
4551
integrations = ServiceLoader.load(KotlinIntegration::class.java, classLoader)
4652
.onEach { integration -> LOGGER.info("Loaded KotlinIntegration: ${integration.javaClass.name}") }
47-
.filter { integration -> integration.enabledForService(context.model, settings) }
53+
.filter { integration -> integration.enabledForService(context.model, settings) } // TODO: Change so we don't filter until previous integrations model modifications are complete
4854
.onEach { integration -> LOGGER.info("Enabled KotlinIntegration: ${integration.javaClass.name}") }
4955
.sortedBy(KotlinIntegration::order)
50-
.toList()
5156

5257
LOGGER.info("Preprocessing model")
5358
// Model pre-processing:

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/core/RuntimeTypes.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ object RuntimeTypes {
323323
object HttpAuthAws : RuntimeTypePackage(KotlinDependency.HTTP_AUTH_AWS) {
324324
val AwsHttpSigner = symbol("AwsHttpSigner")
325325
val SigV4AuthScheme = symbol("SigV4AuthScheme")
326+
val SigV4AsymmetricAuthScheme = symbol("SigV4AsymmetricAuthScheme")
326327
val mergeAuthOptions = symbol("mergeAuthOptions")
327328
val sigV4 = symbol("sigV4")
328329
val sigV4A = symbol("sigV4A")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package software.amazon.smithy.kotlin.codegen.model.knowledge
6+
7+
import software.amazon.smithy.aws.traits.auth.SigV4ATrait
8+
import software.amazon.smithy.kotlin.codegen.model.expectTrait
9+
import software.amazon.smithy.model.shapes.ServiceShape
10+
11+
/**
12+
* AWS Signature Version 4 Asymmetric signing utils
13+
*/
14+
object AwsSignatureVersion4Asymmetric {
15+
/**
16+
* Get the SigV4ATrait auth name to sign request for
17+
*
18+
* @param serviceShape service shape for the API
19+
* @return the service name to use in the credential scope to sign for
20+
*/
21+
fun signingServiceName(serviceShape: ServiceShape): String {
22+
val sigv4aTrait = serviceShape.expectTrait<SigV4ATrait>()
23+
return sigv4aTrait.name
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package software.amazon.smithy.kotlin.codegen.rendering.auth
6+
7+
import software.amazon.smithy.aws.traits.auth.SigV4ATrait
8+
import software.amazon.smithy.aws.traits.auth.UnsignedPayloadTrait
9+
import software.amazon.smithy.codegen.core.Symbol
10+
import software.amazon.smithy.codegen.core.SymbolReference
11+
import software.amazon.smithy.kotlin.codegen.KotlinSettings
12+
import software.amazon.smithy.kotlin.codegen.core.*
13+
import software.amazon.smithy.kotlin.codegen.integration.AuthSchemeHandler
14+
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
15+
import software.amazon.smithy.kotlin.codegen.model.buildSymbol
16+
import software.amazon.smithy.kotlin.codegen.model.hasTrait
17+
import software.amazon.smithy.kotlin.codegen.model.knowledge.AwsSignatureVersion4Asymmetric
18+
import software.amazon.smithy.kotlin.codegen.rendering.protocol.*
19+
import software.amazon.smithy.model.Model
20+
import software.amazon.smithy.model.knowledge.ServiceIndex
21+
import software.amazon.smithy.model.shapes.OperationShape
22+
import software.amazon.smithy.model.shapes.ShapeId
23+
24+
/**
25+
* Register support for the `aws.auth#sigv4a` auth scheme.
26+
*/
27+
class SigV4AsymmetricAuthSchemeIntegration : KotlinIntegration {
28+
// Needs to happen after the `SigV4AsymmetricTraitCustomization` (-60).
29+
override val order: Byte = -50
30+
31+
// Needs to be true due to the way integrations are filtered out before application and sigV4a customization.
32+
// See 'CodegenVisitor' & 'SigV4AsymmetricTraitCustomization'
33+
override fun enabledForService(model: Model, settings: KotlinSettings): Boolean = true
34+
35+
override fun authSchemes(ctx: ProtocolGenerator.GenerationContext): List<AuthSchemeHandler> =
36+
if (modelHasSigV4aTrait(ctx)) listOf(SigV4AsymmetricAuthSchemeHandler()) else emptyList()
37+
}
38+
39+
private class SigV4AsymmetricAuthSchemeHandler : AuthSchemeHandler {
40+
override val authSchemeId: ShapeId = SigV4ATrait.ID
41+
42+
override val authSchemeIdSymbol: Symbol = buildSymbol {
43+
name = "AuthSchemeId.AwsSigV4Asymmetric"
44+
val ref = RuntimeTypes.Auth.Identity.AuthSchemeId
45+
objectRef = ref
46+
namespace = ref.namespace
47+
reference(ref, SymbolReference.ContextOption.USE)
48+
}
49+
50+
override fun identityProviderAdapterExpression(writer: KotlinWriter) {
51+
writer.write("config.credentialsProvider")
52+
}
53+
54+
override fun authSchemeProviderInstantiateAuthOptionExpr(
55+
ctx: ProtocolGenerator.GenerationContext,
56+
op: OperationShape?,
57+
writer: KotlinWriter,
58+
) {
59+
val expr = if (op?.hasTrait<UnsignedPayloadTrait>() == true) {
60+
"#T(unsignedPayload = true)"
61+
} else {
62+
"#T()"
63+
}
64+
writer.write(expr, RuntimeTypes.Auth.HttpAuthAws.sigV4A)
65+
}
66+
67+
override fun instantiateAuthSchemeExpr(ctx: ProtocolGenerator.GenerationContext, writer: KotlinWriter) {
68+
val signingService = AwsSignatureVersion4Asymmetric.signingServiceName(ctx.service)
69+
writer.write("#T(#T, #S)", RuntimeTypes.Auth.HttpAuthAws.SigV4AsymmetricAuthScheme, RuntimeTypes.Auth.Signing.AwsSigningStandard.DefaultAwsSigner, signingService)
70+
}
71+
}
72+
73+
internal fun modelHasSigV4aTrait(ctx: ProtocolGenerator.GenerationContext): Boolean =
74+
ServiceIndex
75+
.of(ctx.model)
76+
.getAuthSchemes(ctx.service)
77+
.values
78+
.any { it.javaClass == SigV4ATrait::class.java }

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/auth/SigV4AuthSchemeIntegration.kt

Lines changed: 41 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import java.util.*
3939
*/
4040
class SigV4AuthSchemeIntegration : KotlinIntegration {
4141
// Allow integrations to customize the service config props, later integrations take precedence
42+
// Needs to happen after the `SigV4AsymmetricTraitCustomization` (-60).
4243
override val order: Byte = -50
4344

4445
override fun enabledForService(model: Model, settings: KotlinSettings): Boolean =
@@ -51,22 +52,7 @@ class SigV4AuthSchemeIntegration : KotlinIntegration {
5152
resolved: List<ProtocolMiddleware>,
5253
): List<ProtocolMiddleware> = resolved + Sigv4SignedBodyHeaderMiddleware()
5354

54-
override fun additionalServiceConfigProps(ctx: CodegenContext): List<ConfigProperty> {
55-
val credentialsProviderProp = ConfigProperty {
56-
symbol = RuntimeTypes.Auth.Credentials.AwsCredentials.CredentialsProvider
57-
baseClass = RuntimeTypes.Auth.Credentials.AwsCredentials.CredentialsProviderConfig
58-
useNestedBuilderBaseClass()
59-
documentation = """
60-
The AWS credentials provider to use for authenticating requests.
61-
NOTE: The caller is responsible for managing the lifetime of the provider when set. The SDK
62-
client will not close it when the client is closed.
63-
""".trimIndent()
64-
65-
propertyType = ConfigPropertyType.Required()
66-
}
67-
68-
return listOf(credentialsProviderProp)
69-
}
55+
override fun additionalServiceConfigProps(ctx: CodegenContext): List<ConfigProperty> = listOf(credentialsProviderProp)
7056

7157
override fun customizeEndpointResolution(ctx: ProtocolGenerator.GenerationContext): EndpointCustomization =
7258
Sigv4EndpointCustomization
@@ -174,13 +160,14 @@ open class SigV4AuthSchemeHandler : AuthSchemeHandler {
174160
* Conditionally updates the operation context to set the signed body header attribute
175161
* e.g. to set `X-Amz-Content-Sha256` header.
176162
*/
177-
class Sigv4SignedBodyHeaderMiddleware : ProtocolMiddleware {
163+
internal class Sigv4SignedBodyHeaderMiddleware : ProtocolMiddleware {
178164
override val name: String = "Sigv4SignedBodyHeaderMiddleware"
179165

180166
override fun isEnabledFor(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Boolean {
181167
val hasEventStream = EventStreamIndex.of(ctx.model).getInputInfo(op).isPresent
182168
return hasEventStream || op.hasTrait<UnsignedPayloadTrait>()
183169
}
170+
184171
override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) {
185172
writer.write(
186173
"op.context.set(#T.SignedBodyHeader, #T.X_AMZ_CONTENT_SHA256)",
@@ -196,20 +183,20 @@ private object Sigv4EndpointCustomization : EndpointCustomization {
196183
)
197184
}
198185

199-
private fun String.toAuthOptionFactoryFn(): Symbol? =
200-
when (this) {
201-
"sigv4" -> RuntimeTypes.Auth.HttpAuthAws.sigV4
202-
"sigv4a" -> RuntimeTypes.Auth.HttpAuthAws.sigV4A
203-
else -> null
204-
}
205-
186+
// SigV4a requires SigV4 so SigV4 integration renders SigV4a auth scheme.
187+
// See comment in example model: https://smithy.io/2.0/aws/aws-auth.html?highlight=sigv4#aws-auth-sigv4a-trait
206188
private fun renderAuthSchemes(writer: KotlinWriter, authSchemes: Expression, expressionRenderer: ExpressionRenderer) {
207189
writer.writeInline("#T to ", RuntimeTypes.SmithyClient.Endpoints.SigningContextAttributeKey)
208190
writer.withBlock("listOf(", ")") {
209191
authSchemes.toNode().expectArrayNode().forEach {
210192
val scheme = it.expectObjectNode()
211193
val schemeName = scheme.expectStringMember("name").value
212-
val authFactoryFn = schemeName.toAuthOptionFactoryFn() ?: return@forEach
194+
195+
val authFactoryFn = when (schemeName) {
196+
"sigv4" -> RuntimeTypes.Auth.HttpAuthAws.sigV4
197+
"sigv4a" -> RuntimeTypes.Auth.HttpAuthAws.sigV4A
198+
else -> return@forEach
199+
}
213200

214201
withBlock("#T(", "),", authFactoryFn) {
215202
// we delegate back to the expression visitor for each of these fields because it's possible to
@@ -221,15 +208,30 @@ private fun renderAuthSchemes(writer: KotlinWriter, authSchemes: Expression, exp
221208
writeInline("disableDoubleUriEncode = ")
222209
renderOrElse(expressionRenderer, scheme.getBooleanMember("disableDoubleEncoding"), "false")
223210

224-
when (schemeName) {
225-
"sigv4" -> renderSigV4Fields(writer, scheme, expressionRenderer)
226-
"sigv4a" -> renderSigV4AFields(writer, scheme, expressionRenderer)
227-
}
211+
renderFieldsForScheme(writer, scheme, expressionRenderer)
228212
}
229213
}
230214
}
231215
}
232216

217+
private fun renderFieldsForScheme(writer: KotlinWriter, scheme: ObjectNode, expressionRenderer: ExpressionRenderer) {
218+
when (scheme.expectStringMember("name").value) {
219+
"sigv4" -> renderSigV4Fields(writer, scheme, expressionRenderer)
220+
"sigv4a" -> renderSigV4AFields(writer, scheme, expressionRenderer)
221+
}
222+
}
223+
224+
private fun renderSigV4Fields(writer: KotlinWriter, scheme: ObjectNode, expressionRenderer: ExpressionRenderer) {
225+
writer.writeInline("signingRegion = ")
226+
writer.renderOrElse(expressionRenderer, scheme.getStringMember("signingRegion"), "null")
227+
}
228+
229+
private fun renderSigV4AFields(writer: KotlinWriter, scheme: ObjectNode, expressionRenderer: ExpressionRenderer) {
230+
writer.writeInline("signingRegionSet = ")
231+
expressionRenderer.renderExpression(Expression.fromNode(scheme.expectArrayMember("signingRegionSet")))
232+
writer.write(",")
233+
}
234+
233235
private fun KotlinWriter.renderOrElse(
234236
expressionRenderer: ExpressionRenderer,
235237
optionalNode: Optional<out Node>,
@@ -243,13 +245,15 @@ private fun KotlinWriter.renderOrElse(
243245
write(",")
244246
}
245247

246-
private fun renderSigV4Fields(writer: KotlinWriter, scheme: ObjectNode, expressionRenderer: ExpressionRenderer) {
247-
writer.writeInline("signingRegion = ")
248-
writer.renderOrElse(expressionRenderer, scheme.getStringMember("signingRegion"), "null")
249-
}
248+
internal val credentialsProviderProp = ConfigProperty {
249+
symbol = RuntimeTypes.Auth.Credentials.AwsCredentials.CredentialsProvider
250+
baseClass = RuntimeTypes.Auth.Credentials.AwsCredentials.CredentialsProviderConfig
251+
useNestedBuilderBaseClass()
252+
documentation = """
253+
The AWS credentials provider to use for authenticating requests.
254+
NOTE: The caller is responsible for managing the lifetime of the provider when set. The SDK
255+
client will not close it when the client is closed.
256+
""".trimIndent()
250257

251-
private fun renderSigV4AFields(writer: KotlinWriter, scheme: ObjectNode, expressionRenderer: ExpressionRenderer) {
252-
writer.writeInline("signingRegionSet = ")
253-
expressionRenderer.renderExpression(Expression.fromNode(scheme.expectArrayMember("signingRegionSet")))
254-
writer.write(",")
258+
propertyType = ConfigPropertyType.Required()
255259
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ software.amazon.smithy.kotlin.codegen.rendering.auth.BearerTokenAuthSchemeIntegr
1111
software.amazon.smithy.kotlin.codegen.rendering.endpoints.discovery.EndpointDiscoveryIntegration
1212
software.amazon.smithy.kotlin.codegen.rendering.endpoints.SdkEndpointBuiltinIntegration
1313
software.amazon.smithy.kotlin.codegen.rendering.compression.RequestCompressionIntegration
14+
software.amazon.smithy.kotlin.codegen.rendering.auth.SigV4AsymmetricAuthSchemeIntegration

0 commit comments

Comments
 (0)