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.