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

SdkBody::from_dyn() produces a Request with an invalid signature #749

Closed
spencergilbert opened this issue Mar 1, 2023 · 20 comments
Closed
Labels
bug This issue is a bug. p2 This is a standard priority issue

Comments

@spencergilbert
Copy link

Describe the bug

We recently updated our AWS dependencies and had to replace our usage of with_body_callback(). I followed the suggestion to wrap the SdkBody, as proposed here.

However this change causes the body to be an UNSIGNED-PAYLOAD, which is - as far as we can find - not valid for all services. Looking at other SDK's and implementations, it only seems to be used with S3.

Expected Behavior

A request containing a wrapped SdkBody to be correctly signed.

Current Behavior

InvalidSignatureException

The request signature we calculated does not match the signature you provided.

Our requests to S3 do seem to correctly signed when using this same code.

Reproduction Steps

I can look to create a sample, but thought raising this now given the differences with other SDKs worthwhile

Possible Solution

No response

Additional Information/Context

https://docs.rs/aws-sig-auth/latest/src/aws_sig_auth/signer.rs.html#196-197

// A body that is already in memory can be signed directly. A body that is not in memory
// (any sort of streaming body or presigned request) will be signed via UNSIGNED-PAYLOAD.

Checking the JS SDK it seems to be specific to services, rather than generally applied.

Version

