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

feat: support default checksums #1191

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2a9d104
Bump smithy IDL version
0marperez Nov 11, 2024
205839c
Add requestChecksumCalculation config option
0marperez Nov 11, 2024
7233b71
Added responseChecksumValidation
0marperez Nov 11, 2024
e1dc616
Add todos for business metrics
0marperez Nov 12, 2024
e436482
Unit tests pass
0marperez Nov 25, 2024
abdba02
Merge branch 'main' of https://github.com/awslabs/smithy-kotlin into …
0marperez Nov 26, 2024
3e4c891
E2E tests pass
0marperez Nov 26, 2024
9760ee1
Self review
0marperez Nov 27, 2024
f8b39b0
Self review 2
0marperez Nov 27, 2024
f676b7b
Smithy codegen version bump
0marperez Nov 27, 2024
6e9b206
Make composite checksum check S3 specific
0marperez Dec 3, 2024
fb3a52a
Turn off all failing protocol tests
0marperez Dec 3, 2024
40bb298
PR feedback and fix breaking changes
0marperez Dec 3, 2024
828adaa
Merge branch 'main' into flexible-checksums
0marperez Dec 3, 2024
e2068e7
Trigger CI
0marperez Dec 4, 2024
08b4a37
Drop support for http body dot bytes response checksums
0marperez Dec 4, 2024
91355d1
Fix HttpChecksumRequiredTrait
0marperez Dec 4, 2024
1fcd4b2
Fix kotlin writer runtime exception
0marperez Dec 5, 2024
4edabfb
Deprecate HttpOperationContext.ChecksumAlgorithm
0marperez Dec 9, 2024
db65d74
PR feedback
0marperez Dec 11, 2024
b2e6f61
Re-add support for http body dot bytes response checksusms
0marperez Dec 13, 2024
c63cf83
Presigned URL checksums
0marperez Dec 13, 2024
b68a9f5
Merge branch 'main' of https://github.com/awslabs/smithy-kotlin into …
0marperez Dec 13, 2024
0dd7886
PR feedback checkpoint?
0marperez Dec 13, 2024
d74c949
Refactor checksum interceptors
0marperez Dec 18, 2024
1157f26
Fix composite checksums
0marperez Dec 19, 2024
d9f0659
Make it compile
0marperez Dec 19, 2024
dc3ce8b
Merge branch 'main' of https://github.com/awslabs/smithy-kotlin into …
0marperez Dec 19, 2024
3ef1c20
Use toList supported for JVM versions less than 16
0marperez Dec 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions codegen/protocol-tests/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,27 @@ data class ProtocolTest(val projectionName: String, val serviceShapeId: String,
// for the configured protocols in [enabledProtocols].
val enabledProtocols = listOf(
ProtocolTest("aws-ec2-query", "aws.protocoltests.ec2#AwsEc2"),
ProtocolTest("aws-json-10", "aws.protocoltests.json10#JsonRpc10"),

// FIXME: Re-enable. This test is broken after a smithy update: https://github.com/smithy-lang/smithy/pull/2467
// ProtocolTest("aws-json-10", "aws.protocoltests.json10#JsonRpc10"),
Comment on lines +28 to +29
Copy link
Contributor

Choose a reason for hiding this comment

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

FYI I know we talked about disabling some tests that are failing in Smithy 1.53.0, but these are disabling the entire test suite, which we don't want to do


ProtocolTest("aws-json-11", "aws.protocoltests.json#JsonProtocol"),
ProtocolTest("aws-restjson", "aws.protocoltests.restjson#RestJson"),
ProtocolTest("aws-restxml", "aws.protocoltests.restxml#RestXml"),

// FIXME: Re-enable. These tests are broken after a smithy update: https://github.com/smithy-lang/smithy/pull/2403
// ProtocolTest("aws-restjson", "aws.protocoltests.restjson#RestJson"),
// ProtocolTest("aws-restxml", "aws.protocoltests.restxml#RestXml"),

ProtocolTest("aws-restxml-xmlns", "aws.protocoltests.restxml.xmlns#RestXmlWithNamespace"),
ProtocolTest("aws-query", "aws.protocoltests.query#AwsQuery"),
ProtocolTest("smithy-rpcv2-cbor", "smithy.protocoltests.rpcv2Cbor#RpcV2Protocol"),

// FIXME: Re-enable. This test is broken after a smithy update: https://github.com/smithy-lang/smithy/pull/2467
// ProtocolTest("smithy-rpcv2-cbor", "smithy.protocoltests.rpcv2Cbor#RpcV2Protocol"),

// Custom hand written tests
ProtocolTest("error-correction-json", "aws.protocoltests.errorcorrection#RequiredValueJson"),
ProtocolTest("error-correction-xml", "aws.protocoltests.errorcorrection#RequiredValueXml"),
// FIXME: Re-enable. These tests were relying on a smithy bug that has since been fixed.
// https://github.com/smithy-lang/smithy/pull/2393
// ProtocolTest("error-correction-json", "aws.protocoltests.errorcorrection#RequiredValueJson"),
// ProtocolTest("error-correction-xml", "aws.protocoltests.errorcorrection#RequiredValueXml"),
)

smithyBuild {
Expand Down
10 changes: 7 additions & 3 deletions codegen/protocol-tests/model/error-correction-tests.smithy
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ operation SayHello { output: TestOutputDocument, errors: [Error] }
@http(method: "POST", uri: "/")
operation SayHelloXml { output: TestOutput, errors: [Error] }

structure TestOutputDocument with [TestStruct] { innerField: Nested, @required document: Document }
structure TestOutputDocument with [TestStruct] {
innerField: Nested,
// @required
document: Document
}
structure TestOutput with [TestStruct] { innerField: Nested }

@mixin
Expand All @@ -60,7 +64,7 @@ structure TestStruct {
@required
nestedListValue: NestedList

@required
// @required
Copy link
Contributor

Choose a reason for hiding this comment

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

Were these @required removed to fix some protocol tests? I'd rather leave them disabled than change our test models

Copy link
Contributor Author

@0marperez 0marperez Dec 13, 2024

Choose a reason for hiding this comment

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

The required trait isn't supposed to be there, a smithy validator will throw an error if it is, even if I disable the protocol test

nested: Nested

@required
Expand Down Expand Up @@ -91,7 +95,7 @@ union MyUnion {
}

structure Nested {
@required
// @required
a: String
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ object RuntimeTypes {
object Interceptors : RuntimeTypePackage(KotlinDependency.HTTP, "interceptors") {
val ContinueInterceptor = symbol("ContinueInterceptor")
val HttpInterceptor = symbol("HttpInterceptor")
val Md5ChecksumInterceptor = symbol("Md5ChecksumInterceptor")
val HttpChecksumRequiredInterceptor = symbol("HttpChecksumRequiredInterceptor")
val FlexibleChecksumsRequestInterceptor = symbol("FlexibleChecksumsRequestInterceptor")
val FlexibleChecksumsResponseInterceptor = symbol("FlexibleChecksumsResponseInterceptor")
val ResponseLengthValidationInterceptor = symbol("ResponseLengthValidationInterceptor")
Expand Down Expand Up @@ -170,6 +170,8 @@ object RuntimeTypes {

object Hashing : RuntimeTypePackage(KotlinDependency.CORE, "hashing") {
val Sha256 = symbol("Sha256")
val toHashFunctionOrThrow = symbol("toHashFunctionOrThrow")
val isSupportedForFlexibleChecksums = symbol("isSupportedForFlexibleChecksums")
}

object IO : RuntimeTypePackage(KotlinDependency.CORE, "io") {
Expand Down Expand Up @@ -199,6 +201,7 @@ object RuntimeTypes {
val toNumber = symbol("toNumber")
val type = symbol("type")
val PlatformProvider = symbol("PlatformProvider")
val runBlocking = symbol("runBlocking")
}

object Net : RuntimeTypePackage(KotlinDependency.CORE, "net") {
Expand Down Expand Up @@ -231,6 +234,8 @@ object RuntimeTypes {
object Config : RuntimeTypePackage(KotlinDependency.SMITHY_CLIENT, "config") {
val RequestCompressionConfig = symbol("RequestCompressionConfig")
val CompressionClientConfig = symbol("CompressionClientConfig")
val HttpChecksumClientConfig = symbol("HttpChecksumClientConfig")
val HttpChecksumConfigOption = symbol("HttpChecksumConfigOption")
}

object Endpoints : RuntimeTypePackage(KotlinDependency.SMITHY_CLIENT, "endpoints") {
Expand Down Expand Up @@ -395,6 +400,7 @@ object RuntimeTypes {
val TelemetryContextElement = symbol("TelemetryContextElement", "context")
val TraceSpan = symbol("TraceSpan", "trace")
val withSpan = symbol("withSpan", "trace")
val warn = symbol("warn", "logging")
}
object TelemetryDefaults : RuntimeTypePackage(KotlinDependency.TELEMETRY_DEFAULTS) {
val Global = symbol("Global")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import software.amazon.smithy.rulesengine.language.EndpointRuleSet
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait
import software.amazon.smithy.rulesengine.traits.EndpointTestCase
import software.amazon.smithy.rulesengine.traits.EndpointTestsTrait
import kotlin.streams.toList as kotlinToList // Gave this import a unique name because the Java one was being preferred

/**
* Get all shapes of a particular type from the model.
Expand All @@ -32,7 +33,7 @@ import software.amazon.smithy.rulesengine.traits.EndpointTestsTrait
* shape's closure for example)
*/
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
inline fun <reified T : Shape> Model.shapes(): List<T> = shapes(T::class.java).toList()
inline fun <reified T : Shape> Model.shapes(): List<T> = shapes(T::class.java).kotlinToList()
Copy link
Contributor

Choose a reason for hiding this comment

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

This has never been a problem before, what changed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There was a refactor in AWS SDK Kotlin to make the codegen tests KMP, which led to the JVM CI checks failing because we've been using JVM 17. I set the JVM version in the tests to 1.8 and the toList function used above is only available since JVM 16

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok that makes sense. You should be able to use shapes(T::class.java).collect(Collectors.toList()) instead of hacking around with the Kotlin toList


/**
* Extension function to return a shape of expected type.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package software.amazon.smithy.kotlin.codegen.rendering.checksums

import software.amazon.smithy.aws.traits.HttpChecksumTrait
import software.amazon.smithy.kotlin.codegen.KotlinSettings
import software.amazon.smithy.kotlin.codegen.core.KotlinWriter
import software.amazon.smithy.kotlin.codegen.core.RuntimeTypes
import software.amazon.smithy.kotlin.codegen.integration.KotlinIntegration
import software.amazon.smithy.kotlin.codegen.model.hasTrait
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.traits.HttpChecksumRequiredTrait

/**
* Handles the `httpChecksumRequired` trait.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit/docs: Explain more about how it "handles" the trait

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The "meat" of the integration is the middleware and both of them have their own Kdocs. It feels like docs overkill to also add a summary of what each middleware is doing here

* See: https://smithy.io/2.0/spec/http-bindings.html#httpchecksumrequired-trait
*/
class HttpChecksumRequiredIntegration : KotlinIntegration {
override fun enabledForService(model: Model, settings: KotlinSettings): Boolean =
model.isTraitApplied(HttpChecksumRequiredTrait::class.java)

override fun customizeMiddleware(
ctx: ProtocolGenerator.GenerationContext,
resolved: List<ProtocolMiddleware>,
): List<ProtocolMiddleware> = resolved + httpChecksumRequiredDefaultAlgorithmMiddleware + httpChecksumRequiredMiddleware
}

/**
* Adds default checksum algorithm to the execution context
*/
private val httpChecksumRequiredDefaultAlgorithmMiddleware = object : ProtocolMiddleware {
override val name: String = "httpChecksumRequiredDefaultAlgorithmMiddleware"
override val order: Byte = -2 // Before S3 Express (possibly) changes the default (-1) and before calculating checksum (0)

override fun isEnabledFor(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Boolean =
op.hasTrait<HttpChecksumRequiredTrait>() && !op.hasTrait<HttpChecksumTrait>()

override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) {
writer.write(
"op.context[#T.DefaultChecksumAlgorithm] = #S",
RuntimeTypes.HttpClient.Operation.HttpOperationContext,
"MD5",
Copy link
Contributor

Choose a reason for hiding this comment

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

question/correctness: Can/should we store this as a HashFunction rather than a String?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We could, I think we would have to initialize the HashFunction in the context and its seems slightly worse than what we have right now. Thoughts?

)
}
}

/**
* Adds interceptor to calculate request checksums.
* The `httpChecksum` trait supersedes the `httpChecksumRequired` trait. If both are applied to an operation use `httpChecksum`.
*
* See: https://smithy.io/2.0/aws/aws-core.html#behavior-with-httpchecksumrequired
*/
private val httpChecksumRequiredMiddleware = object : ProtocolMiddleware {
override val name: String = "httpChecksumRequiredMiddleware"

override fun isEnabledFor(ctx: ProtocolGenerator.GenerationContext, op: OperationShape): Boolean =
op.hasTrait<HttpChecksumRequiredTrait>() && !op.hasTrait<HttpChecksumTrait>()

override fun render(ctx: ProtocolGenerator.GenerationContext, op: OperationShape, writer: KotlinWriter) {
writer.write(
"op.interceptors.add(#T())",
RuntimeTypes.HttpClient.Interceptors.HttpChecksumRequiredInterceptor,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*/
package software.amazon.smithy.kotlin.codegen.rendering.protocol

import software.amazon.smithy.aws.traits.HttpChecksumTrait
import software.amazon.smithy.codegen.core.Symbol
import software.amazon.smithy.kotlin.codegen.core.*
import software.amazon.smithy.kotlin.codegen.integration.SectionId
Expand All @@ -22,7 +21,6 @@ import software.amazon.smithy.model.knowledge.OperationIndex
import software.amazon.smithy.model.knowledge.TopDownIndex
import software.amazon.smithy.model.shapes.OperationShape
import software.amazon.smithy.model.traits.EndpointTrait
import software.amazon.smithy.model.traits.HttpChecksumRequiredTrait

/**
* Renders an implementation of a service interface for HTTP protocol
Expand Down Expand Up @@ -318,8 +316,6 @@ open class HttpProtocolClientGenerator(
.forEach { middleware ->
middleware.render(ctx, op, writer)
}

op.renderIsMd5ChecksumRequired(writer)
}

/**
Expand All @@ -336,27 +332,6 @@ open class HttpProtocolClientGenerator(
*/
protected open fun renderAdditionalMethods(writer: KotlinWriter) { }

/**
* Render optionally installing Md5ChecksumMiddleware.
* The Md5 middleware will only be installed if the operation requires a checksum and the user has not opted-in to flexible checksums.
*/
private fun OperationShape.renderIsMd5ChecksumRequired(writer: KotlinWriter) {
val httpChecksumTrait = getTrait<HttpChecksumTrait>()

// the checksum requirement can be modeled in either HttpChecksumTrait's `requestChecksumRequired` or the HttpChecksumRequired trait
if (!hasTrait<HttpChecksumRequiredTrait>() && httpChecksumTrait == null) {
return
}

if (hasTrait<HttpChecksumRequiredTrait>() || httpChecksumTrait?.isRequestChecksumRequired == true) {
val interceptorSymbol = RuntimeTypes.HttpClient.Interceptors.Md5ChecksumInterceptor
val inputSymbol = ctx.symbolProvider.toSymbol(ctx.model.expectShape(inputShape))
writer.withBlock("op.interceptors.add(#T<#T> {", "})", interceptorSymbol, inputSymbol) {
writer.write("op.context.getOrNull(#T.ChecksumAlgorithm) == null", RuntimeTypes.HttpClient.Operation.HttpOperationContext)
}
}
}

/**
* render a utility function to populate an operation's ExecutionContext with defaults from service config, environment, etc
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ software.amazon.smithy.kotlin.codegen.rendering.endpoints.SdkEndpointBuiltinInte
software.amazon.smithy.kotlin.codegen.rendering.compression.RequestCompressionIntegration
software.amazon.smithy.kotlin.codegen.rendering.auth.SigV4AsymmetricAuthSchemeIntegration
software.amazon.smithy.kotlin.codegen.rendering.smoketests.SmokeTestsIntegration
software.amazon.smithy.kotlin.codegen.rendering.checksums.HttpChecksumRequiredIntegration
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ crt-kotlin-version = "0.8.10"
micrometer-version = "1.13.6"

# codegen
smithy-version = "1.51.0"
smithy-version = "1.53.0"
smithy-gradle-version = "0.9.0"

# testing
Expand Down
3 changes: 3 additions & 0 deletions runtime/auth/aws-signing-common/api/aws-signing-common.api
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public final class aws/smithy/kotlin/runtime/auth/awssigning/AwsSigningConfig {
public static final field Companion Laws/smithy/kotlin/runtime/auth/awssigning/AwsSigningConfig$Companion;
public fun <init> (Laws/smithy/kotlin/runtime/auth/awssigning/AwsSigningConfig$Builder;)V
public final fun getAlgorithm ()Laws/smithy/kotlin/runtime/auth/awssigning/AwsSigningAlgorithm;
public final fun getChecksum ()Lkotlin/Pair;
public final fun getCredentials ()Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials;
public final fun getExpiresAfter-FghU774 ()Lkotlin/time/Duration;
public final fun getHashSpecification ()Laws/smithy/kotlin/runtime/auth/awssigning/HashSpecification;
Expand All @@ -91,6 +92,7 @@ public final class aws/smithy/kotlin/runtime/auth/awssigning/AwsSigningConfig$Bu
public fun <init> ()V
public final fun build ()Laws/smithy/kotlin/runtime/auth/awssigning/AwsSigningConfig;
public final fun getAlgorithm ()Laws/smithy/kotlin/runtime/auth/awssigning/AwsSigningAlgorithm;
public final fun getChecksum ()Lkotlin/Pair;
public final fun getCredentials ()Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials;
public final fun getExpiresAfter-FghU774 ()Lkotlin/time/Duration;
public final fun getHashSpecification ()Laws/smithy/kotlin/runtime/auth/awssigning/HashSpecification;
Expand All @@ -105,6 +107,7 @@ public final class aws/smithy/kotlin/runtime/auth/awssigning/AwsSigningConfig$Bu
public final fun getSigningDate ()Laws/smithy/kotlin/runtime/time/Instant;
public final fun getUseDoubleUriEncode ()Z
public final fun setAlgorithm (Laws/smithy/kotlin/runtime/auth/awssigning/AwsSigningAlgorithm;)V
public final fun setChecksum (Lkotlin/Pair;)V
public final fun setCredentials (Laws/smithy/kotlin/runtime/auth/awscredentials/Credentials;)V
public final fun setExpiresAfter-BwNAW2A (Lkotlin/time/Duration;)V
public final fun setHashSpecification (Laws/smithy/kotlin/runtime/auth/awssigning/HashSpecification;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@ public class AwsSigningConfig(builder: Builder) {
*/
public val logRequest: Boolean = builder.logRequest

/**
* Determines the checksum to add to the canonical request query parameters before signing.
lauzadis marked this conversation as resolved.
Show resolved Hide resolved
* The first element of the pair represents the checksum name, the second represents the checksum value.
*/
public val checksum: Pair<String, String>? = builder.checksum

public fun toBuilder(): Builder = Builder().also {
it.region = region
it.service = service
Expand All @@ -187,6 +193,7 @@ public class AwsSigningConfig(builder: Builder) {
it.credentials = credentials
it.expiresAfter = expiresAfter
it.logRequest = logRequest
it.checksum = checksum
}

public class Builder {
Expand All @@ -204,6 +211,7 @@ public class AwsSigningConfig(builder: Builder) {
public var credentials: Credentials? = null
public var expiresAfter: Duration? = null
public var logRequest: Boolean = false
public var checksum: Pair<String, String>? = null

public fun build(): AwsSigningConfig = AwsSigningConfig(this)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,11 @@ internal class DefaultCanonicalizer(private val sha256Supplier: HashSupplier = :
param("X-Amz-Expires", config.expiresAfter?.inWholeSeconds?.toString(), signViaQueryParams)
param("X-Amz-Security-Token", sessionToken, !config.omitSessionToken) // Add pre-sig if omitSessionToken=false

// Add checksum as query param
if (config.signatureType == AwsSignatureType.HTTP_REQUEST_VIA_QUERY_PARAMS && config.checksum != null) {
param(config.checksum!!.first, config.checksum!!.second)
}

val headers = builder
.headers
.entries()
Expand Down
Loading
Loading