Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Prod Vault Optimization & Vault for Tests

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Now that we know that environment variables override secrets, we can use that to our advantage in two ways.

Dumping Secrets on Deploy: secrets:decrypt-to-local

The first thing is that, during deployment, we can dump our production secrets into a local file. Check it out. Run:

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

And... no output. Lame! SO lame that, in Symfony 5.1, this command will have output - that pull request was already merged.

Anyways, this just created a new .env.prod.local file... which contains all our prod secrets... which is just one right now. This means that, when we're in the prod environment, it will read from this file and will never read secrets from the vault.

Why... is that interesting? Um, good question. Two reasons. First, while deploying, you can add the decrypt key file, run this command, then delete the key file. The private key file then does not need to live on your production server at all. That's one less thing that could be exposed if someone got access to your servers.

And second, this will give you a minor performance boost because the secrets don't need to be decrypted at runtime.

Now, you might be thinking:

Ryan! You crazy man, you've got the brains of a watering can! We went to all this trouble to encrypt our secrets, and now you want me to store them in plain text on production? Are you mad?

I never get mad! The truth is, your sensitive values are never fully safe on production: there is always some way - called an "attack vector" - to get them. If someone gets access to the files on your server, then they would already have your encrypted values and the private key to decrypt them. Storing the secrets in plain text but removing the decrypt key from production is really the same thing from a security standpoint.

The point is: there's no security difference. Let's delete the .env.prod.local file, because we don't need it right now.

Secrets for the test Environment

The other interesting thing that we can do now that we understand that environment variables override secrets is related to the test environment. Because... our test environment is totally broken.

Think about it: in the test environment, there is no vault! And so there is no MAILER_DSN secret. Do we also need a test vault? Nah. There's a simpler solution.

First, let's run our tests to see what's going on:

php bin/phpunit

Ignore the deprecation warnings. Woh! Huge error. If you look closely... yep:

Environment variable not found: "MAILER_DSN"

New in 4.4: Easier HTML Errors in Tests

By the way, trying to find the error message inside the HTML in a test... sucks. But it's easier in Symfony 4.4 because Symfony dumps the error as a comment on the top of the HTML. It also.... yep! Puts that same comment at the bottom. So actually... I didn't need to scroll so far up.

Secrets in the Test Environment

So we do need to specify a MAILER_DSN secret to use in the test environment. But for simplicity, instead of making another vault, let's just add it to .env.test. I'll copy my old null transport value from .env, and put it into .env.test:

8 lines .env.test
... lines 1 - 6
MAILER_DSN=null://null

Done! So really, when you need to add a new secret, you need to add it to your dev vault, prod vault and .env.test.

Let's try the tests again:

php bin/phpunit

Much better! So... that's it for the secrets system! Pretty freakin' cool! Let's clean up our debugging code... nobody likes data being dumped on production. I'll remove the bind:

... lines 1 - 11
services:
# default configuration for services in *this* file
_defaults:
... lines 15 - 18
bind:
... lines 20 - 24
$mailerDsn: '%env(MAILER_DSN)%'
... lines 26 - 51

then go to ArticleController and take out the $mailerDsn stuff there:

... lines 1 - 13
class ArticleController extends AbstractController
{
... lines 16 - 28
public function homepage(ArticleRepository $repository, $mailerDsn)
{
dump($mailerDsn);die;
... lines 32 - 36
}
... lines 38 - 64
}

Next, let's talk about a really cool new feature called "validation auto mapping". It's a wicked-smart feature that automatically adds validation constraints based on your Doctrine metadata and also based on the way that your PHP code is written in your class.

Leave a comment!

2
Login or Register to join the conversation
Kevin B. Avatar
Kevin B. Avatar Kevin B. | posted 3 years ago

