GitHub: https://github.com/faustobranco/devops-db/tree/master/knowledge-base/ansible/secrets

In this post, I want to show how to use Ansible Vault to encrypt sensitive content in playbooks. For example, username, password, some key, IP, etc.

Of course, in more complex environments, managing this content becomes a little complicated, even if versioned in some Git. In these cases, the adoption of a dedicated Vault, such as the HashiCorp Vault, must be taken into consideration.
But even so, on certain occasions, we won’t have to escape having some content in our playbooks that need to be encrypted.

Before we begin, I want to make it clear that there are several ways to protect this content, even with Ansible Vault. One example is encrypting an entire file (Ansible – File Encryption in Ansible Vault), which is excellent, for example for certificates or key files. But in this example, I will only explain how to encrypt variable contents.

The password for encryption and decryption can also be in several ways, being requested at each execution from the operator/user running the playbook (which I think is unfeasible), key within a protected file (I will explain and apply this further below) or even inside another Vault.

Here is a brief explanation of how string encryption works, the ansible-vault command receives as parameters a password, which will be used as a key in encryption/decryption (it will be requested at run time), the string that will be encrypted and a variable name, which is the name of the variable to which the encrypted content will be applied… it will make sense when I show it.

Syntax:

ansible-vault encrypt_string <password_source> '<string_to_encrypt>' --name '<string_name_of_variable>'

So in the example below, you will encrypt any password/string ‘1234qwer‘ which would be the value of a variable ‘pass_database‘. In Ansible, something like this:

pass_database: 1234qwer

On the Ansible host, run the command below and when asked for the password, enter one of your choice, this will also be your key to decrypt.

ansible-vault encrypt_string '1234qwer' --name 'pass_database'

Return:

New Vault password:
Confirm New Vault password:
Encryption successful
pass_database: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          38316233346432383532633265303934373339313266356338333934383461306330613066643165
          3832316137313832653464643134313736393563363832300a396436303038393339393866343732
          39623965623364623661313132303237626239653437306261346533326261313635346335613430
          3864393339303132310a303139633338383635303461646430326430643133393131333931376333
          3934

So the content above, from line 4 onwards, is your variable that must be placed in the playbook files.

Now, let’s decrypt:

echo '$ANSIBLE_VAULT;1.1;AES256
38316233346432383532633265303934373339313266356338333934383461306330613066643165
3832316137313832653464643134313736393563363832300a396436303038393339393866343732
39623965623364623661313132303237626239653437306261346533326261313635346335613430
3864393339303132310a303139633338383635303461646430326430643133393131333931376333
3934' | ansible-vault decrypt && echo

When Ansible asks, type the password again and the decrypted result will appear.

Vault password:
Decryption successful
1234qwer

Password file

Another way to encrypt and decrypt is to use a file with the password. I personally find it much more practical, and with some care in a protected environment, it remains safe.

For this lab, I created a Docker to serve as a specific Ansible server, as most of the scripts, in the case of this Lab, will be run by Jenkins and the Ansible sources in GitLab.
In this container, docker, I will then create the file with the password, simple, in plain text.

An interesting point is that this file can contain several passwords identified by a tag, which can be used according to your criteria, environment, systems, etc.
In this example, I will use two tags: dev and prod.

This file should be protected mode: ‘0644’, owner: root, group: root

/root/.vault_password.sec

dev:SnxvwDsh365DAnId1gfojBSKzfHc8x1vmvyNiaC1dkrlcE5NNe
prod:nBODrPRuespQUOmpEUHFGtynIjoBvjfrA1zl8YF7hVTU9fOIrr

If you want to do encryption tests with the file:

ansible-vault encrypt_string '1234qwer' --name 'pass_database' --vault-id prod@/root/.vault_password.sec
Encryption successful
pass_database: !vault |
          $ANSIBLE_VAULT;1.2;AES256;prod
          35386434336435653233316437306639653163653462363664626261346136656233346164613333
          3631656261323261356137643932393736303264313834360a323330373734383739383333366337
          36386561323761386630633065613637646236626164646339653137343334393465353939366266
          6362663465373762640a663738393430336339663064303635383065343763393437643533353833
          3761

