views:

495

answers:

3

I'm looking for a way to supply an argument to a ruby on rails project at runtime. Essentially, our project uses public key cryptography to encrypt some sensitive client data and we want the ability to supply the password to the private key file at runtime.

+1  A: 

What is wrong with putting the password in a file that is chmod'ed to only be readable by the web server user?

Micah
+1  A: 

An easy way to do this would be to create a Rails plugin that takes arguments using 'gets' in its 'init.rb'. Allow me to cook-up a quick code sample:

Make a directory: '$railsRoot/vendor/plugins/startup_args/lib'

Create an object to store argument data in '$railsRoot/vendor/plugins/startup_args/lib/startup_args.rb':

module StartupArgs
 @@argHash = {}

 def self.setArg(key, value)
  @@argHash[key.to_sym] = value
 end

 def self.getArg(key)
  return @@argHash[key.to_sym]
 end
end

Load the StartupArgs module into the Rails project's namespace and populate it with arguments in '$railsRoot/vendor/plugins/startup_args/init.rb':

require "startup_args"

promptString = "Enter arg name (type nothing to continue):"

puts promptString
while (newArg = gets.chomp) != ""
 puts "Enter value for '#{newArg}':"
 newVal = gets.chomp
 StartupArgs.setArg(newArg, newVal)

 puts promptString
end

Now, during the Rails project's start-up process, it will try to take key-value pairs from the console. These pairs will be stored in the global-namespace object StartupArgs for later access (via 'StartupArgs.getArg()').

If you anticipate that your Rails project might be deployed in scenarios where the daemon will not have access to the console during startup-time, you could read from a named-pipe instead of the console's standard input.

Going one step further, you could remove all parts of 'init.rb' except for the 'require' statement and add an action that performs this setup to a controller that would take the relevant parameters as a post over the web. Make sure to configure Rails to prevent potentially sensitive parameters (e.g. passwords) from being entered into log files recording accesses or errors (especially if it might be used as an HTTP GET, with parameters in the URL).

(You get the same effect this way as with the system described above if you configure your other Rails actions to ignore requests until the setup action has stored the appropriate parameters to the global object.)

A Note For Micah: I don't have the reputation to comment directly on your post, so I will include my comment here. There might be a few reasons to consider a system that never requires the password to be represented in the filesystem. For example, a developer might be planning a Rails project that could be deployed on varying operating systems and in varying environments. If the developer determines that there could be scenarios in which an administrative or root user could be compromised, cannot be trusted, or cannot be asked to sign confidentiality and security agreements, the developer might decide to add the additional obfuscation of placing the password in memory only (in the interest of requiring a slightly less secure system or a slightly more clever attack to steal the password). Perhaps it could be considered a matter of relative costs: at low cost, the password can be hidden in a way that requires more expensive knowledge in order to retrieve.

Jsnydr
This is exactly the type of thing I was looking for, thanks!
latortuga
I'm glad I could help!
Jsnydr
+3  A: 

Any Ruby script has access to local environment variables through the ENV hash.

puts ENV['PATH']

So with any posix system (Linux, Unix, Mac OS) you can simply set it when calling the script, like this:

MY_ARG=supersecret ruby script.rb

The same is also valid for rails. If you put puts ENV['MY_ARG'] in your environment.rb and start your server:

$ MY_ARG=supersecret mongrel_rails start
** Starting Mongrel listening at 0.0.0.0:3000
** Starting Rails with development environment...
supersecret
** Rails loaded.
** Loading any Rails specific GemPlugins
** Signals ready.  TERM => stop.  USR2 => restart.  INT => stop (no restart).
** Rails signals registered.  HUP => reload (without restart).  It might not work well.
** Mongrel 1.1.5 available at 0.0.0.0:3000
** Use CTRL-C to stop.

An environment variable is by far the simplest solution in my opinion.

webmat
Definitely easier than my solution. One might be concerned that, in Linux environments, root can enumerate a process's environment (e.g. by reading /proc/[pid]/environ) trivially but would take more knowledge and work to get a password string out of Ruby's memory.
Jsnydr
I wonder if there is a way to set environment variables inline (like in your example) when calling executables from 'cmd.exe' in Windows.
Jsnydr
Hmm, good point about /proc/[pid]/environ. However it's still a bit more work (and less intuitive) than actually writing the key in a plain text file.
webmat