AnsibleXIX · Privilege EscalationPrivilege escalation
Escalation passwords without leaking them
What you'll learn
- Name every supported route for supplying a become password and its exposure
- Explain why passing a secret with -e is the most durable leak of the set
- Store a become password so that it is encrypted at rest in the repository
- Use a become password file, including the executable form
- Prefer an authorisation design that removes the password from the run entirely
Prerequisites
Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11
If the fleet’s sudo rule requires a password, every run needs it, and the question becomes where it comes from. There are five supported routes, and they differ almost entirely in where the secret ends up written down.
Part XXI owns secrets management for the course. This lesson owns the become-specific case, because it is the one every estate meets in its first month and the one where the convenient answer is the worst one.
The five routes, ranked by what they leave behind
| Route | Where the secret lives afterwards |
|---|---|
--ask-become-pass / -K | Nowhere. Prompted, held in memory for the run |
--become-password-file | A file on the controller, at a path you control |
ansible_become_password in a vaulted file | Encrypted in the repository |
ansible_become_password in a plain inventory | Plaintext in Git, forever, in every clone |
-e ansible_become_password=... | Shell history, process list, and CI logs |
The top three are defensible. The bottom two are the ones that appear in tutorials.
-K: correct, and unusable unattended
ansible-playbook -i inventories/prod site.yml --limit webservers --ask-become-pass-K is the short form. It prompts once, applies to every host in the
run, and leaves nothing on disk.
Its limitation is structural: something has to type it. That rules it out for scheduled runs, CI pipelines and anything event-driven — which is most of the automation this course is building towards. It remains the right choice for an operator running a change by hand, and it is worth defaulting to it for those runs rather than reaching for a stored secret out of habit.
--become-password-file: the unattended answer
Since ansible-core 2.12 there is a first-class password file, with a
property worth knowing:
$ ansible-config list | grep -A8 'BECOME_PASSWORD_FILE:'BECOME_PASSWORD_FILE:
default: null
description:
- The password file to use for the become plugin. `--become-password-file`.
- If executable, it will be run and the resulting stdout will be used as the password.
env:
- name: ANSIBLE_BECOME_PASSWORD_FILE
ini:
- key: become_password_file
section: defaults“If executable, it will be run and the resulting stdout will be used as the password” is what makes this the bridge to a real secret manager. The file does not have to contain the secret; it can be a script that retrieves it.
#!/bin/sh
# /etc/ansible/become-pass.sh - mode 0700, owned by the automation account.
# Prints the escalation password on stdout and nothing else. Any stray
# output - a warning, a progress bar - becomes part of the password.
set -eu
exec vault-client read --field=password secret/ansible/becomeThe trailing-newline question is the one that costs an afternoon: a file whose contents end with a newline is handled, but a script that prints extra diagnostics on stdout is not. Redirect anything that is not the password to stderr.
ansible_become_password in inventory
The connection variable works, and where you put it decides everything.
# inventories/prod/group_vars/all/vault.yml
ansible_become_password: !vault |
$ANSIBLE_VAULT;1.2;AES256;prod
REPLACE_ME_WITH_THE_ACTUAL_VAULTED_STRING
REPLACE_ME_WITH_THE_ACTUAL_VAULTED_STRINGTwo details make this workable rather than merely encrypted.
Vault the value, not the whole file, when the file has other content.
A fully encrypted group_vars file is an opaque blob in every pull
request; a single !vault scalar keeps the rest of the file diffable.
Use a vault ID per environment. --vault-id prod@prompt and
--vault-id staging@prompt mean the staging key cannot decrypt
production secrets. Part XXI develops this; the become password is a
good first candidate because it is usually the first secret an estate
has.
Why -e is the worst option
Passing the password as an extra variable is the route that appears in answers to “how do I make this work in Jenkins”, and it is the most durable leak of the five.
ansible-playbook -i inventories/prod site.yml -e ansible_become_password=REPLACE_MEIt is written down in at least four places, none of which are obvious at the moment you type it:
- Shell history.
~/.bash_historyon the controller, unencrypted, readable by that account and by root, retained for hundreds of commands. - The process list. Command-line arguments are world-readable in
/procon Linux. Any local account can runpsduring the run and see it. This is not a race you can win: a fleet-wide play runs for minutes. - CI job logs. Most runners echo the command they execute. The password lands in a log retained for the pipeline’s retention period and readable by everyone with access to the project.
- Shell trace output. A pipeline step running under
set -xprints every command it runs.
-e also carries the highest variable precedence, which is the separate
problem covered in Part IX: a value supplied that way overrides
everything, including the vaulted value you thought was in effect, so
the leak can be silently shadowing the correct configuration too.
The option that removes the question
The best handling of a secret is not to need it.
A sudoers rule with NOPASSWD for the automation account eliminates the
become password from the run entirely: nothing to store, nothing to
rotate, nothing to leak. That is not a weakening if the rule is scoped —
it moves the control from “prove you know a password” to “this account
may run exactly these things as root from this host”, which is stronger
and far easier to audit.
The trade is real and worth stating plainly. NOPASSWD means anyone who
obtains the automation account’s SSH key can execute the permitted
commands as root without a second factor. That makes key custody the
whole control, which is why Part XLVII treats controller compromise as
fleet compromise.
The design that follows from it — scoped rules rather than
ALL=(ALL) NOPASSWD: ALL — is the next lesson.
Knowledge check
Knowledge check · 4 questions
Q1. Why is passing the become password with -e worse than putting it in a plaintext inventory file?
Q2. A become password file can be an executable script, in which case Ansible runs it and uses its stdout as the password.
Q3. A playbook that works when an operator runs it by hand times out with no output when the same command runs in CI. What is the most likely cause?
Q4. A repository audit is looking for stored escalation passwords. Which names should the search cover? Select all that apply.
Passing score: 75%. Answers are checked in this browser.