├── aws-config v0.54.1
│   ├── aws-credential-types v0.54.1
│   │   ├── aws-smithy-async v0.54.4
│   │   ├── aws-smithy-types v0.54.4
│   ├── aws-http v0.54.1
│   │   ├── aws-credential-types v0.54.1 (*)
│   │   ├── aws-smithy-http v0.54.4
│   │   │   ├── aws-smithy-eventstream v0.54.4
│   │   │   │   ├── aws-smithy-types v0.54.4 (*)
│   │   │   ├── aws-smithy-types v0.54.4 (*)
│   │   ├── aws-smithy-types v0.54.4 (*)
│   │   ├── aws-types v0.54.1
│   │   │   ├── aws-credential-types v0.54.1 (*)
│   │   │   ├── aws-smithy-async v0.54.4 (*)
│   │   │   ├── aws-smithy-client v0.54.4
│   │   │   │   ├── aws-smithy-async v0.54.4 (*)
│   │   │   │   ├── aws-smithy-http v0.54.4 (*)
│   │   │   │   ├── aws-smithy-http-tower v0.54.4
│   │   │   │   │   ├── aws-smithy-http v0.54.4 (*)
│   │   │   │   │   ├── aws-smithy-types v0.54.4 (*)
│   │   │   │   ├── aws-smithy-protocol-test v0.54.4
│   │   │   │   ├── aws-smithy-types v0.54.4 (*)
│   │   │   ├── aws-smithy-http v0.54.4 (*)
│   │   │   ├── aws-smithy-types v0.54.4 (*)
│   ├── aws-sdk-sso v0.24.0
│   │   ├── aws-credential-types v0.54.1 (*)
│   │   ├── aws-endpoint v0.54.1
│   │   │   ├── aws-smithy-http v0.54.4 (*)
│   │   │   ├── aws-smithy-types v0.54.4 (*)
│   │   │   ├── aws-types v0.54.1 (*)
│   │   ├── aws-http v0.54.1 (*)
│   │   ├── aws-sig-auth v0.54.1
│   │   │   ├── aws-credential-types v0.54.1 (*)
│   │   │   ├── aws-sigv4 v0.54.1
│   │   │   │   ├── aws-smithy-eventstream v0.54.4 (*)
│   │   │   │   ├── aws-smithy-http v0.54.4 (*)
│   │   │   ├── aws-smithy-eventstream v0.54.4 (*)
│   │   │   ├── aws-smithy-http v0.54.4 (*)
│   │   │   ├── aws-types v0.54.1 (*)
│   │   ├── aws-smithy-async v0.54.4 (*)
│   │   ├── aws-smithy-client v0.54.4 (*)
│   │   ├── aws-smithy-http v0.54.4 (*)
│   │   ├── aws-smithy-http-tower v0.54.4 (*)
│   │   ├── aws-smithy-json v0.54.2
│   │   │   └── aws-smithy-types v0.54.4 (*)
│   │   ├── aws-smithy-types v0.54.4 (*)
│   │   ├── aws-types v0.54.1 (*)
│   ├── aws-sdk-sts v0.24.0
│   │   ├── aws-credential-types v0.54.1 (*)
│   │   ├── aws-endpoint v0.54.1 (*)
│   │   ├── aws-http v0.54.1 (*)
│   │   ├── aws-sig-auth v0.54.1 (*)
│   │   ├── aws-smithy-async v0.54.4 (*)
│   │   ├── aws-smithy-client v0.54.4 (*)
│   │   ├── aws-smithy-http v0.54.4 (*)
│   │   ├── aws-smithy-http-tower v0.54.4 (*)
│   │   ├── aws-smithy-json v0.54.2 (*)
│   │   ├── aws-smithy-query v0.54.2
│   │   │   ├── aws-smithy-types v0.54.4 (*)
│   │   ├── aws-smithy-types v0.54.4 (*)
│   │   ├── aws-smithy-xml v0.54.2
│   │   ├── aws-types v0.54.1 (*)
│   ├── aws-smithy-async v0.54.4 (*)
│   ├── aws-smithy-client v0.54.4 (*)
│   ├── aws-smithy-http v0.54.4 (*)
│   ├── aws-smithy-http-tower v0.54.4 (*)
│   ├── aws-smithy-json v0.54.2 (*)
│   ├── aws-smithy-types v0.54.4 (*)
│   ├── aws-types v0.54.1 (*)
├── aws-credential-types v0.54.1 (*)
├── aws-sdk-cloudwatch v0.24.0
│   ├── aws-credential-types v0.54.1 (*)
│   ├── aws-endpoint v0.54.1 (*)
│   ├── aws-http v0.54.1 (*)
│   ├── aws-sig-auth v0.54.1 (*)
│   ├── aws-smithy-async v0.54.4 (*)
│   ├── aws-smithy-client v0.54.4 (*)
│   ├── aws-smithy-http v0.54.4 (*)
│   ├── aws-smithy-http-tower v0.54.4 (*)
│   ├── aws-smithy-json v0.54.2 (*)
│   ├── aws-smithy-query v0.54.2 (*)
│   ├── aws-smithy-types v0.54.4 (*)
│   ├── aws-smithy-xml v0.54.2 (*)
│   ├── aws-types v0.54.1 (*)
├── aws-sdk-cloudwatchlogs v0.24.0
│   ├── aws-credential-types v0.54.1 (*)
│   ├── aws-endpoint v0.54.1 (*)
│   ├── aws-http v0.54.1 (*)
│   ├── aws-sig-auth v0.54.1 (*)
│   ├── aws-smithy-async v0.54.4 (*)
│   ├── aws-smithy-client v0.54.4 (*)
│   ├── aws-smithy-http v0.54.4 (*)
│   ├── aws-smithy-http-tower v0.54.4 (*)
│   ├── aws-smithy-json v0.54.2 (*)
│   ├── aws-smithy-types v0.54.4 (*)
│   ├── aws-types v0.54.1 (*)
├── aws-sdk-firehose v0.24.0
│   ├── aws-credential-types v0.54.1 (*)
│   ├── aws-endpoint v0.54.1 (*)
│   ├── aws-http v0.54.1 (*)
│   ├── aws-sig-auth v0.54.1 (*)
│   ├── aws-smithy-async v0.54.4 (*)
│   ├── aws-smithy-client v0.54.4 (*)
│   ├── aws-smithy-http v0.54.4 (*)
│   ├── aws-smithy-http-tower v0.54.4 (*)
│   ├── aws-smithy-json v0.54.2 (*)
│   ├── aws-smithy-types v0.54.4 (*)
│   ├── aws-types v0.54.1 (*)
├── aws-sdk-kinesis v0.24.0
│   ├── aws-credential-types v0.54.1 (*)
│   ├── aws-endpoint v0.54.1 (*)
│   ├── aws-http v0.54.1 (*)
│   ├── aws-sig-auth v0.54.1 (*)
│   ├── aws-smithy-async v0.54.4 (*)
│   ├── aws-smithy-client v0.54.4 (*)
│   ├── aws-smithy-http v0.54.4 (*)
│   ├── aws-smithy-http-tower v0.54.4 (*)
│   ├── aws-smithy-json v0.54.2 (*)
│   ├── aws-smithy-types v0.54.4 (*)
│   ├── aws-types v0.54.1 (*)
├── aws-sdk-s3 v0.24.0
│   ├── aws-credential-types v0.54.1 (*)
│   ├── aws-endpoint v0.54.1 (*)
│   ├── aws-http v0.54.1 (*)
│   ├── aws-sig-auth v0.54.1 (*)
│   ├── aws-sigv4 v0.54.1 (*)
│   ├── aws-smithy-async v0.54.4 (*)
│   ├── aws-smithy-checksums v0.54.2
│   │   ├── aws-smithy-http v0.54.4 (*)
│   │   ├── aws-smithy-types v0.54.4 (*)
│   ├── aws-smithy-client v0.54.4 (*)
│   ├── aws-smithy-eventstream v0.54.4 (*)
│   ├── aws-smithy-http v0.54.4 (*)
│   ├── aws-smithy-http-tower v0.54.4 (*)
│   ├── aws-smithy-json v0.54.2 (*)
│   ├── aws-smithy-types v0.54.4 (*)
│   ├── aws-smithy-xml v0.54.2 (*)
│   ├── aws-types v0.54.1 (*)
├── aws-sdk-sqs v0.24.0
│   ├── aws-credential-types v0.54.1 (*)
│   ├── aws-endpoint v0.54.1 (*)
│   ├── aws-http v0.54.1 (*)
│   ├── aws-sig-auth v0.54.1 (*)
│   ├── aws-smithy-async v0.54.4 (*)
│   ├── aws-smithy-client v0.54.4 (*)
│   ├── aws-smithy-http v0.54.4 (*)
│   ├── aws-smithy-http-tower v0.54.4 (*)
│   ├── aws-smithy-json v0.54.2 (*)
│   ├── aws-smithy-query v0.54.2 (*)
│   ├── aws-smithy-types v0.54.4 (*)
│   ├── aws-smithy-xml v0.54.2 (*)
│   ├── aws-types v0.54.1 (*)
├── aws-sigv4 v0.54.1 (*)
├── aws-smithy-async v0.54.4 (*)
├── aws-smithy-client v0.54.4 (*)
├── aws-smithy-http v0.54.4 (*)
├── aws-smithy-http-tower v0.54.4 (*)
├── aws-smithy-types v0.54.4 (*)
├── aws-types v0.54.1 (*)

