Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: allow download of files stored on S3 without showing the actual S3 URL to user

I have a Rails application hosted on Heroku. The app generates and stores PDF files on Amazon S3. Users can download these files for viewing in their browser or to save on their computer.

The problem I am having is that although downloading of these files is possible via the S3 URL (like "https://s3.amazonaws.com/my-bucket/F4D8CESSDF.pdf"), it is obviously NOT a good way to do it. It is not desirable to expose to the user so much information about the backend, not to mention the security issues that rise.

Is it possible to have my app somehow retrieve the file data from S3 in a controller, then create a download stream for the user, so that the Amazon URL is not exposed?

like image 438
futureshocked Avatar asked Sep 05 '12 10:09

futureshocked


People also ask

How do I get permission to download a S3 bucket?

Sign in to Amazon Web Services and go to S3 Management Console. Select the bucket from the left. At right, click the Properties button if it's not already expanded. Go to the Permissions tab and hit the Add Bucket Policy link.

Can I read S3 file without downloading?

Reading objects without downloading them Similarly, if you want to upload and read small pieces of textual data such as quotes, tweets, or news articles, you can do that using the S3 resource method put(), as demonstrated in the example below (Gist).


2 Answers

You can create your s3 objects as private and generate temporary public urls for them with url_for method (aws-s3 gem). This way you don't stream files through your app servers, which is more scalable. It also allows putting session based authorization (e.g. devise in your app), tracking of download events, etc.

In order to do this, change direct links to s3 hosted files into links to controller/action which creates temporary url and redirects to it. Like this:

class HostedFilesController < ApplicationController    def show     s3_name = params[:id] # sanitize name here, restrict access to only some paths, etc     AWS::S3::Base.establish_connection!( ... )     url = AWS::S3::S3Object.url_for(s3_name, YOUR_BUCKET, :expires_in => 2.minutes)     redirect_to url   end  end 

Hiding of amazon domain in download urls is usually done with DNS aliasing. You need to create CNAME record aliasing your subdomain, e.g. downloads.mydomain, to s3.amazonaws.com. Then you can specify :server option in AWS::S3::Base.establish_connection!(:server => "downloads.mydomain", ...) and S3 gem will use it for generating links.

like image 146
Serge Balyuk Avatar answered Sep 20 '22 04:09

Serge Balyuk


Yes, this is possible - just fetch the remote file with Rails and either store it temporarily on your server or send it directly from the buffer. The problem with this is of course the fact that you need to fetch the file first before you can serve it to the user. See this thread for a discussion, their solution is something like this:

#environment.rb require 'open-uri'  #controller def index   data = open(params[:file])   send_data data, :filename => params[:name], ... end 

This issue is also somewhat related.

like image 34
l4mpi Avatar answered Sep 21 '22 04:09

l4mpi