AWC is a web application written in GO that allows you to execute preconfigured tasks as a user of your Linux system. The idea is to run Ansible Playbooks and view the results remotely. Arguments can be supplied using HTML-Forms. The forms to fill in and commands to execut will be generated dynamically based on the commands.xml file. Beside Playbooks technically any Bash command (and therefore scipts, applications, etc.) can be configured as a task.
This application was created for people who want to run scripts remotely without having to access the shell. Especially, for Ansible users who do not need the vast amount of functions included in AWX or Ansible Tower.
Language
GO, HTML
OS
Linux (tested on CentOS 8 and 9; any distribution should work)
Abstract
Define Shell/ Bash commands (and parameter if required); Show commands on webinterface; Enter parameter (if defined) in generated web-form; Run commands (automatically replace parameter-spacers with user input and write to logfile) from web; List and read log files (result of commands) in webinterface
Dependencies
Using Binary: None (to run the bare application)
Compiling: GO
This application is NOT designed for production use! You may use it for testing or in home lab environments.
DO NOT run this application as root! Once configured the commands can be executed with any parameter. You have to make sure that the parameter will not endanger your system. I recommend using a non root user that can only connect to the Ansible clients via SSH.
Currently there is no authentication included in this application. I recommend blocking the application's port via a firewall and only allow access to the application from localhost or a reverse proxy with authentication (compare installation).
Following the setup instructions, you will not be able to make system-level changes (e.g. updates) to the server that runs Ansible itself. This is a security measure, but you could grant more privileges to the user running the web-application at your own risk.
The included HTML UI/ front-end is (to be kind) very simple. If the UI does not appeal to you and you are a better front-end developer then I am, I designed this application so you can change the complete UI without having to touch or recompile the binary. Just change the files inside the HTML directory to your needs (just make sure to include all the ??<SOME WORD>??s)!
The Environment chapter explains the intended use case of the application. If you do not use it for executing Ansible Playbooks you can skip this part and just create a non-admin user that will be used to execute the application.
The commands in the Installation chapter are tested on Centos 8 and 9 Stream. If you are using another OS you may skip the SELinux and firewall commands and use apache2 instead of https. Beware, the directories for the webserver will only match if you are using httpd!
The Configuration explains how to use the configuration files of this application to configure your remotely executable commands. Read this chapter carefully!
There are demo playbooks with their configuration in the commands.xml located in the demo subdirectory.
I started with a fresh installed Centos 8 Stream Server without desktop. My account is not root, but part of the sudo group.
I wanted to setup Ansible as management tool and AWC to remotely execute my Playbooks.
-
Install Ansible
sudo yum install epel-release sudo yum install ansible
-
Create user to run Playbooks (NOT part of sudo-group) and deny ssh login
sudo useradd ansible -m sudo passwd ansible echo "DenyUsers ansible" >> /etc/ssh/sshd_config
-
Setup demo Ansible files
sudo su - ansible mkdir inventory mkdir playbooks nano inventory/hosts # add your servers that will be managed - compare 1st content below nano playbooks/create-testfile.yml # add 2nd content below nano playbooks/update-ubuntu.yml # add 3rd content below[ubuntu] server1.example.com server2.example.com 10.0.0.6 [centos] localhost--- - hosts: "{{ targets }}" become: yes # become root user on target machine gather_facts: no # default this will be set to yes - collect data before starting #vars: #ansible_port: 12345 # add this lines if you are not using the default ssh port (22) tasks: - name: Creating a test file with content copy: dest: "{{ path }}" content: "{{ content }}"
--- # works for ubuntu & debian - hosts: ubuntu become: yes gather_facts: no tasks: - name: Apt Update apt: update_cache=yes force_apt_get=yes cache_valid_time=3600 - name: Apt Upgrade apt: upgrade=dist force_apt_get=yes - name: Check if reboot is required register: reboot_required_file stat: path=/var/run/reboot-required get_md5=no - name: Reboot if required (e.g. due to new kernel) reboot: msg: "Reboot initiated by Ansible due to kernel updates" connect_timeout: 5 reboot_timeout: 300 pre_reboot_delay: 0 post_reboot_delay: 30 test_command: uptime when: reboot_required_file.stat.exists
-
Add a manged node (on the node)
Create a user that Ansible will connect to on the managed nodes. This user will be part of the sudo group so Ansible will be able to execute tasks that require root privileges. The user will be configured so it can use
sudowithout entering a password (so that no password has to be provided to Ansible).HINT
Instead of allowing the user to use "sudo" without entering its password, you might supply the password to Ansible. Do not store it in plaintext on your Ansible controller - useansible-vault!
[Click here to get instructions]](#use-ansible-vault-insted-of-sudoers)sudo su - # create user "ansible" useradd ansible -m passwd ansible # add user to sudo-group --> grant right to user "sudo ..." sudo usermod -aG sudo ansible # allow user to use "sudo" without entering its password echo "ansible ALL=(ALL:ALL) NOPASSWD:ALL" >> /etc/sudoers # disable ssh login to user echo "Match User ansible" >> /etc/ssh/sshd_config echo " PasswordAuthentication no" >> /etc/ssh/sshd_config # STOP! complete step 5 first - then enter the last two commands on the managed node # restart sshd to apply changes systemctl restart sshd exit
-
Add a manged node (on the server with Ansible installed)
sudo su - ansible # WARN - Only run ssh-keygen command the first time you set up a managed node (DO NOT RUN IT IF YOU ALREADY HAVE A PRIVATE SSH KEY) # generate private ssh key ssh-keygen -t ecdsa -b 521 # just hit enter without providing any text to questions # copy public ssh key to managed node so Ansible can log in with its private key ssh-copy-id ansible@<FQDN> #-p 12345 # add the -p argument when using a custom port # you will be prompted for a password --> use the one set up in step 4
-
Download binary (replace url with latest version) and set permission
# create folders in user's folder sudo mkdir -p /home/ansible/awc sudo mkdir -p /home/ansible/logs # prepare log counter echo "0" > /home/ansible/logs/cntr # download the binary and unzip it into the created folders curl -O https://github.com/0Raptor/AnsibleWebController/releases/download/v1.0/AWC-Linux_AMD64.zip sudo unzip AWC-Linux_AMD64.zip -d /home/ansible/awc # set permissions to let user manage the directories and execute the binary sudo chmod +x /home/ansible/awc/awc sudo chown -R ansible:ansible /home/ansible/awc sudo chown -R ansible:ansible /home/ansible/logs
-
Install webserver to use as reverse proxy with authentication
# use package manager to install apache2 webserver sudo yum install httpd -y # create a new password file with your username (DO NOT USE -c option when adding more users) sudo htpasswd -c /etc/httpd/.htpasswd <USERNAME> # update firewall to allow HTTP (80/TCP) and HTTPS (443/TCP) firewall-cmd --add-service https --permanent firewall-cmd --add-service http --permanent firewall-cmd --reload # setup webserver to start during boot and start it the first time sudo systemctl enable httpd sudo sytemctl start httpd
-
Open your server's domain name/ IP in your browser to confirm the HTTPD-Testpage is shown
-
Configure the reverse proxy
nano /etc/httpd/conf.d/welcome.conf # Comment evrything out nano /etc/httpd/conf.d/awc.conf # Add content bellow # allow webserver to connect to other webservices - required so it can communicate with the webserver delivered by the binary sudo setsebool -P httpd_can_network_connect on # restart webserver to apply changes sudo sytemctl restart httpd<VirtualHost *:80> ProxyPreserveHost On ProxyPass / http://localhost:8080/ ProxyPassReverse / http://localhost:8080/ Timeout 5400 ProxyTimeout 5400 ServerName <YOUR DOMAIN NAME> ServerAlias <YOUR ALIAS DOMAIN NAME> <Proxy *> Order deny,allow Allow from all Authtype Basic Authname "Password Required" AuthUserFile /etc/httpd/.htpasswd Require valid-user </Proxy> </virtualhost> -
Start AWC as the non-root-user
sudo su - ansible cd /home/ansible/awc ./awc -
Refresh your browser (compare 2.) and enter credentials --> AWC interface should be visible
-
Make AWC start during boot
# Allow systemd execution in selinux sudo semanage fcontext -a -t bin_t "/home/ansible/awc/awc" sudo restorecon -v /home/ansible/awc/awc # Create service sudo nano /etc/systemd/system/awc.service # Add content bellow # Load, start and enable service sudo systemctl daemon-reload sudo systemctl start awc sudo systemctl status awc # should say "active (running)" sudo systemctl enable awc
[Unit] Description=Asible Web Controller Service After=network.target [Service] Type=simple User=ansible Group=ansible WorkingDirectory=/home/ansible/awc ExecStart=/home/ansible/awc/awc Restart=always [Install] WantedBy=multi-user.target -
Secure webserver with HTTPS
sudo su - # install ssl mod yum install mod_ssl -y # create directory for certificates and import them mkdir /etc/httpd/ssl openssl dhparam -out /etc/httpd/ssl/dhparam.pem 4096 # generate dhparam mv awc.key /etc/httpd/ssl/ # import website's private key mv awc.crt /etc/httpd/ssl/ # import website's certificate mv intermediate.crt /etc/httpd/ssl/ # import intermediate/ issuer certificate # remove read access on certs for non root users chmod 660 /etc/httpd/ssl/* # allow httpd to read it semanage fcontext -a -t httpd_sys_content_t "/etc/httpd/ssl(/.*)?" restorecon -Rv "/etc/httpd/ssl" # update httpd.conf echo "Include conf/httpd-ssl.conf" >> /etc/httpd/conf/httpd.conf nano /etc/httpd/conf/httpd-ssl.conf # add 1st content below # update website confog nano /etc/httpd/conf.d/awc.conf # Change file to 2nd content bellow # restart webserver systemctl restart httpd exit
SSLCipherSuite EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH SSLProtocol All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1 SSLHonorCipherOrder On Header always set X-Frame-Options DENY Header always set X-Content-Type-Options nosniff SSLCompression off SSLUseStapling on SSLStaplingCache "shmcb:logs/stapling-cache(150000)" SSLSessionTickets Off SSLOpenSSLConfCmd DHParameters "/etc/httpd/ssl/dhparam.pem"<VirtualHost *:80> ServerName <YOUR DOMAIN NAME> ServerAlias <YOUR ALIAS DOMAIN NAME> Redirect permanent / https://<YOUR DOMAIN NAME>/ </virtualhost> <VirtualHost *:443> ProxyPreserveHost On ProxyPass / http://localhost:8080/ ProxyPassReverse / http://localhost:8080/ Timeout 5400 ProxyTimeout 5400 ServerName <YOUR DOMAIN NAME> ServerAlias <YOUR ALIAS DOMAIN NAME> SSLEngine on SSLCertificateFile "/etc/httpd/ssl/awc.crt" SSLCertificateKeyFile "/etc/httpd/ssl/awc.key" SSLCertificateChainFile "/etc/httpd/ssl/intermediate.crt" <Proxy *> Order deny,allow Allow from all Authtype Basic Authname "Password Required" AuthUserFile /etc/httpd/.htpasswd Require valid-user </Proxy> </virtualhost>
If you feel uncomfortable allowing a user to use root-privileges without entering a password, you can use the following commands on the server with Ansible installed instead of performing "echo "ansible ALL=(ALL:ALL) NOPASSWD:ALL" >> /etc/sudoers" on each node.
The password for the ansible-user on the nodes will be stored encrypted on the server and will be read and send to the node by Ansible when it is running a playbook on the node.
cd /home/ansible
mkdir vars
cd vars
head -c 250 /dev/random | base64 | head -c 250 > vault.pass # generate random password
# create vault using the password file
ansible-vault create vault.yml --vault-password-file vault.pass
# file will be opened in vi-editor
# tap i to get into insert mode and add the content below
# tap ESC (to stop editing), type :w and hit enter (to save) and type :q and hit enter (to quit)ansible_port: 12345 # you can also specify your custom-ssh-port here once instead in each playbook
ansible_sudo_pass: "YOUR PASSWORD FOR THE ANSIBLE USER ON THE MANAGED NODES"You can edit the contents of your vault at any time using ansible-vault edit vault.yml --vault-password-file vault.pass. If you sync your playbooks via git, you should consider adding vault.pass to your .gitignore-file so not everybody can read your passwords.
To use the variables in your vault you have to include it in every playbook, using the following code before defining your tasks. Furthermore, you have to call the playbook with an additional argument containing the path to your password-file (e.g. ansible-playbook create-testfile.yml --vault-password-file ../vars/vault.pass -i ../inventory/hosts).
vars_files:
- ../vars/vault.yml
tasks:
- [...]This application is highly customizable. You can (almost) the complete GUI without recompiling the code. Moreover, you can define variables for your tasks which will be prompted on the webserver. This section will only cover how to change the settings and how to define tasks.
A default configuration is shipped with the binary. It's located here.
- port
- Specifies the port the application will listen on (default: ":8080")
- DO NOT forget the colon (:)
- logdir
- Directory the application will search for logs and create the files containing executed commands' outputs
- The user running the application has to have READ and WRITE permission to this directory
A default configuration, including two tasks and utilizing the playbooks created above, is shipped with the binary. It's located here.
- Each task will be defined between
<task> [...] </task>nested inside the outerxmlcontainer - Each task MUST have a
nameandcommandcontainer- name
- Name of the task shown on the web page (so the user will know what it does)
- command
- The command that will be executed when the task is run from the webpage
- You can use variables as described below (see var)
- The assembled command will be executed inside a bash:
bash -c '<COMMAND> > <LOGFILE>'- All results will be written into the log file and can be accessed via the web interface.
- You can add a
formcontainer if user inputs are required - You can omit the
formcontainer BUT you MUST NOT add any container beside these three (otherwise the task will be ignored)
- name
- The
formcontainer must have at least oneinputcontainer, but may have multiple- Each
inputcontainer represents a variable you can use in the command and will generate an html-input on the web page
- Each
- Each
inputcontainer MUST have atype,labelandvarcontainer- type
- Specifies the type of input that will be displayed on the web page
- Options are
- text
- Requests a plain text input
- password
- Like a normal text, but the input is ***-out on the web page
- checkbox
- The user may check the box
- Is the box checked, the value will be
on. Otherwise, there is no value transmitted and??<VAR-name>??will be used in the command
- dropdown
- Creates a drop-down list the user can select an item from
- You have to specify the available items in an
optionscontainer: Its nested insideinput, too- Options are semicolon (;) seperated: e.g.
ubuntu;centos;all - This only applies when having an input with
type=dropdown- otherwise DO NOT add anoptionscontainer
- Options are semicolon (;) seperated: e.g.
- text
- label
- A text that will be displayed in front of the input to explain the user which information he has to provide on the web page
- var
- Name the input will have
- ONLY USE alphanumeric letters, DO NOT use spaces
- The name must be UNIQUE inside one
formcontainer- In the best case it will be unique throughout all tasks, but that's not mandatory
- The command can use the var-name as spacer: The spacer (
??<VAR>??) will be replaced with the html form's user input- e.g.
[...] --extra-vars "path=??path?? content=??content?? targets=??hosts??"- The task will have two inputs: one with
var=pathand a second withvar=content
- The task will have two inputs: one with
- It is useless to define an input if the var is not used inside the command
- e.g.
- Name the input will have
- type
This application is designed to be used by a human operating on the webpage. Nevertheless, tasks can be executed and the configuration can be reloaded through API requests.
- Execute a task
http[s]://<YOUR DOMAIN NAME>/run?id=<TASK ID>[&<VAR>=<VALUE>]*- Tasks are executed via the
/runURL- You have to deliver at least the id
- The tasks in
commands.xmlget their IDs from top to button starting with 0 - The first task has id 0, the second id 1 and so on
- The tasks in
- If your task has inputs you have to deliver values to them
- Each value is delivered starting with a
&followed by the string specified in thevarcontainer an=and the value you want to assign to the variable - Make sure to supply all inputs - The API will NOT validate your data
- Each value is delivered starting with a
- You have to deliver at least the id
- Reload Configuration
http[s]://<YOUR DOMAIN NAME>/refresh- This will apply changes in
settings.xml(only logdir) andcommands.xml - A changed port can only be applied by restarting the service
Remember to supply your credentials when using the reverse proxy with HTTP Basic Authentication.
After a new release you can replace the binary (awc) and html-files with the new binary and html-files. I am trying to ensure that everything around it (logs' directory, configuration files and service) will be compatible.
When using SELinux you have to relabel the binary after an upgrade using sudo restorecon -v /home/ansible/awc/awc!
You like my application? I would be thankful for a small donation to finance my infrastructure (or the next cup of coffee).
Donate via "Buy Me A Coffee"
This project has legally nothing to do with Red Hat's projects Ansible, AWX or Automation controller nor uses any of its source code. These projects, products and names belong to Red Hat Inc..
This project was designed to remotely execute Ansible Playbooks which is represented in the name.
But it can also be used to execute any other kind of script and application.
This application is published undes GNU GNU GENERAL PUBLIC LICENSE Version 3 as refered in the LICENSE-File.
Copyright (C) 2022 0Raptor
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program. If not, see www.gnu.org/licenses/.