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

December 20th, 2011 by andy 1 comment »

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

Using rvm in conjunction with bundler

November 16th, 2011 by andy No comments »

I’m starting to use rvm with my rails projects and am wondering how to be use it in conjunction with bundler, as bundler already does a good job of managing the gems i need in my project.

To keep things simple for now, i’ve decided to keep bundler doing what it’s been doing (managing the project gems), and use rvm to simply specify the versions of ruby and bundler to use within the project.

So, in my project folder, i have 2 files, 1 for rvm and 1 for bundler:

  • .rvmrc
    This includes only the following:
    rvm use 1.9.2@development –create
  • Gemfile
    Includes all gems need within the project

The @development specifies that the app is using this rvm gemset, meaning that if i wish, i could have multiple gemsets and switch between them. As i’m currently handing off most of the gem management responsibility to bundler, this doesn’t makes sense at the moment, but could be useful later as i possibly migrate further responsibility over to rvm over time.

Making rvm gemsets work under OS X Lion with XCode 4.2

November 16th, 2011 by andy 1 comment »

Running Xcode 4.2 on OS X will cause issues with RVM. Here are details of the issues and fix.The process takes no more than 5 minutes in total.

Issue

gem install bundler
/Users/me/.rvm/rubies/ruby-1.8.7-p352/lib/ruby/1.8/timeout.rb:60: [BUG] Bus Error
ruby 1.8.7 (2011-06-30 patchlevel 352) [i686-darwin11.1.0]

Abort trap: 6

Fix

  1. Install Xcode 4.2
  2. Install the gcc standalone compiler from https://github.com/kennethreitz/osx-gcc-installer (which replaces Xcode)
  3. Add “export CC=gcc-4.2″ to your ~/.bash_profile or equivalent (don’t forget to reload it)
  4. Run “rvm implode” then re-install rvm http://beginrescueend.com/

Getting up and running with Git and Rails on EC2

November 6th, 2011 by andy No comments »

Here’s a short list of things to do to get Git and Rails running on EC2:

Install Git

sudo yum install -y git
  • sudo yum install -y git

Install Rails

  • sudo yum install -y rubygems ruby-devel gcc libxml2 libxml2-devel libxslt libxslt-devel mysql mysql-devel
  • sudo gem update –system
  • sudo gem install rails

Quick ref – Kernel.const_get and .constantize

July 26th, 2011 by andy No comments »

Just a quick ref. If you have a string that matches a class name in your app, you can use that string to interact with that model. Here’s how:

string = “User”

string.constantize OR Kernel.const_get(string) – the former only works in rails (as it’s part of ActiveSupport::Inflector)