How to Keep Sensitive Information Secret
Environment variables are the best way to store configuration that depends on where the application is run - for example, some API key that might be set to one value while developing locally and another value on production.
When these values are sensitive and need to be kept private, you can safely store them by using Symfony's secrets management system - sometimes called a "vault".
Note
The Secrets system requires the Sodium PHP extension.
Generate Cryptographic Keys
In order to encrypt and decrypt secrets, Symfony needs cryptographic keys. A pair of keys can be generated by running:
1
$ php bin/console secrets:generate-keys
This will generate a pair of asymmetric cryptographic keys. Each
environment has its own set of keys. Assuming
you're coding locally in the dev
environment, this will create:
config/secrets/dev/dev.encrypt.public.php
- Used to encrypt/add secrets to the vault. Can be safely committed.
config/secrets/dev/dev.decrypt.private.php
-
Used to decrypt/read secrets from the vault. The
dev
decryption key can be committed (assuming no highly-sensitive secrets are stored in the dev vault) but theprod
decryption key should never be committed.
You can generate a pair of cryptographic keys for the prod
environment by
running:
1
$ APP_RUNTIME_ENV=prod php bin/console secrets:generate-keys
This will generate config/secrets/prod/prod.encrypt.public.php
and
config/secrets/prod/prod.decrypt.private.php
.
Danger
The prod.decrypt.private.php
file is highly sensitive. Your team of developers
and even Continuous Integration services don't need that key. If the
decryption key has been exposed (ex-employee leaving for instance), you
should consider generating a new one by running:
secrets:generate-keys --rotate
.
Create or Update Secrets
Suppose you want to store your database password as a secret. By using the
secrets:set
command, you should add this secret to both the dev
and
prod
vaults:
1 2 3 4 5 6 7
# the input is hidden as you type for security reasons
# set your default development value (can be overridden locally)
$ php bin/console secrets:set DATABASE_PASSWORD
# set your production value
$ APP_RUNTIME_ENV=prod php bin/console secrets:set DATABASE_PASSWORD
This will create a new file for the secret in config/secrets/dev
and another
in config/secrets/prod
. You can also set the secret in a few other ways:
1 2 3 4 5 6 7 8
# provide a file where to read the secret from
$ php bin/console secrets:set DATABASE_PASSWORD ~/Download/password.json
# or contents passed to STDIN
$ echo -n "$DB_PASS" | php bin/console secrets:set DATABASE_PASSWORD -
# or let Symfony generate a random value for you
$ php bin/console secrets:set REMEMBER_ME --random
Note
There's no command to rename secrets, so you'll need to create a new secret and remove the old one.
Referencing Secrets in Configuration Files
Secret values can be referenced in the same way as environment variables. Be careful that you don't accidentally define a secret and an environment variable with the same name: environment variables override secrets.
If you stored a DATABASE_PASSWORD
secret, you can reference it by:
1 2 3 4 5 6
# config/packages/doctrine.yaml
doctrine:
dbal:
password: '%env(DATABASE_PASSWORD)%'
# ...
# ...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
<!-- config/packages/doctrine.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:doctrine="http://symfony.com/schema/dic/doctrine"
xsi:schemaLocation="http://symfony.com/schema/dic/services
https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/doctrine
https://symfony.com/schema/dic/doctrine/doctrine-1.0.xsd">
<doctrine:config>
<doctrine:dbal
password="%env(DATABASE_PASSWORD)%"
/>
</doctrine:config>
</container>
1 2 3 4 5 6 7 8 9
// config/packages/doctrine.php
use Symfony\Config\DoctrineConfig;
return static function (DoctrineConfig $doctrine): void {
$doctrine->dbal()
->connection('default')
->password(env('DATABASE_PASSWORD'))
;
};
The actual value will be resolved at runtime: container compilation and cache warmup don't need the decryption key.
List Existing Secrets
Everybody is allowed to list the secrets names with the command
secrets:list
. If you have the decryption key you can also reveal the
secrets' values by passing the --reveal
option:
1 2 3 4 5 6 7
$ php bin/console secrets:list --reveal
------------------- ------------ -------------
Name Value Local Value
------------------- ------------ -------------
DATABASE_PASSWORD "my secret"
------------------- ------------ -------------
Reveal Existing Secrets
If you have the decryption key, the secrets:reveal
command allows
you to reveal a single secret's value.
1 2 3
$ php bin/console secrets:reveal DATABASE_PASSWORD
my secret
7.1
The secrets:reveal
command was introduced in Symfony 7.1.
Remove Secrets
Symfony provides a convenient command to remove a Secret:
1
$ php bin/console secrets:remove DATABASE_PASSWORD
Local secrets: Overriding Secrets Locally
The dev
environment secrets should contain nice default values for development.
But sometimes a developer still needs to override a secret value locally when
developing.
Most of the secrets
commands - including secrets:set
- have a --local
option that stores the "secret" in the .env.{env}.local
file as a standard
environment variable. To override the DATABASE_PASSWORD
secret locally, run:
1
$ php bin/console secrets:set DATABASE_PASSWORD --local
If you entered root
, you will now see this in your .env.dev.local
file:
1
DATABASE_PASSWORD=root
This will override the DATABASE_PASSWORD
secret because environment variables
always take precedence over secrets.
Listing the secrets will now also display the local variable:
1 2 3 4 5 6
$ php bin/console secrets:list --reveal
------------------- ------------- -------------
Name Value Local Value
------------------- ------------- -------------
DATABASE_PASSWORD "dev value" "root"
------------------- ------------- -------------
Symfony also provides the secrets:decrypt-to-local
command which decrypts
all secrets and stores them in the local vault and the secrets:encrypt-from-local
command to encrypt all local secrets to the vault.
Secrets in the test Environment
If you add a secret in the dev
and prod
environments, it will be missing
from the test
environment. You could create a "vault" for the test
environment and define the secrets there. But an easier way is to set the test
values via the .env.test
file:
1 2
# .env.test
DATABASE_PASSWORD="testing"
Deploy Secrets to Production
Due to the fact that decryption keys should never be committed, you will need to manually store this file somewhere and deploy it. There are 2 ways to do that:
Uploading the file
The first option is to copy the production decryption key -
config/secrets/prod/prod.decrypt.private.php
to your server.Using an Environment Variable
The second way is to set the
SYMFONY_DECRYPTION_SECRET
environment variable to the base64 encoded value of the production decryption key. A fancy way to fetch the value of the key is:1 2 3
# this command only gets the value of the key; you must also set an env var # in your system with this value (e.g. `export SYMFONY_DECRYPTION_SECRET=...`) $ php -r 'echo base64_encode(require "config/secrets/prod/prod.decrypt.private.php");'
To improve performance (i.e. avoid decrypting secrets at runtime), you can decrypt your secrets during deployment to the "local" vault:
1
$ APP_RUNTIME_ENV=prod php bin/console secrets:decrypt-to-local --force
This will write all the decrypted secrets into the
.env.prod.local
file. After doing this, the decryption key does not need to remain on the server(s).
Rotating Secrets
The secrets:generate-keys
command provides a --rotate
option to
regenerate the cryptographic keys. Symfony will decrypt existing secrets with
the old key, generate new cryptographic keys and re-encrypt secrets with the
new key. In order to decrypt previous secrets, the developer must have the
decryption key.
Configuration
The secrets system is enabled by default and some of its behavior can be configured:
1 2 3 4 5 6
# config/packages/framework.yaml
framework:
secrets:
#vault_directory: '%kernel.project_dir%/config/secrets/%kernel.environment%'
#local_dotenv_file: '%kernel.project_dir%/.env.%kernel.environment%.local'
#decryption_env_var: 'base64:default::SYMFONY_DECRYPTION_SECRET'
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<!-- config/packages/framework.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:framework="http://symfony.com/schema/dic/framework"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/framework https://symfony.com/schema/dic/framework/framework-1.0.xsd"
>
<framework:config secret="%env(APP_SECRET)%">
<framework:secrets
vault_directory="%kernel.project_dir%/config/secrets/%kernel.environment%"
local_dotenv_file="%kernel.project_dir%/.env.%kernel.environment%.local"
decryption_env_var="base64:default::SYMFONY_DECRYPTION_SECRET"
/>
</framework:config>
</container>
1 2 3 4 5 6 7 8 9 10
// config/packages/framework.php
use Symfony\Config\FrameworkConfig;
return static function (FrameworkConfig $framework): void {
$framework->secrets()
// ->vaultDirectory('%kernel.project_dir%/config/secrets/%kernel.environment%')
// ->localDotenvFile('%kernel.project_dir%/.env.%kernel.environment%.local')
// ->decryptionEnvVar('base64:default::SYMFONY_DECRYPTION_SECRET')
;
};