Making S3 file uploads private yet accessible via your rails app using Paperclip

December 20th, 2011 by andy Leave a reply »

The need

I needed to make file uploads to S3 secure. Files must be private in S3 (i.e. not accessible if someone guessed the url), yet accessible via the rails based web app.

The solution

Add this to the rails model:

has_attached_file :attachment,

:storage => :s3,

:s3_credentials => ‘config/amazon_s3.yml’,

:s3_permissions => :private

Replaced this in the view:

Replaced: asset.attachment.url

With: asset.attachment.expiring_url(60)

This keeps the files private, and the app creates expiring urls as users request them, which expire after a minute.

What Amazon Say

Query String Request Authentication Alternative

You can authenticate certain types of requests by passing the required information as query-string parameters instead of using the Authorization HTTP header. This is useful for enabling direct third-party browser access to your private Amazon S3 data, without proxying the request. The idea is to construct a “pre-signed” request and encode it as a URL that an end-user’s browser can retrieve. Additionally, you can limit a pre-signed request by specifying an expiration time.

Creating a Signature

Following is an example query string authenticated Amazon S3 REST request.

GET /photos/puppy.jpg
?AWSAccessKeyId=0PN5J17HBGZHT7JJ3X82&Expires=1141889120&Signature=vjbyPxybdZaNmGa%2ByT272YEAiv4%3D HTTP/1.1
Host: johnsmith.s3.amazonaws.com
Date: Mon, 26 Mar 2007 19:37:58 +0000

The query string request authentication method doesn’t require any special HTTP headers. Instead, the required authentication elements are specified as query string parameters:

Query String Parameter Name Example Value Description
AWSAccessKeyId 0PN5J17HBGZHT7JJ3X82 Your AWS Access Key Id. Specifies the AWS Secret Access Key used to sign the request, and (indirectly) the identity of the developer making the request.
Expires 1141889120 The time when the signature expires, specified as the number of seconds since the epoch (00:00:00 UTC on January 1, 1970). A request received after this time (according to the server), will be rejected.
Signature vjbyPxybdZaNmGa%2ByT272YEAiv4%3D The URL encoding of the Base64 encoding of the HMAC-SHA1 of StringToSign.

The query string request authentication method differs slightly from the ordinary method but only in the format of the Signature request parameter and the StringToSign element. Following is pseudo-grammar that illustrates the query string request authentication method.

Signature = URL-Encode( Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) ) );

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

YourSecretAccessKeyID is the AWS Secret Access Key ID Amazon assigns to you when you sign up to be an Amazon Web Service developer. Notice how the Signature is URL-Encoded to make it suitable for placement in the query-string. Also note that in StringToSign, the HTTP Date positional element has been replaced with Expires. The CanonicalizedAmzHeaders andCanonicalizedResource are the same.

Example Query String Request Authentication

Request StringToSign
GET /photos/puppy.jpg?AWSAccessKeyId=0PN5J17HBGZHT7JJ3X82&
    Signature=rucSbH0yNEcP9oM2XNlouVI3BH4%3D&
    Expires=1175139620 HTTP/1.1

Host: johnsmith.s3.amazonaws.com
GET\n
\n
\n
1175139620\n

/johnsmith/photos/puppy.jpg

We assume that when a browser makes the GET request, it won’t provide a Content-MD5 or a Content-Type header, nor will it set any x-amz- headers, so those parts of the StringToSign

has_attached_file :attachment,
:storage => :s3,
:s3_credentials => ‘config/amazon_s3.yml’,
:s3_permissions => :private
Advertisement

1 comment

  1. andy says:

    As a note, i tweaked the code view code to make testing more possible. Full details on post: http://www.andygoundry.com/2011/12/21/using-cucumber-to-test-s3-expiring-urls/

Leave a Reply