Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

parameters.yml: Handling Secret Config

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 $9.00

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

Login Subscribe

No matter how you deploy, eventually, you hit the same problem: handling sensitive configuration, like your production database password or Loggly token. Depending on your app, this info will need to be stored in different places, like parameters.yml for a Symfony 3 app or as environment variables for Symfony 4.

But no matter where the config needs to ultimately live, the problem is more or less the same: how can we put secret things onto the server in an automated way?

Options for parameters.yml

Like with everything, there are a few good answers. And this is where things can get complicated. For parameters.yml, one option is to store the production parameters.yml in a private S3 bucket. Then, during deployment, use the s3 Ansible module to download that into your project.

Another option - the way that we will do it - is to store a parameters.yml.dist file in our project, and make it dynamic by printing Ansible variables inside it. To keep things secure, those variables will be stored in the Ansible vault.

Setting up the Deploy Vault

Let's create a new vault to store the secret values:

ansible-vault create ansible/vars/deploy_vault.yml

Choose a safe password... something safer than what I'll choose: beefpass. Here's the plan: we will define some new variables here, then use them inside parameters.yml.dist. So, what needs to be dynamic? For now the secret, loggly_token, database_host, database_user and database_pass.

Back in the vault, create some variables: vault_symfony_secret set to udderly secret $tring and vault_loggly_token set to our production loggly token... this long string:

# ansible/vars/deploy_vault.yml
---
vault_symfony_secret: 'udderly secret $string'
vault_loggly_token: 'fb4aa5b2-30a3-4bd8-8902-1aba5a683d62'

Oh, and, get your own token... because this is fake.

Then, vault_database_host: 127.0.0.1, vault_database_user: root, and vault_database_password set to null:

# ansible/vars/deploy_vault.yml
---
vault_symfony_secret: 'udderly secret $string'
vault_loggly_token: 'fb4aa5b2-30a3-4bd8-8902-1aba5a683d62'
vault_database_host: 127.0.0.1
vault_database_user: root
vault_database_password: null

In the provision playbook, we actually install a MySQL server locally. That's why I'm using the local database server... and no, I haven't bothered to create a proper user with a decent password. But you should.

But also, if I were using AWS for a real application, I would use Amazon's RDS - basically, a hosted MySQL or PostgreSQL database - so that I don't need to manage it on my own. In that case, the database host would be something specific to my RDS instance. But, it's the same idea.

Tip

If you decide to use Amazon's RDS - basically, you need to perform the next steps:

  1. Create an RDS instance. The easiest way to do this is by hand via the AWS web interface;
  2. Put its credentials in the Ansible Vault.

You can automate the creation of the RDS instance, though it’s not as important because these instances are not destroyed and recreated in the same way as EC2 instances.

Save this file and quit. We now have a new, but encrypted, file with those variables:

$ANSIBLE_VAULT;1.1;AES256
37633866356562303665643432386636313937613063373666613739313732313130633364333138
3635376437663463326538633961663931393136643962610a636635363630656264343630613731
30323432303666373366366237393637333932373962373730626465663330326462396532363330
3561346639616234610a353530366337616164633864363038336437623165623534663166633966
31656536326665613063343537333564656334616638663866653934373566303232323036653566
63366436346139333966316364656265633462303233623162353630626661353930343939396661
36326338383538363138633836636235663734303464396236396132306430636362393232366264
34653762303761356262316234333631386262396666313037643661653336613133396439313362
61326464323131646132393538333236643664356539636464373937666530366334313335663637
62383230653762396662653265613133343137336264353233303932313330663831316565636134
35646135303966333639616361306236393134656263386666643532353534313662376232653566
64393665343230353362643363663031636334626466656130616562363038396335323530613135
36313232323766663564666638646562366437616233376632323164303635356631346361376633
6231386263646661383539313439316663333231643261653834

Inside deploy.yml, under vars_files, add ./vars/deploy_vault.yml:

---
- hosts: aws
vars_files:
- ./vars/deploy_vault.yml
- ./vars/vars.yml
... lines 7 - 30

Creating a Simpler Variables File

At this point, we could go directly into parameters.yml.dist and start using those vault_ variables. But, as a best practice, I like to create a separate vars file - deploy_vars.yml - where I assign each of those vault_ variables to a normal variable. Just, stay with me.

