6 ms·
A Better Way to Manage the Rails Secret Token
- deleted 13y ago[deleted]
- ryannielson 13y agoI wrote a gem called envious that could be used as an alternative to dotenv: https://github.com/RyanNielson/envious 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.
- seniorsassycat 13y agoI'm not sure I understand what dotenv does (or why you would need it to do it).
- Aqua_Geek 13y agoIt 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 13y agoCouldn'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 13y agoThe 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 13y agoIt'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.
- willlll 13y agoI 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 13y agoI'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 http://www.gorillatoolkit.org/pkg/sessions#NewCookieStore
- manojlds 13y ago> 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 13y agoSomething 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 13y agoryannielson'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.
- wolframarnold 13y agoRails already raises an exception for you if the secret is blank. In: `actionpack-3.2.13/lib/action_controller/metal/http_authentication.rb`: raise "You must set config.secret_token in your app's config" if secret.blank?
- michaelrkn 13y agoI 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.
- bifrost 13y agoEh... 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 13y agoIf it doesn't show up in a repo file then it is a bit better.
- bifrost 13y agoYes, 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 http://bit.ly/10dLiDz
- olalonde 13y ago> 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 13y agoWhile 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...
- charliesome 13y ago> 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 13y agoDoesn't Rails use randomly generated session IDs? Also, how does it allow an attacker to trivially execute code?
- danielfone 13y ago> 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-manage-the-rails-secret-token/#comment-902646816 http://daniel.fone.net.nz/blog/2013/05/20/a-better-way-to-ma...
- olalonde 13y agoThat'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 13y agoRails' 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
- gridscomputing 13y agoLOL BROGRAMMERS
- steveklabnik 13y agoWe'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-2893759 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/initializers/secret_token.rb https://github.com/hotsh/rstat.us/blob/master/config/initial... Being generic is hard.
- danielfone 13y agoI 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.
- hayksaakian 13y agoThis 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.
- olalonde 13y agoHave 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 13y ago
- jsmoov 13y agoI'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 https://github.com/laserlemon/figaro
- ceol 13y agoFor 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?
- ricardobeat 13y agoWhen 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.
- ceol 13y agoOh, that's a very good point. I assume Heroku allows you to set environment variables? I've never really used it before, so I didn't even think of that possibility.
- steveklabnik 13y agoYes, Heroku does, with 'heroku config'.
- grosskur 13y agoFor 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 http://12factor.net/config
- ceol 13y agoAnother good point. Thanks for the link, too!
- iguana 13y agoYour 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 13y agoHow 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.
- justincormack 13y agoMost people probably set the environment for all processes not just on a need to know basis perhaps.
- ctz 13y agoCertain 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.
- danielfone 13y agoIt'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).
- SeoxyS 13y agoI 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 13y agoI'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 13y agoI 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.
- marco_salvatori 13y agoA 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.
- wolframarnold 13y agoThe 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.