Skip to content
Understanding Privilege Escalation in Ansible: become is NOT The Whole Story

Understanding Privilege Escalation in Ansible: become is NOT The Whole Story

August 27, 2026·anupam
anupam

become: true is not always the solution, it’s half of the story. Let me tell you the rest of the story on ansible privilege escalation.

Ansible usually connects to a managed machine as one user and, when needed, uses become to run tasks as another user. For example, Ansible might connect through SSH as one user but execute the actual tasks as a different user. This is normally straightforward when the second user is root. But things become more interesting when both the SSH user and the user you want to become are ordinary, unprivileged users.

That was the situation I ran into while testing Ansible’s become feature. I connected to the managed machine as alice and wanted Ansible to run the tasks as bob.

What looked like a simple user switch turned into two separate problems:

  • Ansible could not safely give the target user access to its temporary module files.
  • After fixing that, Ansible tried to use sudo, even though the SSH user did not have sudo permission.

The final solution involved understanding two things: why Ansible may need setfacl when becoming another unprivileged user, and why become: true does not automatically mean you are allowed everything.

The Setup

The inventory stays the same throughout the example:

ungrouped:
  hosts:
    ubuntu:
      ansible_host: 10.0.0.1
      ansible_user: alice

So Ansible connects to the managed machine like this:

Controller
    |
    | SSH
    v
Managed node
    |
    +--> logged in as alice

There is another user on that machine:

bob

The goal is to connect as alice and run the playbook tasks as bob.

In other words:

Ansible controller
        |
        | SSH as alice
        v
Managed machine
        |
        | become
        v
       bob
        |
        | run Ansible tasks
        v
     commands/modules

Neither alice nor bob is root in this example so both are unpreviliged users.

That detail is important.

The First Playbook

I started with this playbook:

- name: Test play
  hosts: ubuntu
  become: true
  become_user: bob

  tasks:
    - name: Copy to file
      ansible.builtin.copy:
        content: "iamfile"
        dest: /home/bob/file

    - name: Check whoami
      ansible.builtin.shell: "whoami"
      register: output_whoami

    - name: Whoami output
      ansible.builtin.debug:
        msg: "Whoami output: {{ output_whoami.stdout }}"

Then I ran it with:

ansible-playbook -i test_inventory.yaml \
  --key-file lab-setup/alice \
  test_playbook.yaml \
  --ask-become-pass

Ansible asks for a become password because of --ask-become-pass.

But instead of running successfully, it failed with an error similar to this:

Failed to set permissions on the temporary files Ansible needs to create
when becoming an unprivileged user

The important words are:

becoming an unprivileged user

Why does Ansible suddenly care about temporary files?

What Ansible Actually Does When Running a Module

When you write a task such as:

ansible.builtin.copy:

or:

ansible.builtin.shell:

Ansible does not magically execute that YAML directly.

For many modules, Ansible needs code and arguments to be available on the managed machine.

A simplified version of the process looks like this:

Ansible controller
        |
        | SSH as alice
        v
Managed machine
        |
        | Create temporary Ansible files
        | owned/readable by alice
        v
   /tmp/... or ~/.ansible/tmp/...
        |
        | become bob
        v
       bob executes the module

And now we have a problem.

The temporary files were created while Ansible was connected as:

alice

But the module must be executed as:

bob

Normally, Unix permissions do not automatically allow one normal user to read or execute another user’s private files.

For example:

alice owns temporary file
        |
        | Can bob read it?
        v
       No

Ansible therefore needs a safe way to say:

“This temporary file belongs to alice, but bob is allowed to read and execute it for this Ansible task.”

This is where ACLs come in.

What Is setfacl?

Linux normally gives you the familiar permissions:

owner
group
others

For example:

-rw-------

This is simple, but sometimes it is not flexible enough.

Suppose a file belongs to:

alice

You want:

alice -> full access
bob   -> specific access
everyone else -> no access

Traditional owner/group/other permissions cannot express that very nicely unless you change groups or make the file accessible more broadly.

An ACL, or Access Control List, adds more detailed permissions.

With an ACL, you can effectively say:

This file belongs to alice.

Also allow bob to access this file.

The setfacl command is used to set those ACL permissions.

On Debian and Ubuntu, it is commonly provided by the acl package.

Why Ansible Wants setfacl

