Every lesson so far has moved a secret behind a password. This one asks the
obvious follow-up question, which is remarkably often the one nobody asks:
Where is that password?
Encryption does not remove a secret. It replaces many secrets with one, and
the security of the whole arrangement collapses to the security of the
replacement. If the vault password is easier to obtain than the credentials
it protects, the encryption has made things worse — it has added a step and
a false sense of completion.
Or the same thing with vault_password_file = .vault-pass in
ansible.cfg, and .vault-pass in .gitignore so it is at least not
committed.
Anyone who can read the repository directory can read the password.
Which means anyone who can read the repository can decrypt the vault.
Which means the encryption protects the repository against exactly one
thing: being copied without its directory — a git clone by someone who
has repository access but not filesystem access to that server.
That is not nothing. It defends against a leaked clone, a mirror, a
misconfigured Git host, a laptop backup of a checkout. Those are real and
they happen. But it is a much narrower protection than “our secrets are
encrypted” suggests, and the gap between the belief and the reality is
where incidents live.
What the setting actually accepts
Read-only / Safethe documented behaviour— From ansible-config list on ansible-core 2.21.3.
$ ansible-config list | grep -A3 DEFAULT_VAULT_PASSWORD_FILE
DEFAULT_VAULT_PASSWORD_FILE:
description:
- The vault password file to use. Equivalent to --vault-password-file or --vault-id.
- If executable, it will be run and the resulting stdout will be used as the password.
default: null
That second line is the interesting one. If the file is executable,
Ansible runs it and uses its standard output. The “password file” is
therefore a general extension point, not just a place to keep a string.
Which opens the good options:
Source
Password at rest on the controller?
Auditable centrally?
prompt
no
no
plain file
yes
no
executable script
no, if it fetches
yes, if the store logs
client script
no, if it fetches
yes, and it knows the label
keyring via a script
in the keyring, unlocked per session
partly
Executable, and vault-ID-aware
There are two kinds of executable source and the difference is the
filename. Measured on 2.21.3:
Read-only / Safea plain executable gets no arguments— Executed on 2.21.3. The script echoes its own argument list to stderr. The list is empty.
Read-only / Safea script named *-client is told which label is wanted— Executed on 2.21.3. Same script, renamed. Ansible passes --vault-id and the label.
client invoked with: [--vault-id dev]
no password configured for vault-id: dev
A script whose name ends in -client — or -client.EXTENSION — is
invoked with --vault-id <label>. A plain executable is invoked with
nothing and cannot know which of several passwords is being asked for.
Ansible even distinguishes them in its error text, which is a useful
diagnostic when a script is not being called the way you expected:
Vault password script ... returned non-zero (1)Vault password client script ... returned non-zero (1) when getting secret for vault-id=dev
If you see the first message and expected the second, your script is not
named -client.
Read-only / Safea client script that fails closed
#!/bin/sh
# /usr/local/lib/ansible/vault-client.sh
# Named *-client so Ansible passes --vault-id <label>.
# Fetches from the platform secret store; nothing is stored on this host.
set -eu
label=""
while [ $# -gt 0 ]; do
case "$1" in
--vault-id) label="$2"; shift 2 ;;
*) shift ;;
esac
done
case "$label" in
dev|staging|prod) ;;
*) echo "refusing unknown vault-id" >&2; exit 1 ;;
esac
# Writes ONLY the password to stdout. Diagnostics go to stderr, and
# must never contain the password - see the warning below.
secret-store get "ansible/vault/$label" 2>/dev/null
Two design points in that script, both load-bearing.
It fails closed. An unrecognised label exits non-zero rather than
falling through to a default. A client script that returns the production
password for any label it does not recognise is worse than no scheme at
all, because it looks like a scheme.
It writes only the password to standard output. Anything else on stdout
becomes part of the password — a debug line, a trailing banner, a warning
from a tool you called. This is the most common bug in a hand-written
client script and it presents as a decryption failure with a correct
password, which is a miserable thing to debug.
Ranking the options
prompt — nothing at rest anywhere. Correct for interactive work
against production, and impossible for unattended runs. Use it as the
default for a workstation, and stop treating “but I have to type it” as a
problem to engineer away: for production material, being unable to run
unattended from a laptop is the feature.
Plain file — the common answer. Acceptable when, and only when, the
file is mode 0600, owned by a dedicated automation account, on a machine
whose login access is itself the control. Its weaknesses are that it does
not expire, its access is not logged, and revoking it means finding every
copy.
Client script fetching from a secret store — the best of these for an
unattended controller. Nothing at rest, access is logged and revocable
centrally, and one script serves every label. The cost is a hard dependency
on the store, and the same trade-off appears again in the external secret
managers lesson.
Keyring — a good fit for an engineer’s workstation. The password lives
in the desktop keyring, unlocked once per session by the user’s login. A
small script reading from it is an executable password source like any
other. It is not a fit for a headless controller, where there is nobody to
unlock it.
Knowledge check
Knowledge check · 4 questions
Q1. A repository has an encrypted vault.yml and a .vault-pass file at mode 0644 in the same directory, listed in .gitignore. What has the encryption actually achieved?
Q2. On 2.21.3, what distinguishes a vault password script named vault-client.sh from one named vault-pass.sh?
Q3. Which are correct design rules for a vault client script? Select all that apply.
Q4. Fetching the vault password from a secret store at run time changes offboarding from asking who holds a copy to asking which identities are authorised.
Passing score: 75%. Answers are checked in this browser.