Skip to content

Commit 7471609

Browse files
authored
feat: support S3 Express One Zone (#1033)
1 parent 4a20344 commit 7471609

File tree

23 files changed

+462
-121
lines changed

23 files changed

+462
-121
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "d2f0f8cb-b94d-403e-9db8-5cd61bd8eb1b",
3+
"type": "feature",
4+
"description": "Add support for S3 Express One Zone"
5+
}

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

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@ package software.amazon.smithy.kotlin.codegen.rendering.auth
77

88
import software.amazon.smithy.codegen.core.Symbol
99
import software.amazon.smithy.kotlin.codegen.KotlinSettings
10-
import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
11-
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes
12-
import software.amazon.smithy.kotlin.codegen.core.clientName
13-
import software.amazon.smithy.kotlin.codegen.core.withBlock
10+
import software.amazon.smithy.kotlin.codegen.core.*
1411
import software.amazon.smithy.kotlin.codegen.model.buildSymbol
1512
import software.amazon.smithy.kotlin.codegen.model.knowledge.AuthIndex
1613
import software.amazon.smithy.kotlin.codegen.rendering.protocol.ProtocolGenerator

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

Lines changed: 30 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -186,29 +186,36 @@ private object Sigv4EndpointCustomization : EndpointCustomization {
186186
// SigV4a requires SigV4 so SigV4 integration renders SigV4a auth scheme.
187187
// See comment in example model: https://smithy.io/2.0/aws/aws-auth.html?highlight=sigv4#aws-auth-sigv4a-trait
188188
private fun renderAuthSchemes(writer: KotlinWriter, authSchemes: Expression, expressionRenderer: ExpressionRenderer) {
189-
writer.writeInline("#T to ", RuntimeTypes.SmithyClient.Endpoints.SigningContextAttributeKey)
190-
writer.withBlock("listOf(", ")") {
191-
authSchemes.toNode().expectArrayNode().forEach {
192-
val scheme = it.expectObjectNode()
193-
val schemeName = scheme.expectStringMember("name").value
194-
195-
val authFactoryFn = when (schemeName) {
196-
"sigv4" -> RuntimeTypes.Auth.HttpAuthAws.sigV4
197-
"sigv4a" -> RuntimeTypes.Auth.HttpAuthAws.sigV4A
198-
else -> return@forEach
199-
}
200-
201-
withBlock("#T(", "),", authFactoryFn) {
202-
// we delegate back to the expression visitor for each of these fields because it's possible to
203-
// encounter template strings throughout
204-
205-
writeInline("serviceName = ")
206-
renderOrElse(expressionRenderer, scheme.getStringMember("signingName"), "null")
207-
208-
writeInline("disableDoubleUriEncode = ")
209-
renderOrElse(expressionRenderer, scheme.getBooleanMember("disableDoubleEncoding"), "false")
210-
211-
renderFieldsForScheme(writer, scheme, expressionRenderer)
189+
val schemes = authSchemes.toNode().expectArrayNode().filter {
190+
val name = it.expectObjectNode().expectStringMember("name").value
191+
name == "sigv4" || name == "sigv4a"
192+
}.takeIf { it.isNotEmpty() }
193+
194+
schemes?.let {
195+
writer.writeInline("#T to ", RuntimeTypes.SmithyClient.Endpoints.SigningContextAttributeKey)
196+
writer.withBlock("listOf(", ")") {
197+
schemes.forEach {
198+
val scheme = it.expectObjectNode()
199+
val schemeName = scheme.expectStringMember("name").value
200+
201+
val authFactoryFn = when (schemeName) {
202+
"sigv4" -> RuntimeTypes.Auth.HttpAuthAws.sigV4
203+
"sigv4a" -> RuntimeTypes.Auth.HttpAuthAws.sigV4A
204+
else -> return@forEach
205+
}
206+
207+
withBlock("#T(", "),", authFactoryFn) {
208+
// we delegate back to the expression visitor for each of these fields because it's possible to
209+
// encounter template strings throughout
210+
211+
writeInline("serviceName = ")
212+
renderOrElse(expressionRenderer, scheme.getStringMember("signingName"), "null")
213+
214+
writeInline("disableDoubleUriEncode = ")
215+
renderOrElse(expressionRenderer, scheme.getBooleanMember("disableDoubleEncoding"), "false")
216+
217+
renderFieldsForScheme(writer, scheme, expressionRenderer)
218+
}
212219
}
213220
}
214221
}

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/DefaultEndpointProviderGenerator.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,10 @@ class DefaultEndpointProviderGenerator(
8080

8181
private val propertyRenderers = endpointCustomizations
8282
.map { it.propertyRenderers }
83-
.fold(mutableMapOf<String, EndpointPropertyRenderer>()) { acc, propRenderers ->
84-
acc.putAll(propRenderers)
83+
.fold(mutableMapOf<String, MutableList<EndpointPropertyRenderer>>()) { acc, propRenderers ->
84+
propRenderers.forEach { (key, propRenderer) ->
85+
acc[key] = acc.getOrDefault(key, mutableListOf()).also { it.add(propRenderer) }
86+
}
8587
acc
8688
}
8789

@@ -190,7 +192,9 @@ class DefaultEndpointProviderGenerator(
190192

191193
// caller has a chance to generate their own value for a recognized property
192194
if (kStr in propertyRenderers) {
193-
propertyRenderers[kStr]!!(writer, v, this@DefaultEndpointProviderGenerator)
195+
propertyRenderers[kStr]!!.forEach { renderer ->
196+
renderer(writer, v, this@DefaultEndpointProviderGenerator)
197+
}
194198
return@forEach
195199
}
196200

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/endpoints/DefaultEndpointProviderTestGenerator.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@ class DefaultEndpointProviderTestGenerator(
4242
private val endpointCustomizations = ctx.integrations.mapNotNull { it.customizeEndpointResolution(ctx) }
4343
private val propertyRenderers = endpointCustomizations
4444
.map { it.propertyRenderers }
45-
.fold(mutableMapOf<String, EndpointPropertyRenderer>()) { acc, propRenderers ->
46-
acc.putAll(propRenderers)
45+
.fold(mutableMapOf<String, MutableList<EndpointPropertyRenderer>>()) { acc, propRenderers ->
46+
propRenderers.forEach { (key, propRenderer) ->
47+
acc[key] = acc.getOrDefault(key, mutableListOf()).also { it.add(propRenderer) }
48+
}
4749
acc
4850
}
4951

@@ -131,7 +133,9 @@ class DefaultEndpointProviderTestGenerator(
131133
withBlock("attributes = #T {", "},", RuntimeTypes.Core.Collections.attributesOf) {
132134
endpoint.properties.entries.forEach { (k, v) ->
133135
if (k in propertyRenderers) {
134-
propertyRenderers[k]!!(writer, Expression.fromNode(v), this@DefaultEndpointProviderTestGenerator)
136+
propertyRenderers[k]!!.forEach { renderer ->
137+
renderer(writer, Expression.fromNode(v), this@DefaultEndpointProviderTestGenerator)
138+
}
135139
return@forEach
136140
}
137141

codegen/smithy-kotlin-codegen/src/main/kotlin/software/amazon/smithy/kotlin/codegen/rendering/protocol/HttpProtocolClientGenerator.kt

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -348,19 +348,12 @@ open class HttpProtocolClientGenerator(
348348
return
349349
}
350350

351-
val requestAlgorithmMember = ctx.model.getShape(input.get()).getOrNull()
352-
?.members()
353-
?.firstOrNull { it.memberName == httpChecksumTrait?.requestAlgorithmMember?.getOrNull() }
354-
355351
if (hasTrait<HttpChecksumRequiredTrait>() || httpChecksumTrait?.isRequestChecksumRequired == true) {
356352
val interceptorSymbol = RuntimeTypes.HttpClient.Interceptors.Md5ChecksumInterceptor
357353
val inputSymbol = ctx.symbolProvider.toSymbol(ctx.model.expectShape(inputShape))
358-
359-
requestAlgorithmMember?.let {
360-
writer.withBlock("op.interceptors.add(#T<#T> { ", "})", interceptorSymbol, inputSymbol) {
361-
writer.write("it.#L?.value == null", requestAlgorithmMember.defaultName())
362-
}
363-
} ?: writer.write("op.interceptors.add(#T<#T>())", interceptorSymbol, inputSymbol)
354+
writer.withBlock("op.interceptors.add(#T<#T> {", "})", interceptorSymbol, inputSymbol) {
355+
writer.write("op.context.getOrNull(#T.ChecksumAlgorithm) == null", RuntimeTypes.HttpClient.Operation.HttpOperationContext)
356+
}
364357
}
365358
}
366359

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ okio-version = "3.6.0"
1212
otel-version = "1.32.0"
1313
slf4j-version = "2.0.9"
1414
slf4j-v1x-version = "1.7.36"
15-
crt-kotlin-version = "0.8.2"
15+
crt-kotlin-version = "0.8.5"
1616

1717
# codegen
1818
smithy-version = "1.42.0"

runtime/auth/aws-signing-common/api/aws-signing-common.api

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public final class aws/smithy/kotlin/runtime/auth/awssigning/AwsSigningAttribute
5555
public final fun getEnableAwsChunked ()Laws/smithy/kotlin/runtime/collections/AttributeKey;
5656
public final fun getHashSpecification ()Laws/smithy/kotlin/runtime/collections/AttributeKey;
5757
public final fun getNormalizeUriPath ()Laws/smithy/kotlin/runtime/collections/AttributeKey;
58+
public final fun getOmitSessionToken ()Laws/smithy/kotlin/runtime/collections/AttributeKey;
5859
public final fun getRequestSignature ()Laws/smithy/kotlin/runtime/collections/AttributeKey;
5960
public final fun getSignedBodyHeader ()Laws/smithy/kotlin/runtime/collections/AttributeKey;
6061
public final fun getSigner ()Laws/smithy/kotlin/runtime/collections/AttributeKey;

runtime/auth/aws-signing-common/common/src/aws/smithy/kotlin/runtime/auth/awssigning/AwsSigningAttributes.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,9 @@ public object AwsSigningAttributes {
8686
* @see <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html">SigV4 Streaming</a>
8787
*/
8888
public val EnableAwsChunked: AttributeKey<Boolean> = AttributeKey("aws.smithy.kotlin.signing#EnableAwsChunked")
89+
90+
/**
91+
* Flag indicating whether the X-Amz-Security-Token header should be omitted from the canonical request during signing.
92+
*/
93+
public val OmitSessionToken: AttributeKey<Boolean> = AttributeKey("aws.smithy.kotlin.signing#OmitSessionToken")
8994
}

runtime/auth/aws-signing-crt/jvm/src/aws/smithy/kotlin/runtime/auth/awssigning/crt/CrtAwsSigner.kt

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,30 @@ import aws.sdk.kotlin.crt.auth.signing.AwsSigningAlgorithm as CrtSigningAlgorith
2222
import aws.sdk.kotlin.crt.auth.signing.AwsSigningConfig as CrtSigningConfig
2323
import aws.sdk.kotlin.crt.http.Headers as CrtHeaders
2424

25+
private const val S3_EXPRESS_HEADER_NAME = "X-Amz-S3session-Token"
26+
2527
public object CrtAwsSigner : AwsSigner {
2628
override suspend fun sign(request: HttpRequest, config: AwsSigningConfig): AwsSigningResult<HttpRequest> {
2729
val isUnsigned = config.hashSpecification is HashSpecification.UnsignedPayload
2830
val isAwsChunked = request.headers.contains("Content-Encoding", "aws-chunked")
29-
val crtRequest = request.toSignableCrtRequest(isUnsigned, isAwsChunked)
30-
val crtConfig = config.toCrtSigningConfig()
31+
val isS3Express = request.headers.contains(S3_EXPRESS_HEADER_NAME)
32+
33+
val requestBuilder = request.toBuilder()
3134

32-
val crtResult = CrtSigner.sign(crtRequest, crtConfig)
35+
val crtConfig = config.toCrtSigningConfig().toBuilder()
36+
if (isS3Express) {
37+
crtConfig.algorithm = CrtSigningAlgorithm.SIGV4_S3EXPRESS
38+
crtConfig.omitSessionToken = false
39+
requestBuilder.headers.remove(S3_EXPRESS_HEADER_NAME) // CRT signer fails if this header is already present
40+
}
41+
42+
val crtRequest = requestBuilder.build().toSignableCrtRequest(isUnsigned, isAwsChunked)
43+
44+
val crtResult = CrtSigner.sign(crtRequest, crtConfig.build())
3345
coroutineContext.debug<CrtAwsSigner> { "Calculated signature: ${crtResult.signature.decodeToString()}" }
3446

3547
val crtSignedResult = checkNotNull(crtResult.signedRequest) { "Signed request unexpectedly null" }
3648

37-
val requestBuilder = request.toBuilder()
3849
requestBuilder.update(crtSignedResult)
3950
return AwsSigningResult(requestBuilder.build(), crtResult.signature)
4051
}

0 commit comments

Comments
 (0)