-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
S3 presigned urls do not sign headers x-amz-server-side-encryption-aws-kms-key-id, x-amz-server-side-encryption #874
Comments
Just a heads up. I'm looking into this. I believe the solution is going to invoke tracking down all of the known x-amz-* headers and determining which ones can, must, and must not be hoisted to the URL and which must be signed and sent as headers. |
Curious... is there a reference implementation for the AWS SDKs? Perhaps the Java one? Looks like it's handled here (including SSE headers, ...): https://github.com/aws/aws-sdk-java/blob/948bc1a418d357abf8fd46cc19123ced2e014ce0/aws-java-sdk-s3/src/main/java/com/amazonaws/services/s3/AmazonS3Client.java#L2563 |
So this change was made so that x-amz- headers would be sent via the querystring eliminating the need for users to provide these values twice. Before the change you would have to specify these values to the presigner and then again to your HTTP client to send them as headers. This was necessary because signature version 4 requires all header values that start with x-amz- to be signed, including their expected values. When the change was made, it was intended to be a bug-fix so that a user could provide the values once to the presigner and have the URL querystring contain their values. This means the presigned URL no longer signs them as headers as they are part of the request URI. Here is an example showing how to create a presigned PUT url with server-side-encryption and KMS and then upload something with Net::HTTP: require 'uri'
require 'logger'
obj = Aws::S3::Object.new('aws-sdk', 'secret')
uri = URI(obj.presigned_url(:put, {
server_side_encryption: 'aws:kms',
ssekms_key_id: "cb467d40-59be-44d7-813f-d0f281092da8",
}))
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.set_debug_output(Logger.new($stdout))
req = Net::HTTP::Put.new(uri.request_uri)
req.body = "secret-body-#{Time.now.to_i}"
res = http.request(req)
obj.ssekms_key_id # show that it is encrypted server-side This means you should be able to simply remove those headers from your HTTP request and everything should work. Clearly this was an unintentional side-effect of the bug-fix, and a breaking change. The tricky part is if this is reverted then newer users would be broken (those counting on the values to be part of the URI and not currently sending them as headers). This is a sort of catch 22 and I'm not sure what the best path forward. Thoughts? |
When I had tried just removing the headers originally, I was getting a generic authorization error message. I have a bucket policy set up to require that files are encrypted:
I'm guessing that when your object is PUT in your example above, it's not actually being encrypted. But I'll have to try again. Policy reference: http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html |
The final line of my example was there to demonstrate the object was encrypted: obj.ssekms_key_id # show that it is encrypted server-side That method returns a |
Here's what I get using mostly your example code (different bucket, object key, credentials):
If I remove the bucket policy restriction described above, it works:
I need to have a bucket policy that restricts to aws:kms encrypted file uploads. So, is there a way to write a bucket policy that checks the URL parameters? If not, I need to use the headers. |
Hmm.. Given Amazon S3 accepts the upload with the server side encryption header as a querystring, it seems like that should also be checked when matching the policy. I'll try following up on this and see what the expected behavior is. |
I spoke with an engineer on the S3 team. They are going to update their API reference documentation to reflect this limitation. As a work-around, I suppose we will want to add a Would this resolve the issue? |
It sounds like that would work, if I understand correctly. I'd get a URL that looks like it did previously ( It would be great if this could be mentioned in the CHANGELOG too. |
This issue appears to have resurfaced in 2.3.4.
Results in following request URL:
Which results in the error:
Downgrading to 2.0.39 solves the issue. I noticed in 2.0.39, the url has a semicolon after the X-Amz-SignedHeaders? Is the lack of the semicolon what is causing the error in 2.3.4?
|
Soft ping here. PR #1477 just opened addressing this feature request : ) |
I had the same issue as @tyrauber . I'm trying to put to a bucket with a presigned url and set the acl to
I'm sending these headers on the client:
The file starts to upload, but I get a 403 halfway through:
Yet it looks like the header is present in the url:
I tried removing the headers from my http request on the client side, but it doesn't seem to make a difference. I downgraded to 2.0.39 and things were working well (I have to specify the headers on the client side http request). The request url looks a little different:
|
@mcfiredrill Sorry to hear that you have to work around the problem by downgrading. So this is a known limitation from S3 side, we are actively working on a feature that allows flexible customizable presigned behavior in the long run. Meanwhile there is So here are some work-around with require `aws-sigv4`
# a s3 client at region 'us-west-2'
signer = Aws::Sigv4::Signer.new(
service: 's3',
region: 'us-west-2',
credentials_provider: client.config.credentials,
uri_escape_path: false,
)
# create presigned url for an object with bucket 'a-fancy-bucket' and key 'hello_world'
url = signer.presign_url(
http_method: 'PUT',
url:'https://a-fancy-bucket.s3-us-west-2.amazonaws.com/hello_world',
headers: {
"Content-Type" => "audio/mpeg",
"x-amz-acl" => "public-read"
}
body_digest: 'UNSIGNED-PAYLOAD',
)
# making request
body = ...
Net::HTTP.start(url.host) do |http|
http.send_request("PUT", url.request_uri, body, {
"content-type" => "audio/mpeg",
"x-amz-acl" => "public-read",
})
end
# => #<Net::HTTPOK 200 OK readbody=true> |
@cjyclaire Thanks I may give that Gem a shot 👍 |
i'm still having problems with this in aws-sdk-core 2.10.47 / aws-sigv4 1.0.2. i have a policy on my bucket that requires the x-amz-server-side-encryption parameter (i understand i can also just turn on encryption in the bucket settings, but that's beside the point). i'm trying to create a presigned url for a PUT request (uploading a PDF file) like: my_bucket.object(new_key).presigned_url(:put, {expires_in: 60, content_type: 'application/pdf', server_side_encryption: 'AES256'}) and then upload with an ajax request like this, using a file object from a browser form: $.ajax({url: presignedUrl, type: 'PUT', contentType: file.type, processData: false, data: file}) but i get a 403 when i try to execute the request. i see this in request.headers.keys.each do |key|
if key.match(/^x-amz/i)
params.set(key, request.headers.delete(key))
end
end so any param/header matching "x-amz-*" gets moved from the actual headers to the request params, and thus 1) isn't present in the actual headers, and 2) isn't included in the signed headers. this works fine if i remove that bucket policy, but with the policy in place, my understanding is that the server side encryption header must be included as an actual header and in the signed headers param. if i monkey patch the gem so it leaves the encryption header alone (i.e. comment out the snippet i pasted above) and add the header to my ajax request like this: $.ajax({url: presignedUrl, type: 'PUT', contentType: file.type, processData: false, data: file, beforeSend: function(xhr) {xhr.setRequestHeader('x-amz-server-side-encryption', 'AES256')}}) then it uploads successfully. so it seems like the code moving the headers to params is a bit overzealous and needs to have some exceptions for certain parameters. edit: i see this issue and the comments about aws-sigv4, but i thought that had just been absorbed into the main aws-sdk-ruby gem now, so i wasn't sure if this was still expected. edit 2: using Aws::Sigv4::Signer directly as @cjyclaire described above does seem to work. this whole thing was pretty frustrating/confusing to figure out, though. |
Reopening - deprecating usage of Feature Requests backlog markdown file. |
This issue has been here for so long, when can we get a real fix? Also, can someone offer a solution on how to fix the usage of I'm not sure how to use |
As far as I know, a real fix requires a major version bump as the fix would be breaking. Happy to take any pull requests if that's not the case. |
How about a solution where it doesn't delete the x-amz-* headers? It can still hoist them to querystring, and not call I finally got ActiveStorage to work with this solution. I monkeypatched Aws::Signers::V4#presigned_url from To summarize: Even though querystring works, headers are still needed for Bucket Policy. For those wanting to know the custom working S3 service I wrote for ActiveStorage:
You'll also have to add those headers to S3 bucket's CORS configuration:
|
Does the https://github.com/aws/aws-sdk-ruby/blob/master/gems/aws-sdk-s3/lib/aws-sdk-s3/presigner.rb#L124 It does not hoist headers: https://github.com/aws/aws-sdk-ruby/blob/master/gems/aws-sdk-s3/lib/aws-sdk-s3/presigner.rb#L210 And the method returns both the url and headers to send alongside it. This is in line with Trevor's recommendation (comment on the same thread) - #874 (comment) but was never implemented until recently. |
Maybe, it looks promising. I just can't test it for awhile. |
I'm pretty sure it solves this issue. If not, let me know and I'll re-open. |
I was able to test it today. The new |
Excellent @chase439 ! And when you said a "while" you really meant it. Thanks for getting back to me. |
Just to follow up, I had to use |
There's a regression of presigned urls that use KMS encryption. I've upgraded from 2.0.39 to 2.1.7, and 2.1.7 is broken.
In 2.0.39, when I call
I get a url with
...&X-Amz-SignedHeaders=host%3Bx-amz-server-side-encryption%3Bx-amz-server-side-encryption-aws-kms-key-id&...
But when I use 2.1.7, those headers are missing:
...&X-Amz-SignedHeaders=host&...
and when I apply the headers to my PUT request on that url (as required: http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectPUT.html#put-object-sse-specific-request-headers), I get:
Rolling back to 2.0.39 fixes my problem.
The text was updated successfully, but these errors were encountered: