Symfony Secrets Management

Are you tired of storing a file with your application secrets in a password manager and need to copy it to your CI/CD environment everytime you change it to deploy your application in a security compliant way?

Spin up Symfony

Create a docker-compose.yml in your project root and add the following:

(See PHP Docker development with XDEBUG explained here)

version: '3'
services:
php:
image: webdevops/php-nginx-dev:7.4
working_dir: /app
environment:
- WEB_DOCUMENT_ROOT=/app/public
- PHP_DISPLAY_ERRORS=1
- PHP_MEMORY_LIMIT=2048M
- PHP_MAX_EXECUTION_TIME=-1
- XDEBUG_REMOTE_AUTOSTART=1
- XDEBUG_REMOTE_PORT=9000
- XDEBUG_PROFILER_ENABLE=0
- XDEBUG_REMOTE_CONNECT_BACK=0
- XDEBUG_REMOTE_HOST=docker.for.mac.localhost
- php.xdebug.idekey=PHPSTORM
- php.xdebug.remote_enable=1
- php.xdebug.max_nesting_level=1000
ports:
- "8080:80"
volumes:
- ./:/app:rw,cached
depends_on:
- mysql

mysql:
image: mysql:5.7
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test
MYSQL_USER: test
MYSQL_PASSWORD: test

Spin up the service with docker-compose up and install Symfony 5 to get the app running:

docker-compose exec php bash -c 'composer create-project symfony/website-skeleton project && mv project/* . && rm -rf project'

Now you can browse http://localhost:8080 and should see this:

Symfony default screen

Store secrets in a vault

Since Symfony 4.4 you get a secrets management out of the box that helps to store all secrets encrypted in a vault. You can decrypt your secrets with a private key or a passphrase. We can securely save our DB connection string, which currently lives in the .env file with this command:

php bin/console secrets:set DATABASE_URL

As we can see the command created a vault, a key pair and tells you not to commit the decryption key. We can check the files that were generated in config/secrets/dev:

We see our key, a private decryption key and a public encryption key, that can be committed. There is also a listing php file that contains the following content to list all encrypted keys:

This is used for php bin/console secrets:list and shows you the following:

As you might noticed Symfony stores the secrets divided on the environment. So to not block other developers in your team just commit the dev files and your good to go. For production vault files you shouldn’t commit the private decryption key. Adding the dev secrets for now works like this:

git add config/secrets

When we try to run a database query it should just work, because Symfony fetches %env% params if they are not exposed as environment variables from the vault. In the doctrine package configuration file you see the following:

config/packages/doctrine.yaml

We can test if the connection works:

php bin/console doctrine:query:sql "SHOW VARIABLES LIKE 'max_join_size'"

When you get no error and a 0 printed, everything is fine and the connection worked.

Production Vault

To save the DATABASE_URL param in the production vault we run this command:

php bin/console secrets:set --env=prod DATABASE_URL

This generates a production vault with all the necessary keys and sets the DATABASE_URL with your provided value. We need to be aware to not add the private decryption key to our versioning. That was already handled by installing Symfony with composer, so we see the following line already in the .gitignore file:

/config/secrets/prod/prod.decrypt.private.php

Symfony is so smart!

Reveal the secret values

To list all values we added to the vault we know to use the secrets:list command. To see the secrets cleartext value you can pass an option called — reveal:

For production values use:

php bin/console secrets:list --env=prod --reveal

Now we have version controlled secrets for multiple environments. That’s pretty awesome!

Application deployment

We have only one sensitive value and that’s the private decryption key. So during the deployment we need to add the private decryption file to the project or deploy with an environment variable called SYMFONY_DECRYPTION_SECRET.

When we want to use the SYMFONY_DECRYPTION_SECRET variable we need to provide the private decryption key value encoded with base64 e.g.:

php -r "echo base64_encode(require 'config/secrets/prod/prod.decrypt.private.php');"

When you’re using a build system like Jenkins or Gitlab you can inject this as a environment variable to your build and hand it over to your dockerized build or you add the whole file to the build files and deploy them. Jenkins offers the secret file feature for this:

Jenkins secret file feature

But wait there’s another method to even don’t add the private decryption key to your code files that get deployed. You can use this command on your build system to generate a .env file that you can deploy with your application files:

php bin/console secrets:decrypt-to-local --force --env=prod

This command generates a .env.prod.local file. So your Symfony app will use the env variables from this file and there will be no performance loss because of decrypting secrets at runtime. Amazing job and it makes no difference regarding security. If someone has access to your server he can copy the private decryption key or just copy the .env.prod.local file.

Local vault for overrides during development

How can I override secrets during development? Here is the local vault that we can use to override the DATABASE_URL to check something on another DB. Add a local value like this:

php bin/console secrets:set DATABASE_URL --local

But what happend now? The local credential overrides are stored in .env.dev.local! So no additional vault and of course Symfony already added a line in the .gitignore file to prevent you from committing it. Also .env file entries are overriden so you are safe to go with this way. The only important thing you see is that Symfony looks first for env variables and after that for secrets. So with env variables you can always override secret values.

Summary

  • Env variables first, secrets second
  • secret:set command solves dev, local and prod env handling automatically
  • Symfony handles possible git issues with .gitignore entries

Software Architect

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store