To clarify, the security and flexibility are gained by putting secrets into environment variables.
.ENV
is a convenience and, ideally, is not used in production. Many hosting services will handle deployment for you, and provide a way to store production environment variables securely, without .ENV
.
Putting secrets and configuration in environment variables is about mitigating the damage of more common breeches, eliminating classes of mistakes which can lead to security vulnerabilities, and making deployment simple and flexible.
Secrets are put into environment variables so they never touch disk when deployed. Only the server process and its children have the secrets. This mitigates the damage if an attacker...
- gains read-access to your production server's filesystem
- gains read-access to your code
- can execute code, even your code, on your production servers
To get your environment variables, the attacker must be able to alter code on your production server, or inject it into your deployment process. If someone can inject print_r
into your production, or if you forgot to delete phpinfo.php, a great many things have gone wrong with your release procedure.
Practical security is layered. If one layer is breached, the rest are still secure. If an attacker gains access to your filesystem, they don't have your secrets. If you're worried they'll alter your running code, put your code on a read-only filesystem.
Configuration is put into environment variables to simplify deployment. One does not need to have separate config files for development, and testing, and staging, and production, and each customer. Instead, each deployment sets its own environment variables.
If you're following Point 10 of the Twelve Factor App, different environments and deployments should have few differences.
.ENV
is a optional compromise for convenience. Developers will be running bits of code in many different environments, and remembering to set environment variables in all of them get tedious. Tedium breeds mistakes. So it's better to have a fairly secure process, and all the secrets in a single location.
Development machines have different attack surfaces than a production machine. For example, they can be stolen. One of the important security layers is to ensure developers use an encrypted filesystem, encrypted virtual memory, and screen locks; if their machine is stolen, the information on the disk is safe.
Even so, .ENV
should not contain production secrets. Developers should be using development databases and accounts.
If .ENV
is used for deployment, then it should be encrypted. Then one must only secure a single, per-developer key which is put in, you guessed it, an environment variable. Rails Encrypted Credentials uses this approach.
Is there anything particularly wrong with the good old config.php stored outside document root?
There are many disadvantages.
First and foremost, your secrets must live on your production server's disk. Anyone with read access to that file now has your secrets. Putting it outside document root is little protection, there are many security vulnerabilities which allow access to files outside document root.
config.php
is code and is vulnerable to additional classes of attacks that a data file is not.
config.php
can get large and complex making it difficult to tell which parts are secrets and which are not. This makes security audits difficult.
config.php
requires putting your secrets on disk. .ENV
is optional, you can develop and deploy using only environment variables.
config.php
can easily find its way into the code repository.
Each deployment needs a slightly different config.php
with different secrets making deployment more complex.
In contrast, .ENV
is a small, simple data file with a single purpose. Your QA and deployment process can automatically detect if .ENV
slipped in, or if it is unencrypted. There is a single, simple data file which needs to be changed between environments.
And .ENV
is entirely optional.
I mean you could put that in git ignore too and it's not going to be
part of your code at all.
config.php
is code and should be tested and version controlled. Not having it as part of your repository complicates deployment. This is point 1 of the Twelve Factor App. .ENV
minimizes the compromises to your development process. And, unlike config.php
, it is optional.
There are also other ways it could leak (e.g., logs).
You are correct that logging is a potential security leak. That doesn't mean we give up.
You can set different log levels in development vs production using, you guessed it, environment variables. Production log levels would log less and be more careful about what is logged.
You can add a scrubber to your logging function, there are many available, to remove any sensitive information before it is logged. Some look for common patterns such as passwords in URLs, credit card numbers, and PII. With all your secrets in .ENV
your logger could automatically remove secrets with a simple regex.
And rather than logging to a local file, you can also stream your logs to a logging service. This is point 11 in the Twelve Factor App. Now your logs are off-site; your production server can be compromised but your logs are still safe.
The Twelve-Factor App takes some getting used to. PHP is an older language with more traditional ideas and may require more adaptation than other languages. I've been following it for a couple years in Ruby. It's made a marked improvement in the speed and reliability of my processes.
And I hope now you understand why Environment Variables Considered Harmful for Your Secrets is itself harmful. The author is concerned about exposing secrets to child processes, a valid concern, but then advocates putting those secrets on disk; a far broader exposure. And since child processes are run by the same user as the parent, the child processes can see the secrets file anyway.
If you truly don't want to store any secrets locally at all, look into Vault.