Unverified Commit b989a8d0 authored by John DiSanti's avatar John DiSanti Committed by GitHub
Browse files

Fix presigning bug with `content-length` and `content-type` in S3 (#1216)



* Fix presigning bug with `content-length` and `content-type` in S3

* Fix presigning test in `aws-sigv4`

* Update changelog

* Clean when generating code for diff preview

* Incorporate feedback

* Fix server codegen

Co-authored-by: default avatarZelda Hessler <zhessler@amazon.com>
parent 4f183f71
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -46,3 +46,15 @@ message = "`ClientBuilder` helpers `rustls()` and `native_tls()` now return `Dyn
references = ["smithy-rs#1217", "aws-sdk-rust#467"]
meta = { "breaking" = false, "tada" = false, "bug" = true }
author = "jdisanti"

[[aws-sdk-rust]]
message = "`aws-sigv4` no longer skips the `content-length` and `content-type` headers when signing with `SignatureLocation::QueryParams`"
references = ["smithy-rs#1216"]
meta = { "breaking" = true, "tada" = false, "bug" = false }
author = "jdisanti"

[[aws-sdk-rust]]
message = "Fixed a bug in S3 that prevented the `content-length` and `content-type` inputs from being included in a presigned request signature. With this fix, customers can generate presigned URLs that enforce `content-length` and `content-type` for requests to S3."
references = ["smithy-rs#1216", "aws-sdk-rust#466"]
meta = { "breaking" = false, "tada" = false, "bug" = true }
author = "jdisanti"
+1 −7
Original line number Diff line number Diff line
@@ -219,7 +219,7 @@ pub mod request {
pub(crate) mod service {
    use crate::presigning::request::PresignedRequest;
    use aws_smithy_http::operation;
    use http::header::{CONTENT_LENGTH, CONTENT_TYPE, USER_AGENT};
    use http::header::USER_AGENT;
    use std::future::{ready, Ready};
    use std::marker::PhantomData;
    use std::task::{Context, Poll};
@@ -262,12 +262,6 @@ pub(crate) mod service {
        fn call(&mut self, req: operation::Request) -> Self::Future {
            let (mut req, _) = req.into_parts();

            // Remove headers from input serialization that shouldn't be part of the presigned
            // request since the request body is unsigned and left up to the person making the final
            // HTTP request.
            req.headers_mut().remove(CONTENT_LENGTH);
            req.headers_mut().remove(CONTENT_TYPE);

            // Remove user agent headers since the request will not be executed by the AWS Rust SDK.
            req.headers_mut().remove(USER_AGENT);
            req.headers_mut().remove("X-Amz-User-Agent");
+11 −11
Original line number Diff line number Diff line
@@ -10,7 +10,7 @@ use crate::http_request::sign::SignableRequest;
use crate::http_request::url_escape::percent_encode_path;
use crate::http_request::PercentEncodingMode;
use crate::sign::sha256_hex_string;
use http::header::{HeaderName, CONTENT_LENGTH, CONTENT_TYPE, HOST, USER_AGENT};
use http::header::{HeaderName, HOST, USER_AGENT};
use http::{HeaderMap, HeaderValue, Method, Uri};
use std::borrow::Cow;
use std::cmp::Ordering;
@@ -223,11 +223,6 @@ impl<'a> CanonicalRequest<'a> {
                continue;
            }
            if params.settings.signature_location == SignatureLocation::QueryParams {
                // Exclude content-length and content-type for query param signatures since the
                // body is unsigned for these use-cases, and the size is not known up-front.
                if name == CONTENT_LENGTH || name == CONTENT_TYPE {
                    continue;
                }
                // The X-Amz-User-Agent header should not be signed if this is for a presigned URL
                if name == HeaderName::from_static(header::X_AMZ_USER_AGENT) {
                    continue;
@@ -675,7 +670,7 @@ mod tests {
        assert_eq!(expected, actual);
    }

    // It should exclude user-agent, content-type, content-length, and x-amz-user-agent headers from presigning
    // It should exclude user-agent and x-amz-user-agent headers from presigning
    #[test]
    fn presigning_header_exclusion() {
        let request = http::Request::builder()
@@ -688,15 +683,20 @@ mod tests {
            .unwrap();
        let request = SignableRequest::from(&request);

        let mut settings = SigningSettings::default();
        settings.signature_location = SignatureLocation::QueryParams;
        settings.expires_in = Some(Duration::from_secs(30));
        let settings = SigningSettings {
            signature_location: SignatureLocation::QueryParams,
            expires_in: Some(Duration::from_secs(30)),
            ..Default::default()
        };

        let signing_params = signing_params(settings);
        let canonical = CanonicalRequest::from(&request, &signing_params).unwrap();

        let values = canonical.values.into_query_params().unwrap();
        assert_eq!("host", values.signed_headers.as_str());
        assert_eq!(
            "content-length;content-type;host",
            values.signed_headers.as_str()
        );
    }

    #[test]
+16 −15
Original line number Diff line number Diff line
@@ -154,12 +154,13 @@ class AwsInputPresignedMethod(
        val operationError = operationShape.errorSymbol(symbolProvider)
        val presignableOp = PRESIGNABLE_OPERATIONS.getValue(operationShape.id)

        var makeOperationFn = "make_operation"
        if (presignableOp.hasModelTransforms()) {
            makeOperationFn = "_make_presigned_operation"

            val syntheticOp =
        val makeOperationOp = if (presignableOp.hasModelTransforms()) {
            codegenContext.model.expectShape(syntheticShapeId(operationShape.id), OperationShape::class.java)
        } else {
            section.operationShape
        }
        val makeOperationFn = "_make_presigned_operation"

        val protocol = section.protocol
        MakeOperationGenerator(
            codegenContext,
@@ -168,8 +169,8 @@ class AwsInputPresignedMethod(
            // Prefixed with underscore to avoid colliding with modeled functions
            functionName = makeOperationFn,
            public = false,
            ).generateMakeOperation(this, syntheticOp, section.customizations)
        }
            includeDefaultPayloadHeaders = false
        ).generateMakeOperation(this, makeOperationOp, section.customizations)

        documentPresignedMethod(hasConfigArg = true)
        rustBlockTemplate(
+1 −4
Original line number Diff line number Diff line
@@ -157,10 +157,7 @@ class SigV4SigningFeature(
        return when (section) {
            is OperationSection.MutateRequest -> writable {
                rustTemplate(
                    """
                    ##[allow(unused_mut)]
                    let mut signing_config = #{sig_auth}::signer::OperationSigningConfig::default_config();
                    """,
                    "let mut signing_config = #{sig_auth}::signer::OperationSigningConfig::default_config();",
                    *codegenScope
                )
                if (needsAmzSha256(service)) {
Loading