Re-open the vault file - type beefpass:

ansible-vault edit ansible/vars/deploy_vault.yml

And copy everything. Then, in deploy_vars.yml, paste that. Now, for each variable, create a new variable that's set to it, but without the vault_ prefix:

---
symfony_secret: "{{ vault_symfony_secret }}"
loggly_token: "{{ vault_loggly_token }}"
database_host: "{{ vault_database_host }}"
database_user: "{{ vault_database_user }}"
database_password: "{{ vault_database_password }}"

This is totally optional. The advantage is that you can quickly see a list of all available variables, without needing to open the vault. I can just look in here and say:

Oh! Apparently there is a variable called symfony_secret!

Back in deploy.yml, import this new file: ./vars/deploy_vars.yml:

---
- hosts: aws
vars_files:
- ./vars/deploy_vault.yml
- ./vars/vars.yml
- ./vars/deploy_vars.yml
... lines 8 - 30

Variables inside parameters.yml.dist

Finally, in parameters.yml.dist, let's print some variables! For database_host, print {{ database_host }}. Repeat that for database_user, database_password, and then down below for symfony_secret, and loggly_token:

# This file is a "template" of what your parameters.yml file should look like
# Set parameters here that may be different on each deployment target of the app, e.g. development, staging, production.
# http://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration
parameters:
database_host: "{{ database_host }}"
database_port: ~
database_name: mootube
database_user: "{{ database_user }}"
database_password: "{{ database_password }}"
# You should uncomment this if you want use pdo_sqlite
#database_path: '%kernel.project_dir%/var/data/data.sqlite'
mailer_transport: smtp
mailer_host: 127.0.0.1
mailer_user: ~
mailer_password: ~
# A secret key that's used to generate certain security-related tokens
secret: "{{ symfony_secret }}"
redis_host: localhost
loggly_token: "{{ loggly_token }}"

That's it! We put secret things in the vault and then print them inside the parameters file.

Let's try it. Run the playbook with the same command:

ansible-playbook -i ansible/hosts.ini ansible/deploy.yml

Yep! This fails because it can't decrypt the vault file. From now on, we need to add a --ask-vault-pass flag. And then type, beefpass:

ansible-playbook -i ansible/hosts.ini ansible/deploy.yml --ask-vault-pass

If this gets really annoying, you can store the password in a file and use --vault-password-file to point to it. Just don't commit that file to your repository!

And... done! Let's go check it out! Move out of the current/ directory and then back in:

cd ..
cd current

Deep breath: open parameters.yml. Yes! Everything has its dynamic vault value!

Ok, we're getting really close! Next, let's run Composer and fix some permissions!

Leave a comment!

0
Login or Register to join the conversation
Cat in space

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

While the fundamentals of Ansistrano haven't changed, this tutorial is built using Symfony 3, which has significant differences versus Symfony 4 and later.

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.5.9",
        "doctrine/doctrine-bundle": "^1.6", // 1.6.8
        "doctrine/orm": "^2.5", // v2.7.2
        "incenteev/composer-parameter-handler": "^2.0", // v2.1.2
        "sensio/distribution-bundle": "^5.0.19", // v5.0.20
        "sensio/framework-extra-bundle": "^3.0.2", // v3.0.26
        "symfony/monolog-bundle": "^3.1.0", // v3.1.0
        "symfony/polyfill-apcu": "^1.0", // v1.4.0
        "symfony/swiftmailer-bundle": "^2.3.10", // v2.6.3
        "symfony/symfony": "3.3.*", // v3.3.5
        "twig/twig": "^1.0||^2.0", // v1.34.4
        "doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.1
        "predis/predis": "^1.1", // v1.1.1
        "composer/package-versions-deprecated": "^1.11" // 1.11.99
    },
    "require-dev": {
        "sensio/generator-bundle": "^3.0", // v3.1.6
        "symfony/phpunit-bridge": "^3.0", // v3.3.5
        "doctrine/data-fixtures": "^1.1", // 1.3.3
        "hautelook/alice-bundle": "^1.3" // v1.4.1
    }
}

What Ansible libraries does this tutorial use?

# ansible/requirements.yml
-
    src: DavidWittman.redis
    version: 1.2.4
-
    src: ansistrano.deploy
    version: 2.7.0
-
    src: ansistrano.rollback
    version: 2.0.1
userVoice