views:

569

answers:

3

Hello all,

I am about to begin writing a Rails application that will allow clients to have a separate subdomain for their access to our application. Thinking from a data security standpoint, it would be nice if each client's access was truly limited to their database, that way, if there is a bug in production code, they would only be able to access their own database and not that of any other clients.

I know the code behind how to do what I want, but I was wondering if there was a simpler solution that I might be missing. How would you go about securing client data so that in the event of a bug or hacker threat, their data would be less likely to be exposed?

A: 

Off the top of my head you could run a new server instance for each subdomain using different environment.

But that won't scale very well.

However the first few google hits for multiple rails databases turn up some new suggestions. Putting together the information in those links provides this wholly untested solution for a single server instance.

You'll need to add a database entry for each subdomain in your databases.yml. Then add a before_filter to your application controller

Update! Example reloads the database configurations dynamically. Unfortunately there's no good way to make the update rails wide without messing with your server's internals. So the database configuration will have to be reloaded on every request.

This example assumes database entries in databases.yml are named after subdomains.

config/database.yml

login: &login
  adapter: mysql
  username: rails
  password: IamAStrongPassword!
  host:  localhost

production:
  <<: *login
  database: mysite_www

subdomain1:
  <<: *login
  database: mysite_subdomain1

subdomain2:
  <<: *login
  database: mysite_subdomain2
...

app/controllers/application_controller.rb require 'erb' before_filter :switch_db_connection

def switch_db_connection
  subdomain = request.subdomains.first
  ActiveRecord::Base.configurations = YAML::load(ERB.new(IO.read(Rails.configuration.database_configuration_file)).result)
  ActiveRecord::Base.establish_connection("mysite_#{subdomain}") 
end

As I said it's completely untested. But I don't foresee any major problems. If it doesn't work hopefully it puts you on the right track.

EmFi
The problem with this is that when you add a new subdomain you're going to need to restart all the server instances. If you're adding a lot of subdomains at once, this is going to mean some downtime.
Ryan Bigg
Made the example dynamic in a single server instance. It's still untested though.
EmFi
A: 

Turns out I just asked a really similar question but quite a bit further along in development - I've included three ideas for how to go about securely using a single database in there.

qfinder
Thanks for the link. Gives me a few ideas, but it looks like there isn't a really good solution for this problem yet.
Topher Fangio
+5  A: 

Here is some code I use for this very problem:

application_controller.rb

before_filter :set_website

helper_method :current_website

# I use the entire domain, just change to find_by_subdomain and pass only the subdomain
def current_website    
  @website ||= Website.find_by_domain(request.host)
end

def set_database
  current_website.use_database
end

# Bonus - add view_path
def set_paths
  self.prepend_view_path current_website.view_path unless current_website.view_path.blank?
end

Website.rb

def use_database
  ActiveRecord::Base.establish_connection(website_connection)
end

# Revert back to the shared database
def revert_database
  ActiveRecord::Base.establish_connection(default_connection)
end

private

# Regular database.yml configuration hash
def default_connection
  @default_config ||= ActiveRecord::Base.connection.instance_variable_get("@config").dup
end

# Return regular connection hash but with database name changed
# The database name is a attribute (column in the database)
def website_connection
  default_connection.dup.update(:database => database_name)
end

Hope this helps!

Kris
+1 - Definitely very useful. Can you give me an idea of the performance hit (if any noticeable) with this approach?
Topher Fangio
Sorry I do not have an benchmarks regarding this. I only have this working on a private beta at the moment.
Kris
Anyone have any speculation on whether this will take a terrible performance hit or not? I'd like to do something similar for a production site.
NotDan
The site I use this code on get 20,000 page views (no caching) a month, so any performance hit is not noticeable. If you are looking at 20,000+ per day I would suggest making a simple site and doing some stress testing.
Kris
By the way I pretty sure the performance hit is low for switching database connection details (ie. database name) as it is simply a string and is done early, before a real connection is made (if it is done in the first before_filter) to the database. Adding view_paths on the other hand I believe can be more expensive. This however is pure speculation, only testing will tell the truth.
Kris