Vault & secrets¶
Scope: the course page shows one
vault.ymlper group encrypted with a single password, run via--ask-vault-passor--vault-password-file, plus the vault-indirection naming trick, vault IDs as a concept, andencrypt_stringfor one-off values. This page slows down on rotating a vault password without touching its content, running with several vault passwords at once, and not having to pass--vault-idon every invocation — then zooms out to where secrets can come from besides Vault at all.
TL;DR — in 30 seconds:
ansible-vault rekeyrotates a vault password without ever exposing the file's plaintext to you — it decrypts with the old password and re-encrypts with the new one in one step, but every teammate/CI job still needs the new password file afterward.- A
--vault-password-filetarget can be a plain file or an executable — the executable form is what lets a password live in a keychain or secrets manager instead of on disk, since Ansible runs it and reads the password from stdout. - Vault isn't the only way a secret reaches a play: an
envlookup or an external secret-manager lookup both keep the value out of the repo entirely, at the cost of depending on something live at run time instead of something committed.
1. Rekeying — rotating a password without re-encrypting by hand¶
A vault password should be rotated periodically (an operator leaves, a password leaks, a compliance
policy requires it). ansible-vault rekey re-encrypts a file under a new password without ever
exposing its plaintext to you — you never decrypt then encrypt by hand:
ansible-vault rekey group_vars/prod/vault.yml --new-vault-password-file ~/.ansible/vault-pass-prod-new
Ansible decrypts with the old password (prompted, or read from --vault-password-file if the file
still holds it) and re-encrypts with the new one in a single step. Once rekeyed, every teammate and every
CI job needs the new password file — rekeying one file doesn't rotate anyone's stored copy for you.
2. The vault-password-file: plaintext file vs executable script¶
--vault-password-file accepts two different kinds of target, and the distinction matters for where a
password can safely live:
| Target | Behavior |
|---|---|
| A plain text file | Ansible reads its contents (trimmed of a trailing newline) as the password directly |
| An executable file | Ansible runs it and reads the password from its stdout |
The executable form is what lets the password live in a credential manager instead of on disk: the
"password file" is really a small script that calls out to your OS keychain, a password manager's CLI, or
a secrets API, and prints the result. Either way the file must not be world-readable — restrict its
permissions (chmod 600) and never commit it, the same rule the course page already applies to the vault
password itself.
3. Running with more than one vault password at once¶
A real inventory usually has more than one vault-encrypted group (prod, staging, each with its own
password). The course page's single-password flow doesn't cover this: you pass one --vault-id per
password, and Ansible tries each candidate against each encrypted blob until one matches:
ansible-playbook site.yml \
--vault-id prod@~/.ansible/vault-pass-prod \
--vault-id staging@~/.ansible/vault-pass-staging
This works because an encrypted file's header carries a version, a cipher, and — when it was encrypted
with --vault-id label@… — the label it was encrypted under (format documented
here):
$ANSIBLE_VAULT;<version>;<cipher>[;<label>]
Ansible matches an incoming --vault-id label@source against that header label first; if no label is
present in the header (a file encrypted the old single-password way) it falls back to trying every
supplied password in turn. This is why the label in --vault-id label@source is only a hint for matching
and routing — it is not itself part of the encryption.
flowchart LR
F1["group_vars/prod/vault.yml\nheader label: prod"]
F2["group_vars/staging/vault.yml\nheader label: staging"]
V1["--vault-id prod@vault-pass-prod"]
V2["--vault-id staging@vault-pass-staging"]
V1 -->|label matches| F1
V2 -->|label matches| F2
4. Not typing --vault-id every time — ansible.cfg¶
Once a project has more than one vault password, retyping every --vault-id on every run gets old fast.
ansible.cfg can declare the same identities once, under [defaults]:
[defaults]
vault_identity_list = prod@~/.ansible/vault-pass-prod, staging@~/.ansible/vault-pass-staging
Every ansible-playbook run in that project then has both identities available automatically. A single,
unlabeled password can similarly be set with vault_password_file = <path> for the simple, one-password
case the course page shows. Either setting does exactly what its command-line equivalent does — it just
saves you from repeating the password source on every invocation or in every CI step.
5. Secret injection beyond Vault¶
Vault is not the only way a secret reaches a play — it is the one that fits "commit an encrypted value into the repo." Two other patterns solve the same problem differently, and knowing they exist matters more than memorizing their exact syntax:
| Pattern | How the secret gets in | Trade-off |
|---|---|---|
| Ansible Vault (course page) | Encrypted at rest in the repo; decrypted at run time with a password | Secret travels with the code; password distribution is now your problem |
| Environment-variable lookup | An env lookup reads a variable already present in the runner's environment |
No secret content in the repo at all; but it depends on whatever set that environment variable being trustworthy and not logged |
| External secret-manager lookup | A lookup plugin calls out to a secrets service (e.g. a HashiCorp Vault server, a cloud provider's secrets manager) at run time and returns the current value | Nothing secret ever sits at rest in the repo, and rotation happens centrally — at the cost of needing network access and its own authentication at run time |
flowchart TB
T["A task needs {{ db_password }}"]
A["Ansible Vault\nencrypted file, decrypted with a password"]
B["env lookup\nreads the runner's environment"]
C["Secret-manager lookup\nlive call to an external service"]
A --> T
B --> T
C --> T
The three are not mutually exclusive on one project: it's common to Vault-encrypt long-lived, rarely rotated values while pulling anything that rotates frequently or is centrally managed from an external secret manager instead.
6. How this connects¶
The course page's one vault.yml, one password, one encrypt_string example is the smallest version of
"keep a secret out of plaintext in the repo." This page is what to reach for once that password needs to
rotate, once a project has more than one environment's worth of secrets to juggle at once, once retyping
--vault-id on every run gets in the way, or once the question stops being "how do I encrypt this" and
becomes "should this even live in the repo, encrypted or not."
Common gotchas
- Manually
decrypt-then-encrypt-ing to rotate a vault password. Fix: useansible-vault rekeyinstead — it never exposes the plaintext to you in between, and does the old→new swap in one step. - Leaving a
--vault-password-filetarget world-readable. Fix:chmod 600it and never commit it, whether it's a plaintext password file or an executable script that calls out to a keychain. - Forgetting that rekeying a file doesn't rotate anyone else's stored copy. Fix: after
rekey, every teammate and CI job needs the new password file — the old one no longer works against that file. - Retyping every
--vault-idon every invocation instead of settingvault_identity_listinansible.cfg. Fix: declare all identities once under[defaults]and every run in that project picks them up automatically.
Check yourself — you rekey group_vars/prod/vault.yml to a new password. A CI job using the old password file tries to run against it the next day. What happens?
It fails to decrypt — rekeying re-encrypts the file under the new password only, it doesn't propagate to anyone else's stored copy. Every consumer (teammates, CI) needs the new password file before their next run.
Check yourself — your --vault-password-file argument points at an executable script instead of a plain text file. What does Ansible actually do with it?
It runs the script and reads the password from its stdout, rather than reading file contents directly — that's what lets the password live in a credential manager or OS keychain instead of sitting in a file on disk.
You can defend this when you can explain why rekey never exposes plaintext to the operator, describe
the difference between a plaintext and an executable --vault-password-file target, say how Ansible
matches a --vault-id label against an encrypted file's header, name the ansible.cfg setting that
removes the need to repeat --vault-id on every invocation, and name at least one alternative to Vault for
getting a secret into a play and the trade-off it makes against Vault.