If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeNow that we know that environment variables override secrets, we can use that to our advantage in two ways.
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.
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"
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.
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
:
... 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.
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!
// 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
}
}
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`?