15

I am using Ruby on Rails and AWS gem. I can get pre-signed URL for upload and download. But when I get the URL there is no file, and so setting acl to 'public-read' on the download-url doesn't work.

Use case is this: 1, server provides the user a path to upload content to my bucket that is not readable without credentials. 2, And that content needs to be public later: readable by anyone.

To clarify: I am not uploading the file, I am providing URL for my users to upload. At that time, I also want to give the user a URL that is readable by the public. It seems like it would be easier if I uploaded the file by myself. Also, read URL needs to never expire.

CoderStix
  • 175
  • 1
  • 1
  • 6
  • UPDATE: Fixed. Trevor was very helpful. It turns out that I was using IAM credential without PutWithACL policy set. Once I added that, put_url worked just as Trevor mentions in the accepted answer. – CoderStix Apr 23 '15 at 02:05

2 Answers2

24

When you generate a pre-signed URL for a PUT object request, you can specify the key and the ACL the uploader must use. If I wanted the user to upload an objet to my bucket with the key "files/hello.txt" and the file should be publicly readable, I can do the following:

s3 = Aws::S3::Resource.new
obj = s3.bucket('bucket-name').object('files/hello.text')

put_url = obj.presigned_url(:put, acl: 'public-read', expires_in: 3600 * 24)
#=> "https://bucket-name.s3.amazonaws.com/files/hello.text?X-Amz-..."

obj.public_url
#=> "https://bucket-name.s3.amazonaws.com/files/hello.text"

I can give the put_url to someone else. This URL will allow them to PUT an object to the URL. It has the following conditions:

  • The PUT request must be made within the given expiration. In the example above I specified 24 hours. The :expires_in option may not exceed 1 week.
  • The PUT request must specify the HTTP header of 'x-amz-acl' with the value of 'public-read'.

Using the put_url, I can upload any an object using Ruby's Net::HTTP:

require 'net/http'

uri = URI.parse(put_url)

request = Net::HTTP::Put.new(uri.request_uri, 'x-amz-acl' => 'public-read')
request.body = 'Hello World!'

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true   
resp = http.request(request)

Now the object has been uploaded by someone else, I can make a vanilla GET request to the #public_url. This could be done by a browser, curl, wget, etc.

Trevor Rowe
  • 6,499
  • 2
  • 27
  • 35
  • 1
    Additionally, there is also a pre-signed POST implementation for users where the user is going to upload the object from a browser. – Trevor Rowe Apr 15 '15 at 06:51
  • You linked to an error. The error indicated that your CURL command made a GET request instead of the expected PUT request. I did the following and it worked: curl -H "x-amz-acl:public-read" https://..... -T upapp.txt – Trevor Rowe Apr 22 '15 at 16:07
  • 1
    This is not available in AWSSDK for C# - does anyone know how to specify the `acl` parameter during the presigned URL generation? – Mathias Lykkegaard Lorenzen Apr 25 '19 at 12:04
  • For AWSSDK in C#, see https://github.com/aws/aws-sdk-net/issues/555. – Brant Olsen Mar 17 '21 at 11:09
  • For those running into the same Issue (I write BE in Go and FE in JS). The Magic here is this: X-AMZ-ACL during the Form-Upload / FileUpload – itinance May 19 '22 at 10:31
0

You have two options:

  • Set the ACL on the object to 'public-read' when you PUT the object. This allows you to use the public url without a signature to GET the object.
  • Let the ACL on the object default to private and provide pre-signed GET urls for users. These expire, so you have to generate new URLs as needed. A pre-signed URL allows someone to send GET request to the object without credentials themselves.

Upload a public object and generate a public url:

require 'aws-sdk'

s3 = Aws::S3::Resource.new
s3.bucket('bucket-name').object('key').upload_file('/path/to/file', acl:'public-read')
s3.public_url
#=> "https://bucket-name.s3.amazonaws.com/key"

Upload a private object and generate a GET url that is good for 1-hour:

s3 = Aws::S3::Resource.new
s3.bucket('bucket-name').object('key').upload_file('/path/to/file')
s3.presigned_url(:get, expires_in: 3600)
#=> "https://bucket-name.s3.amazonaws.com/key?X-Amz-Algorithm=AWS4-HMAC-SHA256&..."
Trevor Rowe
  • 6,499
  • 2
  • 27
  • 35
  • Use case is a bit different from what you describe. I am not uploading the file, I am providing URL for my users to upload. At that time, I also want to give the user a URL that is readable by the public. It seems like it would be easier if I uploaded the file by myself. Also, read URL needs to never expire. – CoderStix Apr 15 '15 at 03:44