top | item 5735771

A Better Way to Manage the Rails Secret Token

79 points| johnw | 13 years ago |daniel.fone.net.nz | reply

57 comments

order
[+] steveklabnik|13 years ago|reply
We've had discussions about this several times, and haven't come up with something that's satisfactory as a generic replacement, other than "configuration could probably be improved."

For one example, see this from a year ago: https://github.com/rails/rails/pull/3777#issuecomment-289375...

> If we ignore them, this means a recently created, pushed and then cloned project is not going to work at all.

Some people replace it with a new file upon deployment, some people use ENV vars, some (most) people never open-source their app, and don't mind employees seeing it...

I personally do https://github.com/hotsh/rstat.us/blob/master/config/initial...

Being generic is hard.

[+] danielfone|13 years ago|reply
I absolutely agree that there's no easy solution to this (or it would've been "fixed" already).

> some (most) people never open-source their app, and don't mind employees seeing it...

One of my concerns is that people believe it's only a risk if they ever open source their application. While most apps don't have to worry about a motivated attacker in reality, the risk isn't simply secure or unsecure.

It's more a case of 'more difficult' vs. 'much easier' to compromise. I fear many engineers don't think of securing their apps like this. I know I've only recently begun to understand this way of thinking about security and it's changed the way I code.

[+] olalonde|13 years ago|reply
Have you considered generating a key at first startup and storing it in a database? Or would that introduce too much unnecessary overhead while introducing an attack vector through the database?
[+] hayksaakian|13 years ago|reply
This seems reasonable, why not do it that way?

To a newbie like me, everything will "just work" in development. In production, I'll get a pretty explicit/precise error message.

[+] willlll|13 years ago|reply
I wish Rails supported two secrets the way Rack::Cookie does by always signing with the first, but accepting either. That way you can rotate the secret without signing everyone out.
[+] charliesome|13 years ago|reply
> Knowing the secret token allows an attacker to trivially impersonate any user in the application.

Worse. Knowing the secret token allows an attacker to trivially execute code in your application.

Don't ever let your secret token become public knowledge, and if it does, you need to change it straight away.

[+] olalonde|13 years ago|reply
Doesn't Rails use randomly generated session IDs? Also, how does it allow an attacker to trivially execute code?
[+] iguana|13 years ago|reply
Your environment is not secure. The solution for this type of problem (keeping secrets out of source control) is to create a deployment-specific configuration file that is not kept in source control. It can be created out of a generic version that is kept in source control. Then, OS-level permissions are applied so the file is only readable by the processes that need access.
[+] danielfone|13 years ago|reply
It's worth noting that with 'dotenv' the configuration is stored in a file on the production server before it's loaded into the environment. The security of the method is not based on storing the token in the environment.

The primary advantage I intended was that the secret is confined to one part of the infrastructure (e.g. production server) instead of many (e.g. developer workstations, github, etc).

[+] duaneb|13 years ago|reply
How is the environment any less secure than memory? If someone can read or mutate your environment, you should assume your app is already compromised and OS-level permissions aren't going to do anything.
[+] manojlds|13 years ago|reply
> if Rails.env.development? or Rails.env.test?

I hate code like this that is explicitly aware of the environment. The code says what it will do in an environment, rather than the environment saying what the code should do in it.

[+] ryannielson|13 years ago|reply
Something more like the following is probably a better solution:

if ENV['SECRET_TOKEN'].blank? raise 'SECRET_TOKEN environment variable is not set!' end

App::Application.config.secret_token = ENV['SECRET_TOKEN']

[+] michaelrkn|13 years ago|reply
I had the same feeling. Inspired by this post, I just moved my secret token config into production.rb, test.rb, and development.rb, and deleted secret_token.rb.
[+] ceol|13 years ago|reply
For Python website projects, I enforce the creation of a _secrets.py file in the config folder that is ignored by git and contains all sensitive constants like database information and tokens. Then, in the _base.py settings file (what all other settings files inherit from), I make sure to `from _secrets import *`. I'm not a fan of setting tokens by environment because it gets a little too unwieldy to make sure bash/zsh/whatever sets the variable.

I haven't run into any problems using that method. Does anyone see a reason to prefer environment variables over it?

[+] grosskur|13 years ago|reply
For me the main reason is consistency. I can write apps in Python, Ruby, node, etc. and configure them all the same way.

The envdir program (from daemontools) is useful for setting environment variables in a shell-agnostic way, e.g., run your app with:

  envdir ./env python app.py
See also

http://12factor.net/config

[+] ricardobeat|13 years ago|reply
When using Heroku and similar hosts, the only way to upload files is via git push, so you won't be able to get your _secrets.py file into the server.
[+] ryannielson|13 years ago|reply
I wrote a gem called envious that could be used as an alternative to dotenv: https://github.com/RyanNielson/envious from what I can see it does a few things dotenv doesn't. Good guide though, I wrote Envious when I found out this problem existed.
[+] marco_salvatori|13 years ago|reply
A generic solution to this problem that I don't see mentioned is to have different configuration files for all of ones deployment environements (dev, test, integration....) and have an encrypted config file for production. All the config files go on source control so there are complete records of all changes. Then the key to decrypt the production configuration file is known to the build maintainer and also known by a dedicated build machine (maintained by the build manager). Doing things this way one can be as secure as one likes while at the same time, builds can be fully automated; builds can be machine independent (if you have a dynamic server environment or ever worry about losing a server), and builds have a complete change history in source control.
[+] bifrost|13 years ago|reply
Eh... Keeping that in the system environment isn't really any better than hardcoded in a file. There's a long history of "do not trust the system environment" when it comes to security so I can't say I'd recommend this. Last I checked it was also fairly trivial to dump this data out of a running program...

Unless you're grabbing that key out of "secure memory", a HSM or a TPM then its not really particularly secure.

[+] krapp|13 years ago|reply
If it doesn't show up in a repo file then it is a bit better.
[+] wolframarnold|13 years ago|reply
The way, we've solved this is to default to a hard-coded secret if the environment doesn't have it.

    App::Application.config.secret_token = ENV['COOKIE_SECRET'] || '<default secret>'
Secured environments like production get their own secret. Developer machines can use the default w/o additional overhead.
[+] seniorsassycat|13 years ago|reply
I'm not sure I understand what dotenv does (or why you would need it to do it).
[+] Aqua_Geek|13 years ago|reply
It loads environment variables from a .env file when starting your app so that you don't have to do

$ SECRET_TOKEN=abcdef SOME_OTHER_VAR=hello rails s

or pollute your .profile with a bunch of app-specific variables.

You don't need it to do it; it just makes it easier.

[+] SeoxyS|13 years ago|reply
I don't understand why a secret token is even necessary. This seems like bad design. As a matter of principle, the server should never trust the client. If authentication is necessary, it should be done on every request. If that is the case, what purpose does the token serve?

The entire principle behind HTTP - what enabled it to conquer and dominate the internet, is its statelessness. Storing and trusting things in cookies is a fundamental security design flaw.

[+] eropple|13 years ago|reply
I'm not sure you understand what's going on, which is probably why you've been downvoted. The secret token ensures that the server does not need to trust the client--that's what signing or encrypting session cookies does.

Authentication on every request requires you to store the user's authentication details on the client, which is considerably worse for security purposes than a signed or encrypted session cookie.