In this situation, Ansible is dealing with two unprivileged users:

remote_user  = alice
become_user  = bob

Ansible creates its temporary module files as alice.

Then it needs bob to access those files.

If POSIX ACL support is available, Ansible can use setfacl to grant the second user access without making the files readable by everyone. This is one of the mechanisms Ansible documents for safely handling the “unprivileged user becoming another unprivileged user” case. citeturn0search0turn0search1

Conceptually, the situation changes from:

Temporary file
    |
    +-- owner: alice
    |
    +-- bob: no access

to something like:

Temporary file
    |
    +-- owner: alice
    |
    +-- ACL entry: bob is allowed access

That is why the missing setfacl command can cause Ansible to fail before the actual task even starts.

The Ansible documentation specifically recommends installing the package that provides setfacl when you see this temporary-file error.

Installing ACL Support

On the managed node, I installed the acl package:

sudo apt install acl

Now the setfacl command is available.

After that, Ansible can use ACLs when handling temporary files for this particular privilege escalation situation.

Note

Installing acl solved the temporary file permission problem. It did not solve every problem in the playbook. There was still another issue waiting.

Running the Playbook Again

I used the same command again:

ansible-playbook -i test_inventory.yaml \
  --key-file lab-setup/alice \
  test_playbook.yaml \
  --ask-become-pass

This time the temporary-file error disappeared.

But Ansible failed again:

TASK [Gathering Facts]

fatal: [ubuntu]: FAILED!

sudo: I'm sorry alice. I'm afraid I can't do that

This is actually a different problem.

The important part is:

sudo: I'm sorry alice. I'm afraid I can't do that

Ansible was trying to use:

sudo

But why?

The playbook only said:

become: true
become_user: bob

It did not explicitly say:

sudo

The answer is the default become method.

become Does Not Mean su

This is an easy point to misunderstand.

When you write:

become: true

you are telling Ansible:

“Run this with privilege escalation or user switching.”

But you have not told Ansible which mechanism to use.

Ansible supports different become methods, and the default method is sudo. The --ask-become-pass option only asks for the password used by the configured become method; it does not itself change the method. citeturn0search1turn0search11

So this:

become: true
become_user: bob

effectively leads Ansible to try the default behavior:

alice
  |
  | sudo
  v
bob

Conceptually:

SSH connection
      |
      v
    alice
      |
      | sudo -u bob ...
      v
     bob

But my alice user does not have permission to use sudo.

So Ansible fails.

The password provided through:

--ask-become-pass

does not magically give alice sudo permission.

This is a very important distinction.

A password proves that you know a password. It does not grant permissions that the account is not allowed to use.

Why Providing Bob’s Password Did Not Help

At first, it is tempting to think:

“I gave Bob’s password. Why can’t Ansible just become Bob?”

Because the configured method was still sudo.

The password prompt is connected to the become method being used.

With the default method:

become_method: sudo

Ansible is trying to do something based on sudo.

But alice is not allowed to use sudo.

So even if you know Bob’s password, that does not turn this operation into:

su bob

These are different programs with different rules.

sudo

With sudo, permission is controlled by sudo policy, usually configured through /etc/sudoers and related files.

Conceptually:

alice
  |
  | "Am I allowed by sudo policy?"
  v
sudo
  |
  +--> allowed? -> run as another user
  |
  +--> not allowed? -> fail

su

With su, the login user attempts to switch to another account.

Conceptually:

alice
  |
  | su bob
  |
  | provide Bob's password
  v
bob

The Ansible su become plugin is specifically for allowing the remote/login user to execute commands as another user through the su utility. citeturn0search2

So the problem was not:

Wrong password

The problem was:

Wrong become method

The Correct Method for This Setup

I did not want to add alice to the sudoers configuration.

Instead, I wanted this relationship:

SSH as alice
       |
       | su
       | using Bob's password
       v
      bob

So I explicitly changed the become method:

become_method: su

The complete playbook became:

- name: Test play
  hosts: ubuntu
  become: true
  become_user: bob
  become_method: su

  tasks:
    - name: Copy to file
      ansible.builtin.copy:
        content: "iamfile"
        dest: /home/bob/file

    - name: Check whoami
      ansible.builtin.shell: "whoami"
      register: output_whoami

    - name: Whoami output
      ansible.builtin.debug:
        msg: "Whoami output: {{ output_whoami.stdout }}"

