Deployment guide

ARA Records Ansible 1.8.0

Overview

ARA Records Ansible (ARA) records your Ansible playbook runs. Its callback plugin sends every playbook, play, task, host and result to an API server as the run happens. ARA's web interface then lets you browse, search and compare those runs. You can see which playbook ran, from which controller and as which user, on which hosts, what changed and what failed, and how long each task took. You keep running ansible-playbook as you do today, and ARA builds up the history.

This guide covers launching and using the ARA Records Ansible 1.8.0 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. The AMI is one version of the SecureAnyCloud Infrastructure as Code and Config Management listing on AWS Marketplace. You pick it as the software version when you subscribe and launch.

A fresh upstream ARA server lets anyone who can reach it read every recorded run and write new ones. It also ships Django's admin site and serves plain HTTP. The SecureAnyCloud image changes that.

What you get:

  • HTTPS from the first boot. nginx serves ARA on port 443 with a certificate generated on your instance's first boot for its public address, public DNS name and private address. Port 80 redirects to HTTPS. The certificate's SHA-256 fingerprint is written to the credentials file, so you can check it the first time you connect.
  • A login for everything. Every page of the reporting interface and every read and write through the REST API needs an ARA login (HTTP basic authentication against ARA's own user table). Without one, ARA answers 401 and shows nothing.
  • One administrator, unique to your instance. First boot creates a single user, admin, with a random 24-character password, a fresh Django secret key and an empty database. The image ships no password, no password hash, no secret key and no data. The password is written to /root/ara-credentials.txt (root only).
  • Closed by default. Django's admin site and the health check app are not published. You manage users with a root command on the instance (ara-user, Step 10). Requests are rate limited per client address.
  • Ready to record. ansible-core 2.21.6 and ARA's callback are installed in the same environment. /etc/ara/ansible.cfg already points the callback at this instance over HTTPS, verified against its own certificate. Ansible controllers elsewhere record to it with the same login (Step 8).
  • Hardened. The ARA application server (gunicorn) listens on 127.0.0.1 only, and only root, the ara service account and nginx may connect to it. ARA runs as an unprivileged account that can write only to its database directory. ARA and nginx are blocked from the instance metadata service and from private addresses in your VPC. nginx drops requests for host names the instance does not serve, and the AMI requires IMDSv2.
  • Verified packages. ARA, ansible-core and every Python dependency are installed from a lock file that pins each package to a SHA-256 hash, as binary wheels only. The image is fully patched Ubuntu 24.04 LTS with unattended security upgrades on.

What is on the image: ARA Records Ansible 1.8.0 with ansible-core 2.21.6 in /opt/ara/venv (Python 3.12), a SQLite database in /var/lib/ara/server, nginx 1.24 from the Ubuntu archive, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu. Architecture: x86_64. Recommended instance type: m5.large.

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
  • A security group that allows TCP 22 (SSH), TCP 443 (ARA) and TCP 80 (redirects to HTTPS). Open 22 from your own networks only. Open 443 to the people who read the reports and to the Ansible controllers that record to ARA.
  • Port 8000 is ARA's internal application server. Never open it.
1

Launch the instance

In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick ARA Records Ansible 1.8.0 on Ubuntu 24.04 as the software version and choose your Region. Then choose Launch through EC2. Choose m5.large, your key pair and a security group as above.

To launch with the AWS CLI instead, run the following command and replace the values in angle brackets:

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=ara}]'
2

Connect to your instance

Connect over SSH with your key pair and the login user for the version you launched.

Version SSH user

ARA Records Ansible 1.8.0 on Ubuntu 24.04 ubuntu

ssh -i <your-key.pem> ubuntu@<public-ip>

The login message shows the ARA address, the user name and where to find the password:

ARA Records Ansible 1.8.0 (sac): https://<public-ip>/ user admin, password:
sudo cat /root/ara-credentials.txt Record a playbook here: ANSIBLE_CONFIG=/etc/ara/ansible.cfg (see the guide) Self-test:
sudo ara-selftest Guide: https://secureanycloud.com/stacks/ara-records-ansible-1-8-0/
3

Check the services

First boot creates the database, the administrator, the secret key and the certificate. Then ARA and nginx start. ara-firstboot runs once and then shows as inactive. Check the units and the first-boot marker:

systemctl is-active nginx ara-web ara-firstboot
sudo test -f /var/lib/sac/data/ara-firstboot.done && echo FIRSTBOOT_DONE

The expected output is:

active active inactive FIRSTBOOT_DONE

Only SSH and nginx listen on the network. ARA's application server (gunicorn) listens on 127.0.0.1:8000. List the listening sockets:

sudo ss -lntp | awk 'NR>1 {print $4, $6}' | sed -E 's/pid=[0-9]+,fd=[0-9]+/.../g' | sort

The output looks similar to this:

0.0.0.0:22 users:(("sshd",...),("systemd",...)) 0.0.0.0:443 users:(("nginx",...),("nginx",...),("nginx",...)) 0.0.0.0:80 users:(("nginx",...),("nginx",...),("nginx",...)) 127.0.0.1:8000 users:(("gunicorn",...),("gunicorn",...),("gunicorn",...),("gunicorn",...)) 127.0.0.53%lo:53 users:(("systemd-resolve",...)) 127.0.0.54:53 users:(("systemd-resolve",...)) [::]:22 users:(("sshd",...),("systemd",...)) [::]:443 users:(("nginx",...),("nginx",...),("nginx",...)) [::]:80 users:(("nginx",...),("nginx",...),("nginx",...))
4

Get your URL and password

Only root can read the credentials file. This command prints everything in it except the password line:

sudo grep -v '^ARA_PASSWORD=' /root/ara-credentials.txt

The output looks similar to this:

# ARA Records Ansible 1.8.0 (sac) on this instance. Open ARA_URL in a browser and sign in as ARA_USERNAME / ARA_PASSWORD.
# Give the same login to Ansible (api_username / api_password in the [ara] section of ansible.cfg) to record playbooks.
# The password is unique to this instance. Change it:
sudo ara-set-password (or --stdin to choose one). More users:
sudo ara-user.
# The certificate is self-signed for this instance (/etc/ara/ara-instance.crt): compare its SHA-256 fingerprint with ARA_TLS_SHA256. ARA_URL=https://<public-ip>/ ARA_HOST=<public-ip> ARA_USERNAME=admin ARA_TLS_SHA256=<ARA_TLS_SHA256> ARA_ADDRESS_SOURCE=imdsv2-public-ipv4 GUIDE=https://secureanycloud.com/stacks/ara-records-ansible-1-8-0/

Print the password itself only when you are ready to sign in: sudo sed -n 's/^ARA_PASSWORD=//p' /root/ara-credentials.txt

ARA_URL uses the instance's public IPv4 address, which the instance reads from instance metadata on first boot. An instance without a public address uses its private address. To serve ARA under your own DNS name, see Step 11.

5

Check the certificate and sign in

