Recommended Way to Generate a Presigned Url to S3 Bucket in Ruby

Trying to generate a presigned url link so an user can download an Amazon S3 object, but getting invalid request

When you generate a pre-signed GET object URL, you need to provide all of the same params that you would pass to Aws::S3::Object#get.

s3.get(sse_customer_algorithm: 'AES256', sse_customer_key: customer_key).body.read

This means you need to pass the same sse_customer_* options to #presigned_url:

url = obj.presigned_url(:get,
sse_customer_algorithm: 'AES256',
sse_customer_key: customer_key)

This will ensure that the SDK correctly signs the headers that Amazon S3 expects when you make the final GET request. The next problem is that you are now responsible for sending those values along with the GET request as headers. Amazon S3 will not accept the algorithm and key in the query string.

uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.request_uri, {
"x-amz-server-side-encryption-customer-algorithm" => 'AES256',
"x-amz-server-side-encryption-customer-key" => Base64.encode64(cpk),
"x-amz-server-side-encryption-customer-key-MD5" => Base64.encode64(OpenSSL::Digest::MD5.digest(cpk))
})

Please note - while testing this, I found a bug in the presigned URL implementation of the current v2.0.33 version of the aws-sdk gem. This has been fixed now and should be part of v2.0.34 once it releases.

See the following gitst for a full example that patches the bug and demonstrates:

  • Uploads an object using a cpk
  • Gets the object using the SDK
  • Generates a presigned GET url
  • Downloads the object using just Net::HTTP and the presigned URL

You can view the sample script here:

https://gist.github.com/trevorrowe/49bfb9d59f83ad450a9e

Just replace the bucket_name and object_key variables at the top of the script.

Does an S3 bucket need to be Public to serve user viewable images to an app?

Objects in Amazon S3 are private by default. You can grant access to an object in several ways:

  • A Bucket Policy that can grant access to everyone ('Public'), or to specific IP addresses or users
  • An IAM Policy on an IAM User or IAM Group that grants access to that user or group -- however, they would need to access via an AWS SDK so that they can authenticate the call (eg when an application makes a request to S3, it would make an authenticated API call)
  • An Access Control List (ACL) on the object, which can make the object public without requiring the bucket to be public
  • By using an Amazon S3 pre-signed URL, which is a time-limited URL that provides temporary access to a private object

Given your use-case, an S3 pre-signed URL would be the best choice since the content is kept private but the application can generate a link that provides temporary access to the object. This can also be done with CloudFront.

Generating the pre-signed URL only takes a few lines of code and does not involve an API call to AWS. It is simply creating a hash of the request using your Secret Key, and then appending that hash as a 'signature'. Therefore, there is effectively no speed impact of generating pre-signed URLs for all of your images.

I don't see how SEO would be impacted by using pre-signed URLs. Only actual web pages (HTML) are tracked in SEO -- images are not relevant. Also, the URLs point to the normal image, but have some parameters at the end of the URL so they could be tracked the same as a non-signed URL.



Related Topics



Leave a reply



Submit