Ansible - Vault

I have an Ansible playbook and want to set a password for my brand-new MariaDB instance. Do I need a secret management system like Hashicorp Vault, Bitwarden or something else like gopass?

Ansible - Vault
Photo by Pixabay

I have an Ansible playbook and want to set a password for my brand-new MariaDB instance. Do I need a secret management system like Hashicorp Vault (or even better OpenBao), Bitwarden/Vaultwarden or something else like gopass?

For larger setups, you should think about secret management software, for sure. For private use and small projects? Ansible Vault might be the option you are looking for. And one thing is for sure: Never, ever, even think about putting your passwords and secrets in plain-text in a repository. Really. Don't do it!

What is Ansible Vault?

First, what are the authors saying?

Ansible Vault encrypts variables and files so you can protect sensitive content such as passwords or keys rather than leaving it visible as plaintext in playbooks or roles. To use Ansible Vault you need one or more passwords to encrypt and decrypt content. If you store your vault passwords in a third-party tool such as a secret manager, you need a script to access them. 

Said that, I want to give a bit more context.

With ansible-vault, you are getting a convenient tool to encrypt your secrets. Secrets can be stored in a dedicated password file or in-line in your playbooks and inventories. In case you are seeking for a way to handle access tokens, passwords and other secret data, it might become useful. Furthermore, you will not need any central infrastructure, no additional learning, and you can use everything you already know about Ansible.

To achieve this, Ansible uses a symmetric AES256 encryption algorithm. For decryption, you don't need to use the ansible-vault command, too. Instead, most Ansible tooling supports vaults and can use them with just some simple parameters.

Getting Started

You decided to give ansible-vault a try? Awesome, let's start simple with some basic commands. At the end of the article, you will also find a Cheat Sheet. In case you haven't worked with Ansible and you are just getting started, I strongly recommend reading "Ansible - Getting Started" beforehand. I promise, it will not take longer than 10 minutes.

Ansible - blog.while-true-do.io
Ansible is IT automation, made simple.

Installation

If you have Ansible already installed, you should have the ansible-vault command, already. In case you don't, it's super simple. I am preferring a Python virtual env for things that might change fast or where versions matter in the project scope.

# Create the virtual env
$ python -m venv PATH/TO/YOUR/DESIRED/VENV/DIRECTORY

# Activate the virtual env
$ source ATH/TO/YOUR/DESIRED/VENV/DIRECTORY/bin/activate

# Install Ansible
$ pip install ansible

# Check if ansible-vault works
$ ansible-vault --version
ansible-vault [core 2.16.3]

If you prefer a different way to manage your Ansible installations, please check out the official installation guide.

The simplest project

Now, we need a super-simple, yet standardized project directory for Ansible. You can use all the upcoming commands without a project directory, but it makes more sense to see how Ansible vault files will be integrated in a small project.

The layout, I think will work for us, looks like:

├── inventory
│   ├── group_vars
│   │   └── all
│   │       ├── vars.yml
│   │       └── vault.yml
│   └── hosts.yml
└── playbook.yml

The content of each of the files is, as follows.

---
all:
  hosts:
    localhost:
      ansible_connection: "local"
...

inventory/hosts.yml

---
user: "myUser"
pass: "{{ vault_pass }}"
...

inventory/group_vars/all/vars.yml

---
vault_pass: "SuperSecretPassword"
...

inventory/group_vars/all/vault.yml

---
- name: "Test some things with vaults"
  hosts: "localhost"

  tasks:
    - name: "Show user"
      ansible.builtin.debug:
        msg: "My user is {{ user }}"

    - name: "Show password"
      ansible.builtin.debug:
        msg: "My password is {{ pass }}"
...

playbook.yml

Just a quick explanation. It won't take long.

The playbook will output two variables "user" and "pass". To do so, we will inject the inventory, maintained in the hosts.yml file. The best-practice for inventory management recommends to manage group variables in the shown layout. Outputting the "user" should be easy. For better readability and maintainability, we are maintaining our variables in the vars.yml file and point to a vaulted variable. This variable is managed in the vault.yml file.

This will avoid confusions like "where is this variable coming from". And if you don't know, every .yml file in the inventory/group_vars/ will be parsed from Ansible. If it is encrypted with ansible-vault Ansible will detect the same, automatically.

Without any encryption (for now), we can test the output.

# Run the playbook
$ ansible-playbook -i inventory/hosts.yml playbook.yml 

PLAY [Test some things with vaults] ****************************************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************************************
ok: [localhost]

TASK [Show user] ***********************************************************************************************************************************************
ok: [localhost] => {
    "msg": "My user is myUser"
}

TASK [Show password] *******************************************************************************************************************************************
ok: [localhost] => {
    "msg": "My password is SuperSecretPassword"
}

PLAY RECAP *****************************************************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   


Since we don't want to commit this important password to our repository, I will demonstrate two ways how we can handle secrets with ansible-vault.

File encryption

The first way, which I am using mostly, is the file encryption. In our above example, we want to encrypt the vault.yml file. In the future, we can add more content to the file and be sure, that everything inside is encrypted and nice.

First, we want to encrypt an existing file:

# Encrypt existing vault.yml
$ ansible-vault encrypt inventory/group_vars/all/vault.yml
New Vault password: 
Confirm New Vault password: 
Encryption successful

Yep, that's already it. The file is encrypted. Now, how does the content look like?