Now the configuration clearly says:

Connect as: alice
Become:     bob
Method:     su

Running It With su

I ran the same command:

ansible-playbook -i test_inventory.yaml \
  --key-file lab-setup/alice \
  test_playbook.yaml \
  --ask-become-pass

This time, the password requested by:

--ask-become-pass

is used by the configured su become method.

The Ansible su plugin accepts a become password for switching to the configured become_user. citeturn0search2

The playbook now works.

The output shows:

TASK [Gathering Facts]
ok: [ubuntu]

TASK [Copy to file]
ok: [ubuntu]

TASK [Check whoami]
changed: [ubuntu]

TASK [Whoami output]
ok: [ubuntu] => {
    "msg": "Whoami output: bob"
}

And that final line confirms exactly what we wanted:

Whoami output: bob

The Complete Story

The whole debugging process looked like this:

    flowchart TD
    A[Connect to managed node as alice] --> B[become: true]
    B --> C[become_user: bob]
    C --> D{Can Ansible safely share temporary module files with bob?}

    D -->|No ACL support| E[Temporary file permission error]
    E --> F[Install acl package]
    F --> D

    D -->|Yes| G[Ansible starts executing with become]

    G --> H{Which become method?}
    H -->|Default: sudo| I[alice tries sudo]
    I --> J{Does alice have sudo permission?}

    J -->|No| K[sudo failure]
    K --> L[Set become_method: su]

    H -->|su| M[alice uses su to become bob]
    L --> M

    M --> N[Provide Bob's password]
    N --> O[Tasks run as bob]
  

There were really two completely separate problems.

Problem 1: Temporary Module Files

The first problem happened because:

alice = SSH user
bob   = become user

Both are unprivileged users.

Ansible needed a safe way for bob to access temporary files created while connected as alice.

Installing the acl package provided setfacl, allowing Ansible to use POSIX ACLs where supported for this purpose.

Problem 2: Wrong Become Method

After the temporary-file problem was fixed, Ansible used its default become method:

sudo

But:

alice does not have sudo permission

So it failed.

The solution was to explicitly say:

become_method: su

Now Ansible switches like this:

alice
  |
  | su
  v
bob

using the become password supplied through:

--ask-become-pass

A Small but Important Detail About --ask-become-pass

This option is easy to misunderstand.

It means:

“Ask me for the password needed by the become method.”

It does not mean:

“Use whatever user-switching mechanism matches the password I enter.”

For example:

become_method: sudo

means the become password is used for sudo authentication.

While:

become_method: su

means the become password is used by the su method.

So these settings work together:

become: true
        |
        v
Should Ansible switch users?

become_user: bob
        |
        v
Which user should Ansible become?

become_method: su
        |
        v
How should Ansible become that user?

--ask-become-pass
        |
        v
Ask for the password required by that method

That is the cleanest way to think about it.

Final Working Setup

The inventory:

ungrouped:
  hosts:
    ubuntu:
      ansible_host: 10.0.0.1
      ansible_user: alice

The playbook:

- name: Test play
  hosts: ubuntu
  become: true
  become_user: bob
  become_method: su

  tasks:
    - name: Copy to file
      ansible.builtin.copy:
        content: "iamfile"
        dest: /home/bob/file

    - name: Check whoami
      ansible.builtin.shell: "whoami"
      register: output_whoami

    - name: Whoami output
      ansible.builtin.debug:
        msg: "Whoami output: {{ output_whoami.stdout }}"

The managed node needs the ACL tooling for the temporary-file case:

sudo apt install acl

Then run:

ansible-playbook -i test_inventory.yaml \
  --key-file lab-setup/alice \
  test_playbook.yaml \
  --ask-become-pass

The result:

alice -> SSH connection
alice -> creates/accesses Ansible temporary files
ACL    -> allows safe access for bob where needed
alice -> su bob
bob   -> runs the Ansible tasks

The key lesson is simple:

become: true does not automatically mean su.

By default, Ansible uses sudo. If your SSH user cannot use sudo but can switch to another account using su, explicitly set:

become_method: su

And if both the SSH user and the target become user are unprivileged users, remember that Ansible also has to solve the temporary-file permission problem before your task can even run.

Last updated on