Escalation failures are the second half of the transport story, and they
respond to the same discipline: reproduce it outside Ansible first.
The connection succeeded — you know that, because a task that did not
need become would have worked. What failed is the step after it. That
narrows the problem to one command, run over a connection you have
already proved.
The one command that splits the problem
Read-only / Safeask sudo directly, over the connection Ansible uses— The -n flag means non-interactive: sudo refuses to prompt and fails immediately if a password would be required, which is exactly the condition Ansible is in. The remote command is true, so nothing changes on the host.
ssh ops@app-047.example.com 'sudo -n true' ; echo "exit=$?"
# and to see what the sudoers policy actually grants this user
ssh ops@app-047.example.com 'sudo -l -n'
The defaults are worth knowing while reading that table. Verified from
ansible-config list on 2.21.3: become_method defaults to sudo,
become_user to root, and become_ask_pass to false — so by
default Ansible escalates to root via sudo and will not prompt, which
is why a rule without NOPASSWD fails rather than asking.
The four signatures
Missing sudo password
The remote user has a sudoers rule that requires a password, and no
password was supplied. Ansible cannot prompt in an automated run.
Two fixes, and the choice is a policy decision rather than a technical
one. Either the automation account gets NOPASSWD for the commands it
needs — narrow, auditable, documented in the sudoers file — or the
password is supplied from a vault at run time, which means the password
exists in the automation path and must be protected everywhere it goes.
Most estates choose NOPASSWD scoped tightly, because a password that
automation knows is not much of a second factor.
Incorrect sudo password
A password was supplied and rejected. Worth distinguishing from the
previous case because the diagnosis is different: this one means your
vault content or your lookup is wrong, not that your sudoers policy is.
Check it without printing it. sudo -n true returning “a password is
required” proves the policy; whether the password you hold is correct is
a separate question and one you resolve by rotating rather than by
echoing.
user is not allowed to execute
No rule matched. The most common cause on a mixed fleet is that the rule
exists in /etc/sudoers.d/ on the hosts built by one process and not on
the hosts built by another — which makes this a fleet-consistency
finding as much as a permissions one.
The second most common: the rule is scoped to specific commands, and
Ansible does not run the command you think. It runs a shell invoking a
Python interpreter on a temp file, so a sudoers rule granting
/usr/bin/systemctl does not grant what a systemd_service task
actually executes.
no tty present / requiretty
sudo is configured to require a terminal. Ansible does not allocate
one by default, and pipelining makes it less likely to have one.
This is a sudoers setting on the managed node, and the fix belongs there
— Defaults:<user> !requiretty scoped to the automation account rather
than removed globally. Working around it from the Ansible side by
forcing a pty is possible and hides a policy the host’s owner set
deliberately.
Becoming an unprivileged user
The awkward case, and the one where Ansible’s own mechanics are the
problem rather than the sudoers policy.
When you connect as ops, become: true and become_user: appuser,
the module payload is written to a temp directory owned by ops, and
then a process running as appuser has to read it. Neither user is
root, so no ordinary permission arrangement makes that work without
either widening the file or using POSIX ACLs.
The failure message names the situation directly, and the documented
options are:
Approach
What it costs
POSIX ACLs on the temp directory (setfacl available on the target)
Requires acl installed and a filesystem mounted with ACL support
world_readable_temp
The module payload is world-readable while it exists
pipelining
Avoids writing the payload at all, but requires requiretty off and does not work for every module
Connect as the target user directly
Cleanest where the credential model allows it
Read-only / Safewhat world_readable_temp actually says — read from ansible-doc on 2.21.3— The shell plugin option, read from the installed ansible-core documentation. Nothing is executed against a host.
$ ansible-doc -t shell sh | grep -A 14 world_readable_temp
world_readable_temp This makes the temporary files created on the
machine world-readable and will issue a
warning instead of failing the task.
It is useful when becoming an unprivileged
user.
set_via:
env:
- name: ANSIBLE_SHELL_ALLOW_WORLD_READABLE_TEMP
ini:
- key: allow_world_readable_tmpfiles
section: defaults
vars:
- name: ansible_shell_allow_world_readable_temp
default: false
type: boolean
ANSIBLE_KEEP_REMOTE_FILES
The debugging tool of last resort: keep the module payload on the
managed node so you can run it by hand.
Read-only / Safewhat the setting is and what it disables — read from ansible-config on 2.21.3— ansible-config list prints the shipped default and documentation. The second description line is the part most people do not expect.
$ ansible-config list | grep -A 6 '^DEFAULT_KEEP_REMOTE_FILES'
DEFAULT_KEEP_REMOTE_FILES:
default: false
description:
- Enables/disables the cleaning up of the temporary files Ansible used to execute
the tasks on the remote.
- If this option is enabled it will disable ``ANSIBLE_PIPELINING``.
Two things follow. First, it changes execution behaviour: with
pipelining disabled, the payload is written to disk on every task, which
is a different code path from the one you were debugging. Second, and
more seriously:
Read-only / Safeusing it, and cleaning up after it— Scoped to one host by --limit and to one invocation by the environment variable prefix. The final step removes the preserved payloads from the target, which is a change on the managed node and is deliberately explicit.
# one host, one run, one task
ANSIBLE_KEEP_REMOTE_FILES=1 ansible-playbook -i inventory/prod site.yml \
--limit app-047.example.com --tags failing_task -vvv
# then, on the host, run the preserved payload by hand to see the raw error
ssh ops@app-047.example.com \
'ls -d ~/.ansible/tmp/ansible-tmp-*/ | tail -1'
# and clean up when finished — this deletes files, so it is deliberate
ssh ops@app-047.example.com 'rm -rf ~/.ansible/tmp/ansible-tmp-*'
Knowledge check
Knowledge check · 4 questions
Q1. A task with become: true fails on one host. Which command establishes fastest whether the fault is in sudo or in Ansible?
Q2. Which are accurate about world_readable_temp on ansible-core 2.21.3? Select all that apply.
Q3. Setting ANSIBLE_KEEP_REMOTE_FILES has no effect on how tasks execute; it only stops the cleanup step at the end.
Q4. A sudoers rule grants the automation account NOPASSWD on /usr/bin/systemctl, but a systemd_service task still fails with user is not allowed to execute. Why?
Passing score: 75%. Answers are checked in this browser.