Executing Ansible Playbook on Custom PHP Script Revealed "error initializing audit plugin sudoers_audit"

Some information about Ansible:

ansible 2.10.8
  config file = /home/graysonpeddie.lan/ansible/ansible/ansible.cfg
  configured module search path = ['/home/graysonpeddie.lan/ansible/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3/dist-packages/ansible
  executable location = /usr/bin/ansible
  python version = 3.9.2 (default, Feb 28 2021, 17:03:44) [GCC 10.2.1 20210110]
ansible@multiservers:~/ansible$ cat /etc/os-release 
PRETTY_NAME="Debian GNU/Linux 11 (bullseye)"
NAME="Debian GNU/Linux"
VERSION_ID="11"
VERSION="11 (bullseye)"
VERSION_CODENAME=bullseye
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"
ansible@multiservers:~/ansible$ sudo --version
Sudo version 1.9.5p2
Sudoers policy plugin version 1.9.5p2
Sudoers file grammar version 48
Sudoers I/O plugin version 1.9.5p2
Sudoers audit plugin version 1.9.5p2
ansible@multiservers:~/ansible$ php --version
PHP 7.4.30 (cli) (built: Jul  7 2022 15:51:43) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.30, Copyright (c), by Zend Technologies

First, here’s my playbook that I wrote a couple of years ago:

---

- name: copy the issue file to both /etc/issue and /etc/issue.net.
  hosts: all
  become: true
  tasks:
  - name: copy the issue file to /etc/issue.
    copy:
      src: ~/ansible/files/issue
      dest: /etc/issue
  - name: copy the issue file to /etc/issue.net.
    copy:
      src: ~/ansible/files/issue
      dest: /etc/issue.net

(Vim’s visual mode won’t let me copy text so I had to execute :set mouse-=a to disable visual mode when copying text from Vim. What an annoying mouse-related feature…)

Here’s an output from Ansible when I execute a playbook in a non-interactive way (meaning that I don’t get a password prompt):

ansible@multiservers:~/ansible$ ansible-playbook update_issue.yml --extra-vars "ansible_become_pass=[REDACTED]"

PLAY [copy the issue file to both /etc/issue and /etc/issue.net.] ***************************************************************************************************************************************************************************

TASK [Gathering Facts] **********************************************************************************************************************************************************************************************************************
ok: [localhost]
ok: [172.20.1.4]
ok: [172.20.1.6]

TASK [copy the issue file to /etc/issue.] ***************************************************************************************************************************************************************************************************
ok: [localhost]
ok: [172.20.1.6]
ok: [172.20.1.4]

TASK [copy the issue file to /etc/issue.net.] ***********************************************************************************************************************************************************************************************
ok: [localhost]
ok: [172.20.1.6]
ok: [172.20.1.4]

PLAY RECAP **********************************************************************************************************************************************************************************************************************************
172.20.1.4                 : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
172.20.1.6                 : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Here’s what I get when I execute the playbook from the PHP script:

PLAY [copy the issue file to both /etc/issue and /etc/issue.net.] **************

TASK [Gathering Facts] *********************************************************
fatal: [localhost]: FAILED! => {"msg": "privilege output closed while waiting for password prompt:\nsudo: PERM_ROOT: setresuid(0, -1, -1): Operation not permitted\nsudo: error initializing audit plugin sudoers_audit\n"}
ok: [172.20.1.6]
ok: [172.20.1.4]

TASK [copy the issue file to /etc/issue.] **************************************
ok: [172.20.1.6]
ok: [172.20.1.4]

TASK [copy the issue file to /etc/issue.net.] **********************************
ok: [172.20.1.6]
ok: [172.20.1.4]

PLAY RECAP *********************************************************************
172.20.1.4                 : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
172.20.1.6                 : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
localhost                  : ok=0    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

Here is the PHP script that I wrote that lists the files that have a *.yml extension and executes a playbook once a password is entered.

ansible@multiservers:~/ansible$ cat /var/www/172.20.31.5-ansible/index.php 
<!DOCTYPE html>
<html lang="en">
<head>
  <title>Ansible Playbooks</title>
  <meta charset="utf-8" />

</head>
<body>
  <header>
    <h1>Ansible Playbooks</h1>
  </header>
  <main>
  <form method="post">
<?php
chdir('/path/to/ansible/playbook');
$playbooks = glob("*.yml"); ?>
    <fieldset>
    <legend>Select a playbook:</legend>
<?php for($i = 0; $i < count($playbooks); $i++) {
    $playbookName = isset(yaml_parse_file($playbooks[$i])[0]['name']) ? yaml_parse_file($playbooks[$i])[0]['name'] : $playbook; ?>
      <div>
        <input type="radio" name="playbook" value="<?=$playbooks[$i] ?>" id="p<?=$i ?>" />
        <label for="p<?=$i ?>"><?=$playbookName ?>
      </div>
<?php } ?>
    <div>
      <label for="password">Sudo Password:</label>
      <input type="password" name="password" id="password" required="required" />
    </div>
    <div>
      <input type="submit" value="Run Playbook" />
    </div>
    </fieldset>
    <h2>Output:</h2>
<?php if(!isset($_POST['playbook'])) { ?>
<pre>Select a playbook.</pre>
<?php } else {
    $playbook = escapeshellcmd($_POST['playbook']);
    $password = $_POST['password'];
    $output = shell_exec(escapeshellcmd('ansible-playbook '.$playbook.' --extra-vars "ansible_become_pass='.$password.'"')); ?>
<pre>
Executing <?=$playbook ?>...
<?=$output ?>
</pre>
<?php } ?>
  </main>
</body>
</html>

For this to work, you need to have php-yaml package installed in your web server.

What I am trying to achieve is that I want to have a GUI way of executing Ansible playbooks. I’ve been looking for a lightweight web application that allows me to execute playbooks, but I decided to write my own. Not a fully-fledged GUI editor for adding/removing playbooks, but just for listing the playbooks and executing them. I use a password manager to store my password so I do not have to type the password again and again.

I realize that I do have a vulnerability in my script where if the playbook is executed and if an attacker were to view the process output in realtime, my password can show up in ps aux and that manually executing my playbook with a pre-entered password is also a vulnerability as I can inadvertently store my password in my .bash_history file (lines can be erased), so I’m looking for ways to cut down on the vulnerabilities. I do not know if I could use --ask-become-pass in my PHP script as this will prompt for a password from the command line. I don’t know how prompting for a password worked from a PHP script. Is prompting for a password possible with shell_exec()? Yes, that function is a vulnerability in of itself, but I want to use my script internally.

However, I do have a question: how can output in my PHP script show an error message…

fatal: [localhost]: FAILED! => {"msg": "privilege output closed while waiting for password
prompt:\nsudo: PERM_ROOT: setresuid(0, -1, -1): Operation not permitted\nsudo: error initializing
audit plugin sudoers_audit\n"}

…and executing a command from the terminal does not? I have done some searching regarding how to fix that issue to no avail. The password for sudo is correct for both the command line and from a PHP script.

Have you looked into AWX at all?

No, but if it’s as simple as pointing a directory to the playbooks that I have created and simply run from the web UI, I might check it out. I do not use Git, so I would rather not have to rely on the repository if I do not want to. It will have to be very basic and very lightweight.