Rokibul Hasan
Rokibul Hasan

Reputation: 4146

403 Forbidden for amazon s3 put api to upload file in Rails App

I have the following aws s3 api end point from third party application

https://xxx.s3.amazonaws.com/uploads/74d75512c28b49358846f959bd798536?Signature=lxBIZJD7DN4QK3LPmsHxR7D2eTA%3D&Expires=1506108780&AWSAccessKeyId=AKIAJG6Z6A5TUL7ULPXA

I am trying to make an PUT request with this url to upload an File, but always get the following error

#<Net::HTTPForbidden 403 Forbidden readbody=true>

Update And here is the response body

<?xml version="1.0" encoding="UTF-8"?>
<Error>
   <Code>SignatureDoesNotMatch</Code>
   <Message>
      The request signature we calculated does not match the 
      signature you provided. Check your key and signing method.
  </Message>
  <AWSAccessKeyId>AKIAJG6Z6A5TUL7ULPXA</AWSAccessKeyId>
  <StringToSign>
    PUT

    application/xml
    1506144576
    /xxx/uploads/93a64f8081804604be917b4185c5ed58
  </StringToSign>
  <SignatureProvided>8pwtC2vtN4LuOwGc887AlT2ZnUc=</SignatureProvided>
  <StringToSignBytes>50 55 </StringToSignBytes>
  <RequestId>B2B18369BA23A92F</RequestId>
  <HostId>YCtjOJsfskITKxjW96ouZq1BV=</HostId>
</Error>

If I inspect the response it shows following data, but did not understood what's wrong with this? no other clue, anyone can help will be appreciated.

{
 "x-amz-request-id" => [
    [0] "385D5CADFE6175FC"
],
       "x-amz-id-2" => [
    [0] "4uD51Gb/rJy0QooQVmF25Qbp0E568bB9v1P4Grg9CTM2dJ/Iiccad/IyuuEnWDphlGZrr8ZUnQw="
],
     "content-type" => [
    [0] "application/xml"
],
"transfer-encoding" => [
    [0] "chunked"
],
             "date" => [
    [0] "Fri, 22 Sep 2017 19:28:01 GMT"
],
           "server" => [
    [0] "AmazonS3"
]
}

I am running the following code snippet

uri = URI.parse(aws_api_end_point)
request = Net::HTTP::Put.new(uri)

request.body = File.read(file_path)
request.content_type = 'application/xml'

http = Net::HTTP.start(uri.host, uri.port, :use_ssl => true)
response = http.request(request)

Note:: I am not using any aws SDK.

Upvotes: 0

Views: 912

Answers (1)

Michael - sqlbot
Michael - sqlbot

Reputation: 178966

Request signing is deterministic -- for a given request at a given moment in time, there is exactly one valid signature. For all practical purposes, the opposite is also true -- for any signature, there's only one valid request you can make. Anything else, and the signature does not match.

The algorithm is designed not to be reverse-engineered, so we can't say what request they expected you to make when they gave you the signature.

But, we do have this. I believe some whitespace was lost, so I have added it back in:

<StringToSign>
  PUT

  application/x-www-form-urlencoded
  1506144576

  /xxx/uploads/93a64f8081804604be917b4185c5ed58
</StringToSign>

This is the request you made (not the request you were expected to make), converted to its canonical form using this pseudocode.

StringToSign = 
  HTTP-VERB + "\n" +
  Content-MD5 + "\n" +
  Content-Type + "\n" +
  Expires + "\n" +
  CanonicalizedAmzHeaders + 
  CanonicalizedResource;   

The most obvious candidate for a problem is Content-Type. It is almost a certainty that they did not expect you to use application/x-www-form-urlencoded because the S3 PUT Object operation does not use HTML Form uploads. S3 PUT expects the raw octets of the object in the request body, and a Content-Type header to match. S3 itself does not actually validate whether they match (header vs. body), but without at least the expected Content-Type header, the upload will be blocked.

If you are providing a Content-Type to the 3rd party API, then your upload needs to use that same type on the S3 upload, because the signature expects it.

Unfortunately, for troubleshooting purposes, there is an infinite number of things that can be done wrong to invalidate a request intended to be used with a pre-signed URL. The documentation from the third party should clarify the structure of the subsequent request they expect you to make.

Upvotes: 1

Related Questions