Decrypt

echo '$ANSIBLE_VAULT;1.2;AES256;prod
35386434336435653233316437306639653163653462363664626261346136656233346164613333
3631656261323261356137643932393736303264313834360a323330373734383739383333366337
36386561323761386630633065613637646236626164646339653137343334393465353939366266
6362663465373762640a663738393430336339663064303635383065343763393437643533353833
3761' | ansible-vault decrypt --vault-id prod@/root/.vault_password.sec && echo
Decryption successful
1234qwer

Encrypt the variables.

The playbook that I am going to create is, as always, on my GitHub (https://github.com/faustobranco/devops-db/tree/master/knowledge-base/ansible/secrets), but the structure is as described below and basically it is a file for the playbook (secrets.yaml), the test_secret role with a task, a jinja template and a variables file and a global variables file, at the playbook level.

The host is the least important in this case, but it is an Ubuntu VM created in Vagrant, which I use only for testing.

|-- hosts
|-- roles
|   `-- test_secret
|       |-- tasks
|       |   `-- main.yaml
|       |-- templates
|       |   `-- secrets
|       |       `-- secrets.j2
|       `-- vars
|           `-- main.yaml
|-- secrets.yaml
`-- vars
    `-- global_vars.yaml

For the global variables file (global_vars.yaml) we will encrypt the contents:

Original values:

ansible_ssh_user=usr_linuxadmin
ansible_ssh_pass=sg8BnHlWpkoQlLwmAZPePZ9JnLgwDffjIKTcLwP1NXCN5L2tms
ansible_sudo_pass=sg8BnHlWpkoQlLwmAZPePZ9JnLgwDffjIKTcLwP1NXCN5L2tms

global_database
  username=usr_database_admin
  password=IgzuZ2MSOsdtOT9JMovkrQATACla3ro6xtL2BHcvNOhr9BohJI

Encrypt:

ansible-vault encrypt_string 'usr_linuxadmin' --name 'ansible_ssh_user' --vault-id prod@/root/.vault_password.sec
ansible-vault encrypt_string 'sg8BnHlWpkoQlLwmAZPePZ9JnLgwDffjIKTcLwP1NXCN5L2tms' --name 'ansible_ssh_pass' --vault-id prod@/root/.vault_password.sec
ansible-vault encrypt_string 'sg8BnHlWpkoQlLwmAZPePZ9JnLgwDffjIKTcLwP1NXCN5L2tms' --name 'ansible_sudo_pass' --vault-id prod@/root/.vault_password.sec

ansible-vault encrypt_string 'usr_database_admin' --name 'username' --vault-id prod@/root/.vault_password.sec
ansible-vault encrypt_string 'IgzuZ2MSOsdtOT9JMovkrQATACla3ro6xtL2BHcvNOhr9BohJI' --name 'password' --vault-id prod@/root/.vault_password.sec

And the result of the above commands is added to the file, remember that with each execution, the result is always different, so don’t worry about it being the same as the result below, but it must be in the same format.

vars/global_vars.yaml

ansible_ssh_user: !vault |
          $ANSIBLE_VAULT;1.2;AES256;prod
          62303766613665316364363963333266623835626534366635373163336233666464336235376439
          6537633738356535643337393962306137303739643563350a353838363231323763333038356331
          31336535363161623564383933363734343436326438393837646336373364323234613263383333
          6137303031623731650a323034333537646262393935663863353233623835646335333536383866
          6664

ansible_ssh_pass: !vault |
          $ANSIBLE_VAULT;1.2;AES256;prod
          31646463303962666233653431343733623732633735356335376163346433623431653133373062
          6432366262336533306135313838386635336162396661610a636665313838326236323962346335
          64643831383934643566343064636130313063616332393435346632646437393734313961356139
          3865303332653432390a343835393765383733393162333962313965356538393965326633663664
          62613366393139343234646231653265383263356564623165316631623663396666636630366431
          66633736333262613365656532356433626661643735313236656531376266653464656564653361
          623636663265613239376366356162656561

ansible_sudo_pass: !vault |
          $ANSIBLE_VAULT;1.2;AES256;prod
          35303437313531373733316562666234623264623533323962323034373330666430626330626339
          3737643033323637633661626664363764343539306531650a393532643633383364616362373064
          30383235653131366366363933373863313738366531373562663763396331333161313532396363
          6133333364636139650a333830333230383763646139393130366137316433646663306465323766
          38623134616361366139663134313465303561613039626539613962623062313236663036616635
          30363938346234303232356366396435626531313439663062653835613962396365393537633733
          393231363230626339333239633837336137

global_database:
  username: !vault |
          $ANSIBLE_VAULT;1.2;AES256;prod
          61323730333638656364383263383665343461303533663363623038363637623064383362643630
          3536323831306235326365663939653264313831623565300a383362313465373633393464613635
          39616463623033336531663633313365386261653533313335613839373962396466303234633162
          3030343032626639630a343133656437613136383537653636643432343537666333383665316264
          32653363333637393362663066333539656361366638353966363237623166393161
  password: !vault |
          $ANSIBLE_VAULT;1.2;AES256;prod
          35633535333664633835333138333733646533373736653831656631366366366563326162353864
          3330306232363639376366616439383164396433393666660a353932313431383834313464386362
          65373133343261326434373764363131626234303533386363663936363066363564343632646236
          3431386163383738610a306564303331316433366164376337646636353238623031316464343935
          38326431376466653537366531343633313962626137303333666433353932343731373764373562
          31383563376662353766346163303337353132343030336662623166666535313064643761613730
          353731633534623066353437323563366633

Next, we will encrypt the contents of the variables in the role variables file in the same way.

Original values:

devopsdb_share:
  username=devopsdbshare
  password=6abtaPLIEgx1pp7V7tX7rLzsw7UkdMvEUHa3vZOsLMnAK21Kg1

Encrypt:

ansible-vault encrypt_string 'devopsdbshare' --name 'username' --vault-id prod@/root/.vault_password.sec
ansible-vault encrypt_string '6abtaPLIEgx1pp7V7tX7rLzsw7UkdMvEUHa3vZOsLMnAK21Kg1' --name 'password' --vault-id prod@/root/.vault_password.sec

roles/test_secret/vars/main.yaml

Yes, there are unencrypted variables in the middle, to demonstrate that there are no problems.

ansible_ssh_common_args: '-o StrictHostKeyChecking=no'

devopsdb_share:
  username: !vault |
            $ANSIBLE_VAULT;1.2;AES256;prod
            32636134313633366232306431653736396461313432326564386238346561363831666462356631
            3235646134623661356439623865373730373138636535350a616338363962636465656333376666
            61623131666265336362323739326237343835303263336431643561353235306664336134383165
            3438343136363263630a346239353862616435356531303232323666313238626236376466323134
            3139
  password: !vault |
          $ANSIBLE_VAULT;1.2;AES256;prod
          36303966393831653130316234336634333966636264306132326333363462666565366330336664
          6463376232343730626339303364333161653334666437340a303665386538613465613163643534
          36386535343738363531643337636231363566396138663433376337306134646366346661613337
          3731306533333135660a653262333163653033623335633039313333663431336462653961316264
          36646436363963346261633836656530336330623266316435376263303331356430633933363062
          38663761333739323732663039373365323632343065346462653762653134636661366563376662
          653730373233646130663930613630613831

And now, finally, a templates file in Jinja2, which will be created in real-time and copied to the host. with the And now, finally, a templates file in Jinja2, which will be created in real-time and copied to the host. already with the decrypted information created above.

roles/test_secret/templates/secrets/secrets.j2

username={{ devopsdb_share.username }}
password={{ devopsdb_share.password }}

Playbook

The playbook is very simple, just to show the values ​​in the terminal, to demonstrate that the values ​​were successfully decrypted, and finally, a cat in the file created on the host.

(playbook) secrets.yaml

- hosts: "tests_Hosts"
  become: true
  roles:
   - test_secret
  vars_files:
    - vars/global_vars.yaml
  gather_facts: True

(role/task) roles/test_secret/tasks/main.yaml

- name: output the global playbook 'ansible_ssh_user' variable
  ansible.builtin.debug: 
    var: ansible_ssh_user

- name: output the global playbook 'ansible_ssh_pass' variable
  ansible.builtin.debug: 
    var: ansible_ssh_pass

- name: output the global playbook 'ansible_ssh_common_args' variable
  ansible.builtin.debug: 
    var: ansible_ssh_common_args

- name: output the global playbook 'global_database.username' variable
  ansible.builtin.debug: 
    var: global_database.username

- name: output the global playbook 'global_database.password' variable
  ansible.builtin.debug: 
    var: global_database.password

- name: Create secrets dir 
  file: 
    path: "/root/secrets"
    state: directory

- name: Add secrets mount with local variables - jinja
  ansible.builtin.template:
    dest: /root/secrets/secrets
    src: secrets/secrets.j2
    mode: '0644'
    owner: root
    group: root

Let’s run the playbook. To do this we have to pass, in addition to the standard parameters, also the file with the passwords.

But if the file has more than one password, which one will Ansible choose? Note that in variable encryptions, this information already has this information, the tags are in the instructions, example: $ANSIBLE_VAULT;1.2;AES256;prod

ansible-playbook -i /work/secrets/hosts /work/secrets/secrets.yaml --vault-id /root/.vault_password.sec

The return, as Ansible standard:

PLAY [tests_Hosts] ***********************************************************************************************************************************************************************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************************************************************************************************************************************************************
ok: [srv-infrastructure-test-master-01]

TASK [test_secret : output the global playbook 'ansible_ssh_user' variable] **************************************************************************************************************************************************************************************************************************************************
ok: [srv-infrastructure-test-master-01] => {
    "ansible_ssh_user": "usr_linuxadmin"
}

TASK [test_secret : output the global playbook 'ansible_ssh_pass' variable] **************************************************************************************************************************************************************************************************************************************************
ok: [srv-infrastructure-test-master-01] => {
    "ansible_ssh_pass": "sg8BnHlWpkoQlLwmAZPePZ9JnLgwDffjIKTcLwP1NXCN5L2tms"
}

TASK [test_secret : output the global playbook 'ansible_ssh_common_args' variable] *******************************************************************************************************************************************************************************************************************************************
ok: [srv-infrastructure-test-master-01] => {
    "ansible_ssh_common_args": "-o StrictHostKeyChecking=no"
}

TASK [test_secret : output the global playbook 'global_database.username' variable] ******************************************************************************************************************************************************************************************************************************************
ok: [srv-infrastructure-test-master-01] => {
    "global_database.username": "usr_database_admin"
}

TASK [test_secret : output the global playbook 'global_database.password' variable] ******************************************************************************************************************************************************************************************************************************************
ok: [srv-infrastructure-test-master-01] => {
    "global_database.password": "IgzuZ2MSOsdtOT9JMovkrQATACla3ro6xtL2BHcvNOhr9BohJI"
}

TASK [test_secret : Create secrets dir] **************************************************************************************************************************************************************************************************************************************************************************************
ok: [srv-infrastructure-test-master-01]

TASK [test_secret : Add secrets mount with local variables - jinja] **********************************************************************************************************************************************************************************************************************************************************
ok: [srv-infrastructure-test-master-01]

PLAY RECAP *******************************************************************************************************************************************************************************************************************************************************************************************************************
srv-infrastructure-test-master-01 : ok=8    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

There on the target host:

root@srv-infrastructure-test-master-01:/home/vagrant# cat /root/secrets/secrets
username=devopsdbshare
password=6abtaPLIEgx1pp7V7tX7rLzsw7UkdMvEUHa3vZOsLMnAK21Kg1