Skip to content

Commit 7a79e13

Browse files
authored
feat: support unbounded streams as UNSIGNED-PAYLOAD (#117)
1 parent 14b19b9 commit 7a79e13

File tree

3 files changed

+31
-12
lines changed

3 files changed

+31
-12
lines changed

client-runtime/auth/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ kotlin {
1818
api("software.aws.smithy.kotlin:http:$smithyKotlinVersion")
1919
implementation(project(":client-runtime:crt-util"))
2020
implementation("aws.sdk.kotlin.crt:aws-crt-kotlin:$crtKotlinVersion")
21+
implementation("software.aws.smithy.kotlin:logging:$smithyKotlinVersion")
2122
}
2223
}
2324
commonTest {

client-runtime/auth/common/src/aws/sdk/kotlin/runtime/auth/AwsSigV4SigningMiddleware.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import aws.sdk.kotlin.runtime.execution.AuthAttributes
1616
import software.aws.clientrt.client.ExecutionContext
1717
import software.aws.clientrt.http.*
1818
import software.aws.clientrt.http.operation.SdkHttpOperation
19+
import software.aws.clientrt.http.operation.logger
1920
import software.aws.clientrt.time.epochMilliseconds
2021
import software.aws.clientrt.util.get
2122

@@ -91,6 +92,28 @@ public class AwsSigV4SigningMiddleware internal constructor(private val config:
9192
// otherwise to sign a request we need to convert: builder -> crt kotlin HttpRequest (which underneath converts to aws-c-http message) and back
9293
val signableRequest = req.subject.toSignableCrtRequest()
9394

95+
// SDKs are supposed to default to signed payload _always_ when possible (and when `unsignedPayload` trait isn't present).
96+
//
97+
// There are a few escape hatches/special cases:
98+
// 1. Customer explicitly disables signed payload (via AuthAttributes.UnsignedPayload)
99+
// 2. Customer provides a (potentially) unbounded stream (via HttpBody.Streaming)
100+
//
101+
// When an unbounded stream (2) is given we proceed as follows:
102+
// 2.1. is it a file?
103+
// (2.1.1) yes -> sign the payload (bounded stream/special case)
104+
// (2.1.2) no -> unsigned payload
105+
//
106+
// NOTE: Chunked signing is NOT enabled through this middleware.
107+
// NOTE:
108+
// 2.1.1 is handled by toSignableRequest() by special casing file inputs
109+
// 2.1.2 is handled below
110+
//
111+
112+
// FIXME - see: https://github.com/awslabs/smithy-kotlin/issues/296
113+
// if we know we have a (streaming) body and toSignableRequest() fails to convert it to a CRT equivalent
114+
// then we must decide how to compute the payload hash ourselves (defaults to unsigned payload)
115+
val isUnboundedStream = signableRequest.body == null && req.subject.body is HttpBody.Streaming
116+
94117
val signingConfig: AwsSigningConfig = AwsSigningConfig.build {
95118
region = req.context[AuthAttributes.SigningRegion]
96119
service = req.context.getOrNull(AuthAttributes.SigningService) ?: checkNotNull(config.signingService)
@@ -107,6 +130,10 @@ public class AwsSigV4SigningMiddleware internal constructor(private val config:
107130
signedBodyValue = when {
108131
req.context.isUnsignedRequest() -> AwsSignedBodyValue.UNSIGNED_PAYLOAD
109132
req.subject.body is HttpBody.Empty -> AwsSignedBodyValue.EMPTY_SHA256
133+
isUnboundedStream -> {
134+
req.context.logger.warn { "unable to compute hash for unbounded stream; defaulting to unsigned payload" }
135+
AwsSignedBodyValue.UNSIGNED_PAYLOAD
136+
}
110137
// use the payload to compute the hash
111138
else -> null
112139
}

client-runtime/crt-util/common/src/aws/sdk/kotlin/crt/Http.kt

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,25 +13,16 @@ import software.aws.clientrt.http.util.splitAsQueryParameters
1313
import aws.sdk.kotlin.crt.http.Headers as HeadersCrt
1414
import aws.sdk.kotlin.crt.http.HttpRequest as HttpRequestCrt
1515

16-
/**
17-
* Convert an [HttpRequestBuilder] into a CRT HttpRequest instance (e.g. for signing)
18-
*/
19-
@InternalSdkApi
20-
public suspend fun HttpRequestBuilder.toCrtRequest(): HttpRequestCrt {
21-
// this looks roughly like toSignableCrtRequest() but needs to account for streamed bodies (will have to spawn a coroutine to do this when
22-
// given any body type of HttpBody.Streaming other than a file stream which we can special case)
23-
TODO("not-implemented")
24-
}
25-
2616
/**
2717
* Convert an [HttpRequestBuilder] into a CRT HttpRequest for the purpose of signing.
2818
*/
2919
@InternalSdkApi
3020
public fun HttpRequestBuilder.toSignableCrtRequest(): HttpRequestCrt {
3121
// FIXME - this does not account for streaming bodies. The main use case for this conversion is for signing purposes
3222
// only. We need to special case file streams as being signable. Custom dynamic streams that implement
33-
// HttpBody.Streaming are not signable without consuming the stream.
34-
// We will have to reconcile this with event-streams which have signed chunks...
23+
// HttpBody.Streaming are not signable without consuming the stream and would need to go through
24+
// chunked signing or unsigned payload
25+
// see: https://github.com/awslabs/smithy-kotlin/issues/297
3526
val bodyStream = (body as? HttpBody.Bytes)?.let { HttpRequestBodyStream.fromByteArray(it.bytes()) }
3627
return HttpRequestCrt(method.name, url.encodedPath, HttpHeadersCrt(headers), bodyStream)
3728
}

0 commit comments

Comments
 (0)