Environment details (OS name and version, etc.)

Darwin COMP-J44M27KKWV 22.3.0 Darwin Kernel Version 22.3.0: Mon Jan 30 20:38:37 PST 2023; root:xnu-8792.81.3~2/RELEASE_ARM64_T6000 arm64 arm Darwin

Logs

2023-03-01T16:10:49.377040Z TRACE map_request{name="sigv4_sign_request"}: aws_sigv4::http_request::sign: signing request request=SignableRequest { method: POST, uri: https://logs.us-east-1.amazonaws.com/, headers: {"content-type": "application/x-amz-json-1.1", "x-amz-target": "Logs_20140328.DescribeLogGroups", "content-length": "61", "user-agent": "aws-sdk-rust/0.54.1 os/macos lang/rust/1.66.1", "x-amz-user-agent": "aws-sdk-rust/0.54.1 api/cloudwatchlogs/0.24.0 os/macos lang/rust/1.66.1"}, body: UnsignedPayload } params=SigningParams { access_key: "********", secret_key: "*******", security_token: None, region: "us-east-1", service_name: "logs", time: SystemTime { tv_sec: 1677687049, tv_nsec: 376983000 }, settings: SigningSettings { percent_encoding_mode: Double, payload_checksum_kind: NoHeader, signature_location: Headers, expires_in: None, excluded_headers: Some(["user-agent"]), uri_path_normalization_mode: Enabled } }
2023-03-01T16:10:49.377806Z TRACE map_request{name="sigv4_sign_request"}: aws_sigv4::http_request::sign: canonical_request=POST
/

content-length:61
content-type:application/x-amz-json-1.1
host:logs.us-east-1.amazonaws.com
x-amz-date:20230301T161049Z
x-amz-target:Logs_20140328.DescribeLogGroups
x-amz-user-agent:aws-sdk-rust/0.54.1 api/cloudwatchlogs/0.24.0 os/macos lang/rust/1.66.1

content-length;content-type;host;x-amz-date;x-amz-target;x-amz-user-agent
UNSIGNED-PAYLOAD
2023-03-01T16:10:49.378195Z TRACE dispatch: aws_smithy_http_tower::dispatch: dispatching request request=Request { method: POST, uri: https://logs.us-east-1.amazonaws.com/, version: HTTP/1.1, headers: {"content-type": "application/x-amz-json-1.1", "x-amz-target": "Logs_20140328.DescribeLogGroups", "content-length": "61", "user-agent": "aws-sdk-rust/0.54.1 os/macos lang/rust/1.66.1", "x-amz-user-agent": "aws-sdk-rust/0.54.1 api/cloudwatchlogs/0.24.0 os/macos lang/rust/1.66.1", "x-amz-date": "20230301T161049Z", "authorization": Sensitive}, body: SdkBody { inner: BoxBody, retryable: true } }
2023-03-01T16:10:49.440272Z TRACE load_response: aws_smithy_http::middleware: read HTTP response body http_response=Response { status: 400, version: HTTP/1.1, headers: {"x-amzn-requestid": "3dfbd4d5-ab57-4c08-9184-cee365f7466d", "content-type": "application/x-amz-json-1.1", "content-length": "229", "date": "Wed, 01 Mar 2023 16:10:48 GMT", "connection": "close"}, body: b"{\"__type\":\"InvalidSignatureException\",\"message\":\"The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.\"}" }
@spencergilbert spencergilbert added bug This issue is a bug. needs-triage This issue or PR still needs to be triaged. labels Mar 1, 2023
@Velfi
Copy link
Contributor

Velfi commented Mar 2, 2023

Hey @spencergilbert, thanks for submitting this issue. We'll add it to our backlog.

Adding a minimal example that reproduces the issue would be much appreciated.

@Velfi Velfi removed the needs-triage This issue or PR still needs to be triaged. label Mar 2, 2023
@rcoh
Copy link
Contributor

rcoh commented Mar 21, 2023

ah—I found the issue. When you wrap the body, we lose the ability to return bytes() when they're present without streaming. This causes the signer to fallback to unsigned payload. Let me see if I can figure out a workaround.

As a sanity check, will you try using SdkBody::from_path with those APIs? That should fail as well (for the same reason).

@rcoh
Copy link
Contributor

rcoh commented Mar 21, 2023

And as an immediate workaround, I would check to see if .bytes() returns Some on the body before you wrap it. If you only wrap bodies where .bytes() is None, then you will only wrap streaming bodies (sidestepping this issue).

@spencergilbert
Copy link
Author

Thanks! I'll take a look at those suggestions this week, I've been meaning to make a MRE but I've just been swamped 😭

@jmklix jmklix added the p2 This is a standard priority issue label Mar 30, 2023
@spencergilbert
Copy link
Author

Can confirm only wrapping when .bytes() returns None does avoid the issue, but our usage of the callback/wrapper is needed on all requests so unfortunately we can't use the workaround.

Sorry for the delay in the MRE, had a lot of going ons outside of work but I'm taking a look at that again.

@rcoh
Copy link
Contributor

rcoh commented Apr 3, 2023

ah, I see. Yeah that's tricky. If it's any consolation, when .bytes().is_some() more or less guarantees that it's a small directly serialized request body (instead of a potentially large streaming request).

Another thing we should consider is handling this on the signing side by attempting to clone() then read the body to the end during signing instead of just giving up.

A final option might be doing something like this to enable your dyn body to still provide a hint about bytes: https://github.com/awslabs/smithy-rs/compare/proto-bytes-hint?expand=1

@spencergilbert
Copy link
Author

Clarifying, @rcoh, is that final option something we could implement on our wrapper type or as part of the SDK itself?

@rcoh
Copy link
Contributor

rcoh commented Apr 4, 2023

We'll need to do something on the SDK side unfortunately

@spencergilbert
Copy link
Author

Would an MRE still be useful here?

@rcoh
Copy link
Contributor

rcoh commented Apr 5, 2023 via email

@Velfi
Copy link
Contributor

Velfi commented Apr 13, 2023

Hey @spencergilbert, we're PRing a change that should solve your issue.

The PR adds a new method to SdkBody called map_immutable which will preserve the inner body's bytes, if it has some.

Would you mind checking out the PR and letting me know what you think? I think you could even test it if you set your project to target this version of aws-smithy-http by a file path dependency (and maybe even with a Git dependency? I'm not sure about that though.)

@spencergilbert
Copy link
Author

Thank you! I'll try and carve out some time to test that soon.

@spencergilbert
Copy link
Author

Sorry @Velfi - I was pulled onto a separate project, I'm just coming back to this now.

I see the PR was closed without merging, did this go in a different direction or should I test the previous implementation and the PR could/should be revived?

@Velfi
Copy link
Contributor

Velfi commented Jun 15, 2023

I'm embarrassed to say I don't even remember why I closed the PR. It might have just been housecleaning.

We've been working on a new implementation of the client that changes a lot of things and we may make breaking changes to the body. We've also got work on our calendar to change the way that chunked encoding happens so that we can support more use-cases and I expect that work to be a little related to the closed PR.

What was your original reason for defining a body callback? Progress reporting? I think we should start by figuring out what we want to be able to do and then considering if we should extend the old SdkBody design or break it.

@spencergilbert
Copy link
Author

All good - we had been using the callback to accumulate the bytes sent which we then used in our internal telemetry. I included the original callback code below, it's pretty straightforward.

struct BodyCaptureCallback {
    bytes_sent: usize,
    shared_bytes_sent: Arc<AtomicUsize>,
}

impl BodyCaptureCallback {
    fn new() -> (Self, Arc<AtomicUsize>) {
        let shared_bytes_sent = Arc::new(AtomicUsize::new(0));

        (
            Self {
                bytes_sent: 0,
                shared_bytes_sent: Arc::clone(&shared_bytes_sent),
            },
            shared_bytes_sent,
        )
    }
}

impl BodyCallback for BodyCaptureCallback {
    fn update(&mut self, bytes: &[u8]) -> Result<(), BoxError> {
        // This gets called every time a chunk is read from the request body, which includes both static chunks and
        // streaming bodies. Just add the chunk's length to our running tally.
        self.bytes_sent += bytes.len();
        Ok(())
    }

    fn trailers(&self) -> Result<Option<headers::HeaderMap<headers::HeaderValue>>, BoxError> {
        Ok(None)
    }

    fn make_new(&self) -> Box<dyn BodyCallback> {
        // We technically don't use retries within the AWS side of the API clients, but we have to satisfy this trait
        // method, because `aws_smithy_http` uses the retry layer from `tower`, which clones the request regardless
        // before it even executes the first attempt... so there's no reason not to make it technically correct.
        Box::new(Self {
            bytes_sent: 0,
            shared_bytes_sent: Arc::clone(&self.shared_bytes_sent),
        })
    }
}

@spencergilbert
Copy link
Author

And a long time since I said I'd check... but I patched the crates we used and the proposed PR does appear to fix the issue @Velfi

spencergilbert added a commit to vectordotdev/aws-sdk-rust that referenced this issue Jun 22, 2023
@rcoh
Copy link
Contributor

rcoh commented Aug 8, 2023

oh great—we'll merge that PR. cc @Velfi can you resurrect and merge? Looks like it needs a rebase but should otherwise apply cleanly.

fuchsnj added a commit to vectordotdev/aws-sdk-rust that referenced this issue Aug 22, 2023
@rcoh rcoh added the pending-release This issue will be fixed by an approved PR that hasn't been released yet. label Sep 19, 2023
@rcoh
Copy link
Contributor

rcoh commented Sep 20, 2023

clarification here given the pending-release label—we didn't release runtime-library changes in the last release, just codegen. I'll update this when it's published

@rcoh
Copy link
Contributor

rcoh commented Nov 7, 2023

Fix verified:

use aws_sdk_s3::primitives::SdkBody;
fn main() {
    let body = SdkBody::from("1234");
    let body = body.map_preserve_contents(|b|SdkBody::from_body_0_4(b));
    assert_eq!(body.bytes(), Some(&b"1234"[..]));
}

@rcoh rcoh closed this as completed Nov 7, 2023
@rcoh rcoh removed the pending-release This issue will be fixed by an approved PR that hasn't been released yet. label Nov 7, 2023
Copy link

github-actions bot commented Nov 7, 2023

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see.
If you need more assistance, please either tag a team member or open a new issue that references this one.
If you wish to keep having a conversation with other community members under this issue feel free to do so.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug This issue is a bug. p2 This is a standard priority issue
Projects
None yet
Development

No branches or pull requests

4 participants