Red Hat Certified Engineer (RHCE) #10 Ansible Vault: Managing Secrets

8 min read

In #9 Tags and Conditionals we ran tasks selectively and controlled flow with conditions and loops. This time it’s about keeping the secret values a playbook handles safe. If you leave values like database passwords, API tokens, or initial user passwords in cleartext in an inventory or variable file, they get exposed as-is in your git repository or the exam grading environment. To prevent this, Ansible provides an encryption mechanism called Ansible Vault.

Vault encrypts an entire variable file, or a single variable, with AES256 and decrypts it with a password only at the moment the playbook runs. On the RHCE exam, “encrypt a secret variable with vault and use it in a playbook” and “place a password file so the playbook runs non-interactively” are perennial tasks, so let’s get the ansible-vault subcommands and runtime options into our hands.

The problem Vault solves #

A playbook’s behavior branches on its variables. Among those variables are values that must not be shown to anyone.

  • Database connection passwords
  • API keys and tokens for external services
  • Initial passwords for user accounts
  • Credential material like TLS private keys

If you write such values in cleartext in group_vars/db/vars.yml, anyone who reads the file learns the secret. Vault keeps the file encrypted on disk and decrypts it only with the vault password supplied at playbook runtime, so even if you commit it to git the contents stay hidden.

An encrypted file begins on its first line with $ANSIBLE_VAULT;1.1;AES256, followed by a blob of hexadecimal below it. When Ansible loads a variable file, it sees this header and automatically attempts decryption.

ansible-vault subcommands #

ansible-vault handles encrypted files through the following subcommands. These six are everything you use on the exam.

CommandWhat it does
ansible-vault create <file>Creates a new encrypted file and opens it in the editor
ansible-vault edit <file>Decrypts an encrypted file and opens it in the editor, re-encrypting on save
ansible-vault view <file>Decrypts an encrypted file and shows its contents on screen
ansible-vault encrypt <file>Encrypts an existing cleartext file
ansible-vault decrypt <file>Reverts an encrypted file back to cleartext
ansible-vault rekey <file>Changes the vault password to a new one

create: making a new encrypted file #

To build an encrypted variable file from scratch, use create. After you enter the password twice, the editor specified by $EDITOR opens.

Create an encrypted file
ansible-vault create group_vars/db/secret.yml
# New Vault password:
# Confirm New Vault password:

Inside the editor you write ordinary YAML.

group_vars/db/secret.yml
# group_vars/db/secret.yml (the cleartext you see in the editor)
db_root_password: "S3cr3t!root"
db_app_password: "App#2026pw"

Once you save and exit, it’s written to disk in encrypted form.

Check the encrypted form
cat group_vars/db/secret.yml
# $ANSIBLE_VAULT;1.1;AES256
# 39613266313764336239...

view and edit: viewing and modifying #

Since an encrypted file shows only hexadecimal under cat, you check its contents with view.

View the contents
ansible-vault view group_vars/db/secret.yml
# Vault password:
# db_root_password: "S3cr3t!root"
# db_app_password: "App#2026pw"

To change a value, use edit. It opens the decrypted temporary contents in the editor and re-encrypts on save.

Edit the file
ansible-vault edit group_vars/db/secret.yml
# Vault password:

encrypt and decrypt: converting an existing file #

If you already have a variable file written in cleartext, encrypt it in one shot with encrypt.

Encrypt an existing file
ansible-vault encrypt group_vars/web/secret.yml
# Vault password:
# Encryption successful

Conversely, to undo the encryption and revert to cleartext, use decrypt.

Decrypt back to plaintext
ansible-vault decrypt group_vars/web/secret.yml
# Vault password:
# Decryption successful

rekey: changing the password #

To swap the vault password, use rekey: enter the current password, then set a new one.

Change the vault password
ansible-vault rekey group_vars/db/secret.yml
# Vault password:
# New Vault password:
# Confirm New Vault password:
# Rekey successful

Encrypting a variable file and using it #

The standard pattern in both practice and the exam is to split the group variables into two. Values that are fine to expose go in vars.yml, and secret values go in vault.yml.

group_vars directory layout
group_vars/
  db/
    vars.yml      # cleartext: db_user, db_port, etc.
    vault.yml     # encrypted: vault_db_password, etc.

Inside the encrypted file, a common convention is to prefix variable names with vault_.

group_vars/db/vault.yml
# group_vars/db/vault.yml (cleartext as seen via ansible-vault edit)
vault_db_password: "S3cr3t!root"

In the cleartext file you wire that vault variable to a regular name. This way the playbook references only an ordinary variable name, while the actual secret lives only in the encrypted file.

group_vars/db/vars.yml
# group_vars/db/vars.yml
db_user: appuser
db_port: 3306
db_password: "{{ vault_db_password }}"

In the playbook you use it just like a cleartext variable.

site.yml
# site.yml
- name: Configure database
  hosts: db
  tasks:
    - name: Set DB root password
      ansible.builtin.debug:
        msg: "connecting with {{ db_user }} / {{ db_password }}"