# Check the content
$ cat inventory/group_vars/all/vault.yml
$ANSIBLE_VAULT;1.1;AES256
61326132396433643236663761633066333662623863343763346464313039633830303234346436
3730323338363365313738666331373666613930393237340a336535613831623137616163396139
61313530303065323635323438616234653864313463326363323363656466353938386632646238
3064326531643339380a333336663838613932373931393331366233353238636433656236646337
39356632313732643463656133326333353962393539346266643166303962373866366133393636
6330383132316264363562346535316262633539333737366630

Ok, but what if we want to view the real content?

# view a vaulted file
$ ansible-vault view inventory/group_vars/all/vault.yml 
Vault password: 
---
vault_pass: "SuperSecretPassword"
...

And, if we want to edit something?

# Edit a vaulted file
$ ansible-vault edit inventory/group_vars/all/vault.yml 
Vault password: 
⚠️
Please avoid using ansible-vault decrypt. This will decrypt your vault file in place, and you might accidentally push the unencrypted file to a repository.

Now, how do we use this file? Easy! Without providing a vault password, we will run into issues.

# Try without vault password
ansible-playbook -i inventory/hosts.yml playbook.yml 

PLAY [Test some things with vaults] ****************************************************************************************************************************
ERROR! Attempting to decrypt but no vault secrets found

If we provide a vault secret (in our case a password), it will work as before.

# Try again with vault password
 ansible-playbook -i inventory/hosts.yml --ask-vault-pass playbook.yml 
Vault password: 

PLAY [Test some things with vaults] ****************************************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************************************
ok: [localhost]

TASK [Show user] ***********************************************************************************************************************************************
ok: [localhost] => {
    "msg": "My user is myUser"
}

TASK [Show password] *******************************************************************************************************************************************
ok: [localhost] => {
    "msg": "My password is SuperSecretPassword"
}

PLAY RECAP *****************************************************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   


That was easy, right? If not, please let me know. 😸

Variable encryption

Well, well. We know how to encrypt a file. But what, if we want to encrypt a single variable? I mean, do we really need a vault file? To answer this right away, we don't.

Let's see how we can encrypt our "user" variable in-place and avoid spoofing our users to the internet.

# Create an encrypted variable
$ ansible-vault encrypt_string --ask-vault-pass myUser --name user
New Vault password: 
Confirm New Vault password: 
Encryption successful
user: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          38623361373165303063656536646130333837306534333132616234656136353435613339316564
          3533366466323531636530333336313233623634663164650a623631383633646465313333343665
          65303131616436323138643031663337383636653935346631333033623335373765343364323337
          3639396461663937650a333166666461343939353338393733616339333631656364646333323135
          6634

Just copy-paste the content to the vars.yml file and this one is done. The resulting file will look like this:

---
user: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  38623361373165303063656536646130333837306534333132616234656136353435613339316564
  3533366466323531636530333336313233623634663164650a623631383633646465313333343665
  65303131616436323138643031663337383636653935346631333033623335373765343364323337
  3639396461663937650a333166666461343939353338393733616339333631656364646333323135
  6634

pass: "{{ vault_pass }}"
...

inventory/group_vars/all/vars.yml

Using this works like shown before.

# without provided password
$ ansible-playbook -i inventory/hosts.yml playbook.yml 

PLAY [Test some things with vaults] ****************************************************************************************************************************
ERROR! Attempting to decrypt but no vault secrets found

# With a provided password
$ ansible-playbook -i inventory/hosts.yml --ask-vault-pass playbook.yml 
Vault password: 

PLAY [Test some things with vaults] ****************************************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************************************
ok: [localhost]

TASK [Show user] ***********************************************************************************************************************************************
ok: [localhost] => {
    "msg": "My user is myUser"
}

TASK [Show password] *******************************************************************************************************************************************
ok: [localhost] => {
    "msg": "My password is SuperSecretPassword"
}

PLAY RECAP *****************************************************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Differences between files and variables

Now that you have seen two ways of encrypting variables, you might wonder which one should be used. This is not always the same answer. Both provide different features, that might be interesting for you.

Encrypted variables

Encrypted files

How much is encrypted?

Variables within a plaintext file

The entire file

When is it decrypted?

On demand, only when needed

When loaded or referenced

What can be encrypted?

Only variables

Any structured data file

I hope this helps when deciding which way suites your needs. I prefer the file way, since it structures the data more clearly and keeps regular vars files easy on the eyes.

Next Steps

Now, where to go from here? The above examples are really minimal. You can do way more with ansible-vault. There are options to:

  • have multiple passwords for different scopes (aka vault IDs)
  • use a different secret for vaulted files and variables (files and scripts)
  • integrate with secret management solutions
  • use ansible-vault in pipelines like GitLab-CI

Below, you can find a couple links, that might be helpful for inspiration and use cases.

Cheat Sheet

Before closing the article, let me share one additional cheat sheet. You can find this one and more on the Cheat Sheets page. Maybe even in a more recent and updated version. The sources for the cheat sheets can be found in a repository, too.

I have a huge collection of Ansible articles on the blog. Also, you can find lots of help in the upstream documentation.

Ansible - blog.while-true-do.io
Ansible is IT automation, made simple.
Protecting sensitive data with Ansible vault — Ansible Documentation
ansible-vault — Ansible Documentation
Encrypting content with Ansible Vault — Ansible Documentation

Conclusion

Having a default option to encrypt some secrets is nice to have. Sure, one can use 3rd party tools, sometimes we also need to have a central infrastructure to activate things like automatic password rotation or audits of access.

For small projects and personal use, ansible-vault seems like the perfect option. No additional tooling, no learning curve and easy management.

Do you use Ansible Vault? How do you manage your passwords and secrets? I would love to hear about your experience.