The certificate is self-signed, so your browser asks you to confirm it the first time. Before you accept it, compare its SHA-256 fingerprint (in the browser's certificate viewer) with ARA_TLS_SHA256 from the credentials file. From your workstation, you can read the fingerprint with OpenSSL:

echo | openssl s_client -connect <public-ip>:443 2>/dev/null | openssl x509 -noout -fingerprint -sha256

On the instance itself, the same check reads the fingerprint that nginx serves:

H=$(sudo sed -n 's/^ARA_HOST=//p' /root/ara-credentials.txt)
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -fingerprint -sha256 sudo grep '^ARA_TLS_SHA256=' /root/ara-credentials.txt

The two fingerprints are the same:

sha256 Fingerprint=<ARA_TLS_SHA256> ARA_TLS_SHA256=<ARA_TLS_SHA256>

Open ARA_URL in your browser. It asks for a user name and password. Enter admin and the password from the credentials file.

ARA opens on the Playbooks page. The page lists every recorded run with its status, date, duration, name or path, Ansible version, controller, user, and its host, task and result counts. A new instance shows the run that sudo ara-selftest records (Step 6) once you have run it:

6

Record a playbook run on the instance

The image ships a self-test that proves the whole path end to end over HTTPS. Without a login, and with a wrong password, every page and the API answer 401. Django's admin and the health check app are not served.

The self-test then runs a small demo playbook, /opt/ara/demo/record-demo.yml, through ARA's callback with this instance's password. It reads the new run back through the API and the reporting interface. It never prints the password. Run it:

sudo ara-selftest

The output looks similar to this:

ara-selftest: 10 paths x (no credential, wrong password) -> 401, an anonymous write -> 401, /admin and /healthcheck -> 404; Ansible recorded playbook <n> (5 results) and it reads back through the API and the UI ARA_SELFTEST_OK

To record your own playbooks on the instance, use the image's Ansible with ANSIBLE_CONFIG=/etc/ara/ansible.cfg, and give the callback the ARA login. This example records the same demo playbook:

export ANSIBLE_CONFIG=/etc/ara/ansible.cfg ARA_API_USERNAME=admin ARA_API_PASSWORD="$(sudo sed -n 's/^ARA_PASSWORD=//p' /root/ara-credentials.txt)";
export ARA_API_PASSWORD /opt/ara/venv/bin/ansible-playbook /opt/ara/demo/record-demo.yml

The run ends with a recap similar to this:

... PLAY RECAP ********************************************************************* localhost : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Replace the demo with your own playbook and inventory, for example /opt/ara/venv/bin/ansible-playbook -i inventory.ini site.yml.

If you prefer not to keep the password in your shell, copy /etc/ara/ansible.cfg to a private file (mode 0600). Add api_username = admin and api_password = ... to its [ara] section. Then point ANSIBLE_CONFIG at that copy.

Two playbook variables label a run in ARA. ara_playbook_name gives the run a readable name instead of its path, and ara_playbook_labels adds labels that you can filter on:

- name: Configure web tier hosts: web vars: ara_playbook_name: Deploy web and database tiers ara_playbook_labels: [production, deploy] tasks: ...

ARA's command-line client reads the same records through the API:

export ARA_API_CLIENT=http ARA_API_SERVER=https://localhost ARA_API_CA=/etc/ara/ara-instance.crt ARA_API_USERNAME=admin ARA_API_PASSWORD="$(sudo sed -n 's/^ARA_PASSWORD=//p' /root/ara-credentials.txt)";
export ARA_API_PASSWORD /opt/ara/venv/bin/ara playbook list --limit 3 -c id -c status -c path -c duration

The output looks similar to this:

+----+-----------+-------------------------------+-----------------+ | id | status | path | duration | +----+-----------+-------------------------------+-----------------+ | 11 | completed | /opt/ara/demo/record-demo.yml | 00:00:20.545874 | | 10 | completed | /opt/ara/demo/record-demo.yml | 00:00:21.491273 | | 9 | completed | /home/ubuntu/infra/patch.yml | 00:00:25.895798 | +----+-----------+-------------------------------+-----------------+
7

Explore a recorded run

Select a playbook to open its report. The report shows when and where the run happened, the Ansible, ARA and Python versions, and a summary of hosts, tasks, results, plays and files. It also shows each host with its ok, changed and failed counts, the playbook files, and every task result below:

Hosts lists each host with the result counts from its latest run. It links to the host's facts and history:

Tasks lists every task across your runs with its action, name, file and line. Use it to find which task changed or failed, and in which playbook:

Each page has Search and filter for narrowing by name, status, controller, user, label or date. API opens ARA's browsable REST API (/api/v1/), which uses the same login.

8

Record from your own Ansible controllers

Any Ansible controller that can reach port 443 can record to this instance. On the controller, install the same ARA version. Copy the instance's certificate so that the callback can verify it:

python3 -m pip install --user "ara==1.8.0" scp -i <your-key.pem> ubuntu@<public-ip>:/etc/ara/ara-instance.crt ~/ara-instance.crt

Then enable the callback and point it at the instance with the ARA login:

export ANSIBLE_CALLBACK_PLUGINS="$(python3 -m ara.setup.callback_plugins)"
export ANSIBLE_CALLBACKS_ENABLED=ara_default
export ARA_API_CLIENT=http
export ARA_API_SERVER=https://<public-ip>
export ARA_API_CA=~/ara-instance.crt
export ARA_API_USERNAME=admin
export ARA_API_PASSWORD='<the ARA_PASSWORD from /root/ara-credentials.txt>' ansible-playbook -i inventory.ini site.yml

Each run appears on the Playbooks page as soon as it starts. For CI pipelines and shared controllers, give each controller its own ARA user (Step 10) instead of the administrator's password.

The certificate names the instance's public and private addresses and its public DNS name. If you use your own DNS name (Step 11), copy the new certificate to your controllers again.

9

Check the security posture from the command line

Port 80 redirects to HTTPS. The reporting interface and the API refuse requests without a valid login. Django's admin and the health check app are not served. A request for a host name that the instance does not serve is dropped without an answer.

/healthz is answered by nginx itself for load balancer health checks and reveals nothing about ARA. Run the following checks:

H=$(sudo sed -n 's/^ARA_HOST=//p' /root/ara-credentials.txt); C=(--cacert /etc/ara/ara-instance.crt --connect-to ::127.0.0.1:) curl -s -o /dev/null -w 'http:// -> %{http_code} %{redirect_url}\n' "${C[@]}" "http://$H/" curl -s -o /dev/null -w 'UI, no login -> %{http_code}\n' "${C[@]}" "https://$H/" curl -s -o /dev/null -w 'API, no login -> %{http_code}\n' "${C[@]}" "https://$H/api/v1/playbooks" curl -s -o /dev/null -w 'API, bad password -> %{http_code}\n' "${C[@]}" -K <(printf 'user = "admin:%s"\n' "$(openssl rand -hex 12)") "https://$H/api/v1/playbooks" curl -s -o /dev/null -w 'API write, no login-> %{http_code}\n' "${C[@]}" -X POST -H 'Content-Type: application/json' -d '{}' "https://$H/api/v1/playbooks" curl -s -o /dev/null -w 'Django admin -> %{http_code}\n' "${C[@]}" "https://$H/admin/" curl -s -o /dev/null -w 'health check app -> %{http_code}\n' "${C[@]}" "https://$H/healthcheck/" curl -s -o /dev/null -w 'nginx /healthz -> %{http_code}\n' "${C[@]}" "https://$H/healthz" curl -sk -o /dev/null -w 'unknown host -> %{http_code}\n' --resolve evil.example:443:127.0.0.1 https://evil.example/ || true

The expected output is:

http:// -> 301 https://<public-ip>/ UI, no login -> 401 API, no login -> 401 API, bad password -> 401 API write, no login-> 401 Django admin -> 404 health check app -> 404 nginx /healthz -> 200 unknown host -> 000

000 means nginx closed the connection without answering, which is how it treats any host name it does not serve.

10

Manage ARA users

Manage ARA's users with ara-user as root. Names and passwords are passed on standard input or generated, never on a command line. To list the users:

sudo ara-user list

The output looks similar to this:

admin active last-login=never

You can add a user, for example one per Ansible controller or CI pipeline. Use a generated password that is shown once, or a password of your own of 16 characters or more. You can also remove a user:

sudo ara-user set ci-pipeline printf '%s\n' '<new-password>' | sudo ara-user set alice --stdin sudo ara-user delete alice

Every ARA user can read every recorded run and record new ones. ARA has no per-user permissions.

To change the administrator's password, either generate a new random one (written to /root/ara-credentials.txt) or set your own (not stored). The change takes effect at once. Update ARA_API_PASSWORD or api_password on every controller that records as admin:

# a new random password
sudo ara-set-password
# or your own printf '%s\n' '<new-password>' | sudo ara-set-password --stdin
11

Your own DNS name and certificate

Point a DNS record at the instance. An Elastic IP keeps the address stable across stop and start. Then run ara-set-domain with the name. The command sets nginx's server names and ARA's allowed hosts. It issues a new self-signed certificate for that name, plus any extra names or IPv4 addresses you list, localhost and 127.0.0.1:

sudo ara-set-domain <your-domain>
sudo grep -E '^ARA_(URL|HOST|TLS_SHA256)=' /root/ara-credentials.txt

Copy the new /etc/ara/ara-instance.crt to your Ansible controllers (Step 8). If you do not use your own name, run sudo ara-set-domain with the new address after a stop and start changes the instance's public IP.

To serve ARA with a certificate that your browsers and controllers already trust, put an Application Load Balancer with an AWS Certificate Manager certificate in front of the instance. Forward HTTPS to the instance on port 443 and use /healthz as the health check path.

The load balancer passes your name on to the instance, and its health checks use the instance's private address. Serve both names with sudo ara-set-domain <your-domain> <private-ip>. nginx drops any other host name, health checks included.

12

Reach a private network from ARA

ARA only receives data, so it never needs to connect to anything inside your VPC. The ara and nginx accounts are blocked from private addresses, carrier-grade NAT addresses, your VPC's own address ranges and the instance metadata service.

If something you run as the ara account must reach a private host, add its network to the allow list and reload the rule:

echo '<private-ip>/32' | sudo tee -a /etc/sac/ara-egress-allow
sudo systemctl reload sac-ara-egress

The metadata service and link-local addresses can never be allowed.

13

Data, backups and server components

Check the component versions on the image:

/opt/ara/venv/bin/python -c "from ara.setup import ara_version as v; print('ARA', v)" /opt/ara/venv/bin/ansible --version 2>/dev/null | head -1 /opt/ara/venv/bin/python -V nginx -v 2>&1

The output looks similar to this:

ARA 1.8.0 ansible [core 2.21.6] Python 3.12.3 nginx version: nginx/1.24.0 (Ubuntu)

Everything ARA records is in one SQLite database, /var/lib/ara/server/ansible.sqlite. To back it up, stop ARA for a moment, copy the file and start ARA again. Alternatively, take a snapshot of the root volume:

sudo systemctl stop ara-web
sudo install -m 0600 /var/lib/ara/server/ansible.sqlite <backup-dir>/ara-$(date +%F).sqlite
sudo systemctl start ara-web

To restore into a new instance from this image, stop ARA. Copy the backup over the database with the same owner and mode, then start ARA:

sudo systemctl stop ara-web
sudo install -o ara -g ara -m 0600 <backup-dir>/ara-<date>.sqlite /var/lib/ara/server/ansible.sqlite
sudo systemctl start ara-web

The restored database keeps its users and passwords. The administrator from the old instance signs in, not the one in the new credentials file.

The image places the main components at these paths.

Component Path

ARA, ansible-core and their Python packages (root-owned) /opt/ara/venv

Database (ARA's only writable path) /var/lib/ara/server/ansible.sqlite (ara:ara 0600)

ARA server settings (rendered by ara-render-settings; do not edit) /etc/ara/server/settings.yaml

Ansible configuration for recording on the instance /etc/ara/ansible.cfg

Instance certificate (public copy) /etc/ara/ara-instance.crt

Demo playbook /opt/ara/demo/record-demo.yml

nginx site /etc/nginx/sites-available/ara

Licences, third party notices, source note, lock file /usr/share/doc/ara/

Helpers /usr/local/sbin/ara-user, ara-set-password, ara-set-domain, ara-selftest

Services ara-web (gunicorn), nginx, ara-firstboot (first boot only), sac-ara-egress

Credentials /root/ara-credentials.txt (mode 0600)

Logs. ARA and gunicorn log to the journal (sudo journalctl -u ara-web). nginx's access log records request paths without query strings.

Recorded data. ARA stores what Ansible reports: task arguments, results and, unless you turn it off, host facts. Mark tasks that handle secrets with no_log: true. See ARA's documentation for the callback options that ignore facts or arguments.

14

Security recommendations

  • Allow port 22 in the security group from your own networks only, and allow port 443 only to the people and controllers that use ARA. Never open port 8000.
  • Check the certificate fingerprint the first time you connect (Step 5), then use your own DNS name (Step 11).
  • Give each controller and pipeline its own ARA user (Step 10). Change the administrator's password if you shared it (Step 10).
  • Use no_log: true on tasks that handle secrets, so their values are never sent to ARA.
  • Keep the operating system patched. Unattended security upgrades are on, and sudo apt-get update && sudo apt-get upgrade applies everything else.

Support and licensing

ARA Records Ansible is open source under the GNU General Public License v3.0 or later, and so is ansible-core. There is no per-user or per-CPU licence fee. The licences, the third-party notices, a note on where the source comes from and the lock file are in /usr/share/doc/ara/.

SecureAnyCloud is not affiliated with or endorsed by the ARA project or Red Hat. Ansible is a registered trademark of Red Hat, Inc. The AMI price covers SecureAnyCloud's hardening, first-boot setup and support.

  • Email: sales@secureanycloud.com
  • Website: https://secureanycloud.com
  • Support: 24/7, with a 24 hour response SLA

Deploy on AWS

Launch ARA Records Ansible 1.8.0 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS