
A Better Way to Manage the Rails Secret Token - johnw
http://daniel.fone.net.nz/blog/2013/05/20/a-better-way-to-manage-the-rails-secret-token/
======
steveklabnik
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...](https://github.com/rails/rails/pull/3777#issuecomment-2893759)

> 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...](https://github.com/hotsh/rstat.us/blob/master/config/initializers/secret_token.rb)

Being generic is hard.

~~~
olalonde
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?

~~~
rst
The extra overhead could be kept pretty small. After being retrieved once, it
can be cached in the memory of a server process. So, there's one short SQL
query at process startup (or perhaps first request, depending on how you do
it), and negligible overhead after that.

~~~
joevandyk
This is what I do, works great.

Most of my application secrets/configuration/keys/tokens are stored in the
database.

The only one that's not is the information about how to connect to the
database. That's stored in the DATABASE_URL environment variable and it's
stored on each machine. envdir is used to start the apps, reading that
environment data.

------
willlll
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.

~~~
elithrar
I'm surprised that it doesn't? gorilla/sessions[1] does the same; and you can
eventually remove your old keys provided you keep your expiry times sane.

[1]: <http://www.gorillatoolkit.org/pkg/sessions#NewCookieStore>

------
charliesome
> _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
Doesn't Rails use randomly generated session IDs? Also, how does it allow an
attacker to trivially execute code?

~~~
danielfone
> Session data is marshal-ed Ruby data, deserializing it has the same risks as
> YAML.load.

[http://daniel.fone.net.nz/blog/2013/05/20/a-better-way-to-
ma...](http://daniel.fone.net.nz/blog/2013/05/20/a-better-way-to-manage-the-
rails-secret-token/#comment-902646816)

~~~
olalonde
That's a bit surprising. I'd be interested to read the reasoning behind this
design decision as most frameworks I have used store session data in a
database rather than directly in the cookie. Well I guess there is a pretty
damn good reason given Rails' reputation.

~~~
davesims
Rails' _default_ session is cookie store, _highly recommended_ is DB. It's a
config issue that depends on your app's particular DB setup, so default isn't
DB. But in session_store.rb you'll see this comment recommending against the
default:

    
    
      # Use the database for sessions instead of the cookie-based default,
      # which shouldn't be used to store highly confidential information
      # (create the session table with "rails generate session_migration")
      # ShopMobile::Application.config.session_store :active_record_store

~~~
gingerlime
also, in config/initializers/secret_token.rb:

    
    
       # Your secret key for verifying the integrity of signed cookies.
    

Does this mean that if you use redis/db-backed sessions you can safely ignore
this secret_token parameter completely or even delete this initializer?

UPDATE: I just tried to remove it from our environment, and everything seems
fine. Unless I'm missing something out, I'd say that's a far better and easier
solution overall. It gives you much better control over you session + you
don't have to worry about this configuration variable.

~~~
davesims
Since it's not only used for session cookies, I'd keep it around, on the off
chance I ever wanted a signed cookie, like:

    
    
      cookies.signed['some-id'] = model.id
    

Rails will use the secret here too.

~~~
gingerlime
just curious: when would you need a signed cookie though? i.e. What need does
it fulfil that neither the session store nor your database do?

~~~
davesims
I dunno, good question. Maybe something like this: there's some cases where
I'll prefer cookies to session data, esp if it's non-volatile data that I can
take or leave, like convenience values for the user on the js side, "last
selected item" or the like.

I don't _necessarily_ want that data in my session, or as a user attribute
that I have to migrate the DB for. If it's a pkid or something that isn't
necessarily sensitive, but I also don't want it to be too easy to get at, I
might use a signed cookie.

Now, I've never actually _done_ this... ;p

------
iguana
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.

~~~
duaneb
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.

~~~
ctz
Certain weird-shit UNIX operating systems do not provide privacy for a
process's environment. (eg. another user on the same box can see them with 'ps
e'). More relevantly, POSIX does not require it. The same is not true of
process memory.

------
manojlds
> 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
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']

~~~
johnvschmitt
ryannielson's solution is the best IMO, as it requires the environment
variable to be set, & most importantly, shows a nice error to the developer
should they miss it.

Even better, raise 'SECRET_TOKEN not set! Please refer to the doc in xyz'

So, the specific method for setting is in an "xyz" doc that your team keeps in
a SEPARATE location from the code repo.

And, we really need a standard way to do this, or Github pulls / forks will
have more friction or bad security when setting up forks.

Also, I really would rather put it in a file, not system env, as the env might
be setup different on different systems, & you'd hate to have that env
potentially shared in multi-user systems. Files are more reliably locked down.

------
ceol
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
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>

~~~
ceol
Another good point. Thanks for the link, too!

------
ryannielson
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
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
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
If it doesn't show up in a repo file then it is a bit better.

~~~
bifrost
Yes, but only marginally; You can get the environment of another process with
the "ps" command trivially. If you're in a shared environment you just made it
that much easier for other people to monkey with your stuff.

Here's a great example of the repo file checkin fail though:
<http://bit.ly/10dLiDz>

~~~
olalonde
> If you're in a shared environment

The common wisdom seems to be that you can forget about security in a shared
environment regardless. The "secret key in environment variable" technique is
mostly useful at protecting against malicious employees since it's easy to
limit access to the production server but not so easy to limit access to
configuration files which are in a Git repository.

~~~
bifrost
While I generally agree, there's a lot of shared environment out there that
people seem to think is secure. I also would posit that its probably easier to
get the calling environment through some flaw in bad programming vs getting a
file off the filesystem. I would also posit that not having strong controls on
your source tree is probably not a good thing as well...

~~~
duaneb
The only secure shared environments I would trust are jailed environments or
virtualized OSes, and even then only if I could control the hardware. Even
then there have been vulnerabilities which allow virtualized OSes to access
the host system (and "sideways" into other OSes), meaning even something like
EC2 is potentially vulnerable.

Don't trust your environment.

------
wolframarnold
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.

------
jsmoov
I've found that the figaro gem is also a nice gem to use re: secret tokens and
ENV variables in general - esp on Heroku.
<https://github.com/laserlemon/figaro>

------
seniorsassycat
I'm not sure I understand what dotenv does (or why you would need it to do
it).

~~~
Aqua_Geek
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.

~~~
duaneb
Couldn't you just read the file directly? e.g.

    
    
        secret_token = File.read('secret_token_dont_check_in')
    

I fail to see how loading the file into the environment and then into the
variable is anything but worse.

~~~
masklinn
The original issue is people checking their secret token files into their VCS
repository and publishing that. Getting the secret token from the ENV means it
probably won't be checked into the repo.

~~~
duaneb
It's still a file (how do you think it gets into the environment?). Whether
it's a ruby file or a .env file or a yaml file, it's still equally at risk of
being checked in.

------
gridscomputing
LOL BROGRAMMERS

------
SeoxyS
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
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.

~~~
SeoxyS
I wrote this comment with the assumption that we all understand the security
implications of what's going on, which is why I probably did a poor job of
making my point.

However, I think that relying on a single easy-to-compromise security token as
a single point of failure for your app's security is terrible practice.

A secure user authentication system is usually built by having a users
database that contains [user_id, nonce, password_key = pbkdf2(password,
nonce)]. When logging the user in, you should store this information in a
cookie: [user_id, login_timestamp, authentication_token = hmac-sha256(nonce,
login_timestamp, password_key)]. Then, on every subsequent request, you would
verify that the authentication_token matches what you'd expect based on the
login_timestamp and user_id that the client has provided. (Substitute this for
any equivalent-security scheme and crypto primitives.)

Now, if you have such a scheme in place, a secret token would provide no
additional security. You really shouldn't be storing session information in
cookies, but if you must, you can use a key derived from the nonce to sign the
cookie, removing the need for an app-global secret. But really, don't do that,
you should use a server side datastore for session information (like shopping
carts, etc.).

If you do not have such a scheme in place, you app is much more vulnerable to
attack. Something based on one global secret token (which by default is
checked in to your source code) opens up many more attack vectors that might
compromise the security of your app.

~~~
eropple
That's a completely valid route, for sure, but if your app's owned, your
database is owned and this is going to be blown as wide open as a secret
token.

