Protecting process’s envvars from exposure

configurationenvironment-variablespermissionsSecurity

From I've read about current best practices for developing web applications (such as the Twelve Factor guide), the recommended approach for storing configuration data is within environment variables. This is specifically contrasted from keeping them inside configuration files, as those are often accidentally checked into the project's repository, breaking the "code/config" separation, and potentially exposing secrets (such as API keys, salts, etc.).

However, what I haven't seen addressed is the defense against accidental exposure. When using configuration files, one can set up file permissions accordingly, preventing anyone but specific users from reading the secret configuration. But Unix systems do not seem to offer such protection for the environment variables of a process — even if the process is started by different user:

$ sudo -u root x=5 sleep 30
$ ps au -E | grep sleep  # in different terminal
root 53814 s003  S+     0:00.01 sudo -u root x=5 sleep 30

Should the x variable contain sensitive data, it would be possible to read it just by having login access to the machine, as any user (not necessarily as root, or the user who started the process we want to snoop on). Therefore, this approach to config seems less secure (from the defense-in-depth PoV; obviously, the machine should be protected from unauthorized access) than configuration files — unless we can somehow protect the environment variables.

My question is thus: is there a way to prevent other users on the same Unix system from seeing the environment variables of a process (through ps a -E and any other possible means)?

Best Answer

Firstly, great question!

The -E switch only shows the environment at the time the process is launched, so any changes would be invisible to this method. Thus if the data is loaded into the environment after launching the process, then it will not be discoverable through this method. However, this is likely not a feasible strategy, IMHO, for reasons discussed below.

I agree with the Twelve Factor that the environment variable trick is the way to go. However, there are some important caveats that are not made clear in the Twelve Factor guide, as well as some practical considerations and techniques to get there.

Firstly, environment variables are volatile (non-persistent). All data is lost on reboot. This means that if the configuration isn't persisted somewhere, it will be non- recoverable in the event of a server reboot, unless it is in memory in /dev/brain0 of the sys-admin (not a gamble I would make with 128-bit keys). If that is the server's private key(s), you have at best a major and at worst a catastrophic problem on your hands (your CA may need to re-issue the keys). This makes a good case that the config must be written down somewhere, and I don't think many people would dispute that. Where/how to store it however, is what is important.

The Twelve Factor App addresses this though not in a straight-forward way. "A litmus test for whether an app has all config correctly factored out of the code is whether the codebase could be made open source at any moment, without compromising any credentials." So we know that we have to write it down somewhere, and that we can't put the config in the codebase. "Another approach to config is the use of config files which are not checked into revision control." This is what I would do.

The Twelve Factor App says, "it’s easy to mistakenly check in a config file to the repo." To counter this, production config (like private keys) should be different than development and test config. They should also be strictly controlled. Some version control systems like git have blacklists (like a .gitignore file) that prevent checking in certain files. These should also be used. "There is a tendency for config files to be scattered about in different places and different formats, making it hard to see and manage all the config in one place. Further, these formats tend to be language- or framework-specific." This is countered by simply defining the "environment variables" in the config file. This can then be loaded into the environment at startup by the server.

What I would do:

  1. Store the production server config info (like the server's private key(s)) in a separate repo that is different and distinct from the main repo
  2. Protect the production config file(s) very well. Only a few people need to access it, not every developer on the team. QA/DevOps/Sys-admins/etc for example. Treat this file like the keys to the server, because they are probably in it. Development and test environments should have different credentials and keys than production. These can then be stored more loosely
  3. Have the server's init routine load the config file info into the live environment variables
  4. Have the app load the config info it needs from the environment variables at runtime
Related Question