How does `composer dump-env prod` ( https://symfony.com/blog/ne... ) work with `bin/console secrets:decrypt-to-local --force --env=prod`? When deploying, should I run `bin/console secrets:decrypt-to-local --force --env=prod` then `composer dump-env prod`?

Reply

Yo Kevin B.!

Oh man, nice question. I shouldn't be surprised ;). Honestly, this hadn't occurred to me. BUT, it probably occurred to Nicolas Grekas, because it appears to work correctly in this order:


# creates .env.prod.local
bin/console secrets:decrypt-to-local --force --env=prod

# creates .env.local.php which will take "into account" the new values in .env.prod.local
composer dump-env prod

I just tested this - and the final .env.prod.local will contain the plain-text secrets as expected. If you do it in the "wrong" order, .env.local.php simply won't contain the secret... and since the other .env files aren't loaded when .env.local.php is present, the env var won't exist and it will try to load your secrets from the "vault"... which will only work if the decrypt key is present.

So, it looks like all works as expected. But if you see anything different, let me know!

Cheers!

1 Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.3.0",
        "ext-iconv": "*",
        "antishov/doctrine-extensions-bundle": "^1.4", // v1.4.2
        "aws/aws-sdk-php": "^3.87", // 3.110.11
        "composer/package-versions-deprecated": "^1.11", // 1.11.99
        "doctrine/doctrine-bundle": "^2.0", // 2.0.6
        "doctrine/doctrine-migrations-bundle": "^1.3|^2.0", // 2.1.2
        "doctrine/orm": "^2.5.11", // v2.7.2
        "doctrine/persistence": "^1.3.7", // 1.3.8
        "easycorp/easy-log-handler": "^1.0", // v1.0.9
        "http-interop/http-factory-guzzle": "^1.0", // 1.0.0
        "knplabs/knp-markdown-bundle": "^1.7", // 1.8.1
        "knplabs/knp-paginator-bundle": "^5.0", // v5.0.0
        "knplabs/knp-snappy-bundle": "^1.6", // v1.7.0
        "knplabs/knp-time-bundle": "^1.8", // v1.11.0
        "league/flysystem-aws-s3-v3": "^1.0", // 1.0.23
        "league/flysystem-cached-adapter": "^1.0", // 1.0.9
        "league/html-to-markdown": "^4.8", // 4.8.2
        "liip/imagine-bundle": "^2.1", // 2.3.0
        "nexylan/slack-bundle": "^2.1", // v2.2.1
        "oneup/flysystem-bundle": "^3.0", // 3.3.0
        "php-http/guzzle6-adapter": "^2.0", // v2.0.1
        "sensio/framework-extra-bundle": "^5.1", // v5.5.3
        "symfony/asset": "5.0.*", // v5.0.2
        "symfony/console": "5.0.*", // v5.0.2
        "symfony/dotenv": "5.0.*", // v5.0.2
        "symfony/flex": "^1.0", // v1.17.6
        "symfony/form": "5.0.*", // v5.0.2
        "symfony/framework-bundle": "5.0.*", // v5.0.2
        "symfony/mailer": "5.0.*", // v5.0.2
        "symfony/messenger": "5.0.*", // v5.0.2
        "symfony/monolog-bundle": "^3.5", // v3.5.0
        "symfony/security-bundle": "5.0.*", // v5.0.2
        "symfony/sendgrid-mailer": "5.0.*", // v5.0.2
        "symfony/serializer-pack": "^1.0", // v1.0.2
        "symfony/twig-bundle": "5.0.*", // v5.0.2
        "symfony/twig-pack": "^1.0", // v1.0.0
        "symfony/validator": "5.0.*", // v5.0.2
        "symfony/webpack-encore-bundle": "^1.4", // v1.7.2
        "symfony/yaml": "5.0.*", // v5.0.2
        "twig/cssinliner-extra": "^2.12", // v2.12.0
        "twig/extensions": "^1.5", // v1.5.4
        "twig/inky-extra": "^2.12" // v2.12.0
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.0", // 3.3.0
        "fzaninotto/faker": "^1.7", // v1.8.0
        "symfony/browser-kit": "5.0.*", // v5.0.2
        "symfony/debug-bundle": "5.0.*", // v5.0.2
        "symfony/maker-bundle": "^1.0", // v1.14.3
        "symfony/phpunit-bridge": "5.0.*", // v5.0.2
        "symfony/profiler-pack": "^1.0", // v1.0.4
        "symfony/var-dumper": "5.0.*" // v5.0.2
    }
}
userVoice