You must supply the vault password at runtime for the variable to be decrypted.

Run with the vault password
ansible-playbook site.yml --ask-vault-pass
# Vault password:

Supplying the password at runtime #

A playbook that uses encrypted variables won’t run without the vault password. There are two ways to pass it.

–ask-vault-pass: interactive entry #

You enter the password directly when running. This suits a one-off run with a person watching.

Interactive run
ansible-playbook site.yml --ask-vault-pass

You use the same option when running with ansible-navigator.

Run with ansible-navigator
ansible-navigator run site.yml -m stdout --ask-vault-pass

–vault-password-file: automating with a password file #

Write the password on a single line in a file and pass that file’s path, and decryption happens automatically with no input. This is the approach for cron, unattended runs, and the exam requirement to “make it run non-interactively.”

Non-interactive run with a file
echo 'mypassword' > ~/.vault_pass
chmod 600 ~/.vault_pass
ansible-playbook site.yml --vault-password-file ~/.vault_pass

Register it in ansible.cfg and that file is always used, even without the option.

ansible.cfg
# ansible.cfg
[defaults]
vault_password_file = ~/.vault_pass

The ansible-vault subcommands take the same option too, so with a password file in place, view and edit also work non-interactively.

View with the password file
ansible-vault view group_vars/db/vault.yml --vault-password-file ~/.vault_pass

encrypt_string: inline-encrypting a single variable #

When you want to encrypt just one value rather than an entire variable file, use encrypt_string. You can paste the resulting ciphertext block directly into a cleartext YAML file.

Encrypt with encrypt_string
ansible-vault encrypt_string 'S3cr3t!root' --name 'db_password'
# Vault password:
# db_password: !vault |
#           $ANSIBLE_VAULT;1.1;AES256
#           39613266313764336239...

Paste this output into a cleartext vars.yml and the file itself is cleartext, but that single variable alone is encrypted.

group_vars/db/vars.yml
# group_vars/db/vars.yml
db_user: appuser
db_password: !vault |
          $ANSIBLE_VAULT;1.1;AES256
          39613266313764336239...

Have it read the password from standard input and you can use it in a pipeline.

Non-interactive encrypt_string
ansible-vault encrypt_string --vault-password-file ~/.vault_pass \
  'App#2026pw' --name 'db_app_password'

A value tagged with !vault is decrypted automatically when Ansible evaluates the variable, so in the playbook you reference it plainly as {{ db_password }}.

vault id: multiple passwords #

When you use files encrypted with different vault passwords together in one playbook, you distinguish them by attaching labels with a vault id. You pass several at once in the --vault-id label@source form.

Run with multiple vault ids
ansible-playbook site.yml \
  --vault-id dev@~/.vault_dev --vault-id prod@prompt

Specify the same label when encrypting, and on decryption Ansible matches which password to use by the label.

Encrypt with a vault id label
ansible-vault encrypt --vault-id prod@~/.vault_prod group_vars/db/vault.yml

Exam points #

  • Password file permissions. The file used with --vault-password-file holds the cleartext password, so lock it down with chmod 600 so only you can read it. Loose permissions leave the secret-exposure risk intact.
  • Non-interactive runs with –vault-password-file. When the exam says “make it run without entering a password,” create a password file and either register it in ansible.cfg’s vault_password_file or pass it as an option. --ask-vault-pass is interactive and doesn’t fit an automation requirement.
  • Separating what gets encrypted. The standard structure is secrets in vault.yml, cleartext in vars.yml, with the cleartext referencing the vault_ variables. If a secret is left in cleartext, that’s a deduction during grading.
  • A single value uses encrypt_string. When the requirement is not to encrypt the whole file, or to hide just one variable, paste the encrypt_string output inline into a cleartext file.
  • A play with vault variables fails without the password. Check the contents first with ansible-vault view, and verify that supplying the password isn’t missing from your run options.

Wrap-up #

What this post locked in:

  • Ansible Vault encrypts a variable file or a single variable with AES256 and keeps it safely on disk.
  • Subcommands. create, edit, view, encrypt, decrypt, and rekey handle encrypted files.
  • The standard pattern. Put secrets in group_vars/<group>/vault.yml and reference the vault_ variables from a cleartext vars.yml.
  • Supplying the password at runtime. --ask-vault-pass is interactive, --vault-password-file is for non-interactive automation and can also be registered in ansible.cfg.
  • Inline-encrypt a single value with encrypt_string, and distinguish multiple passwords by label with --vault-id.

Next — writing and using roles #

We’ve got the safe handling of secrets down. Now we move on to roles, which bundle playbooks into reusable units.

In #11 Writing and using roles, we’ll build our own: a role’s directory structure (tasks/handlers/templates/vars/defaults), how to scaffold the skeleton with ansible-galaxy init, how to call a role from a playbook, and the common exam type of “write a role to the given requirements and apply it.”

X