Skip to main content
RunBook Academy

AnsibleXIX · Privilege EscalationPrivilege escalation

Escalation passwords without leaking them

Intermediate⏱ ~17 minansible-playbookansible-vaultansible-config

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

Not yet marked complete on this device.

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

RouteWhere the secret lives afterwards
--ask-become-pass / -KNowhere. Prompted, held in memory for the run
--become-password-fileA file on the controller, at a path you control
ansible_become_password in a vaulted fileEncrypted in the repository
ansible_become_password in a plain inventoryPlaintext 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

Service impact possibleprompting for the escalation password
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:

Read-only / Safethe setting, from the installation
$ 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.

Configuration changean executable password file backed by a secret manager
#!/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/become

The 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.

Configuration changethe same variable, encrypted at rest
# 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_STRING

Two 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.

Service impact possibledo not do this
ansible-playbook -i inventories/prod site.yml -e ansible_become_password=REPLACE_ME

It is written down in at least four places, none of which are obvious at the moment you type it:

  • Shell history. ~/.bash_history on 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 /proc on Linux. Any local account can run ps during 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 -x prints 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

  1. Q1. Why is passing the become password with -e worse than putting it in a plaintext inventory file?

  2. Q2. A become password file can be an executable script, in which case Ansible runs it and uses its stdout as the password.

  3. 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?

  4. 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.