Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Obtain S3 presigned post url with query parameters for a mobile client

I am creating an API for a backend service with Rails 4. The service needs to upload an image file to an amazon s3 bucket.

I'd like to use a direct upload url, so that the clients manage the uploads to s3 and the server is not kept busy.

Currently I have the following prototypical rails action

def create
  filename = params[:filename]
  s3_direct_post = S3_BUCKET.presigned_post(key: "offers/#{SecureRandom.uuid}/#{filename}", acl: 'public-read')
  s3p = s3_direct_post.fields
  url = "#{s3_direct_post.url}/#{filename}?X-Amz-Algorithm=#{s3p['x-amz-algorithm']}&X-Amz-Credential=#{s3p['x-amz-credential']}&X-Amz-Date=#{s3p['x-amz-date']}&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=#{s3p['x-amz-signature']}"
  render json: {success: true, url: url}, status: :ok
end

This generates such an url:

https://my-bucket.s3.eu-central-1.amazonaws.com/test.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=MYKEY/20150420/eu-central-1/s3/aws4_request&X-Amz-Date=20150420T162603Z&X-Amz-Expires=3600&X-Amz-SignedHeaders=host&X-Amz-Signature=MYSIGNATURE

Now I try to post the test.png to this url with the following:

curl -v -T test.png "url"

and I get the following error response:

<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>MYKEY</AWSAccessKeyId>...

I believe the problem comes from the fact, that the specified X-Amz-SignedHeaders Header is wrong. I am not sure which headers are used by default from the amazon rails sdk gem.

How should I change my url generation, so that a mobile client can just take the url and post a file to it?

like image 845
peshkira Avatar asked Apr 20 '15 16:04

peshkira


People also ask

How does Presigned URL work S3?

When you use the URL to upload an object, Amazon S3 creates the object in the specified bucket. If an object with the same key that is specified in the presigned URL already exists in the bucket, Amazon S3 replaces the existing object with the uploaded object.

Does S3 bucket need to be public for Presigned URL?

All objects and buckets are private by default. However, you can use a presigned URL to optionally share objects or allow your customers/users to upload objects to buckets without AWS security credentials or permissions.

What Amazon S3 feature can be used to provide a time limited URL for an object to be uploaded or downloaded?

With Query String Authentication, customers can create a URL to an Amazon S3 object which is only valid for a limited time.


1 Answers

Here is a solution:

In config/initializers/aws.rb:

AWS_CREDS = Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY'])

Aws.config.update({
  region: 'eu-central-1',
  credentials: AWS_CREDS
})

S3 = Aws::S3::Resource.new('eu-central-1')
S3_BUCKET_NAME = ENV['S3_BUCKET_NAME']
S3_BUCKET = S3.bucket(S3_BUCKET_NAME)

In your model/controller/concern/or whatever:

obj = S3_BUCKET.object("offers/#{user.id}/#{self.id}")
url = obj.presigned_url(:put) # obj.presigned_url(:put, acl: 'public-read') #if you want to make the file public

Then to upload you can use a mobile client or curl:

curl -X PUT -T file_to_upload "url from above"

Note that you will have to add the x-amz-acl: public-read header if you used the public-read acl option:

curl -H "x-amz-acl: public-read" -X PUT -T file_to_upload "url from above"
like image 151
peshkira Avatar answered Sep 22 '22 21:09

peshkira