Configure AWX to securely retrieve database credentials and API keys from HashiCorp Vault using dynamic secrets that automatically rotate. Set up credential types, database engines, and secure playbook execution with centralized secrets management.
Prerequisites
- AWX 24.6 installed and running
- PostgreSQL database server
- Network connectivity between AWX and Vault
- Basic understanding of Ansible playbooks
What this solves
AWX automation workflows often need database passwords, API keys, and certificates that change regularly. Hardcoding these secrets in playbooks or storing them in AWX credentials creates security risks and maintenance overhead. This integration allows AWX to dynamically fetch secrets from Vault at runtime, with automatic rotation and centralized policy management.
Prerequisites
You need AWX 24.6 running with PostgreSQL backend and HashiCorp Vault server accessible from your AWX instance. The AWX user must have network connectivity to your Vault server on the configured port (usually 8200).
Step-by-step configuration
Install and configure HashiCorp Vault
Set up Vault server with the database secrets engine for dynamic credential generation.
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add -
sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main"
sudo apt update && sudo apt install -y vault
Configure Vault server
Create the Vault configuration file with API listener and file storage backend for development.
storage "file" {
path = "/opt/vault/data"
}
listener "tcp" {
address = "0.0.0.0:8200"
tls_disable = 1
}
api_addr = "http://127.0.0.1:8200"
cluster_addr = "https://127.0.0.1:8201"
ui = true
disable_mlock = true
Initialize and start Vault
Create the data directory, start Vault service, and initialize the Vault cluster.
sudo mkdir -p /opt/vault/data
sudo chown -R vault:vault /opt/vault
sudo systemctl enable --now vault
sudo systemctl status vault
Initialize Vault and capture the unseal keys and root token.
export VAULT_ADDR='http://127.0.0.1:8200'
vault operator init -key-shares=5 -key-threshold=3
Unseal Vault and enable database engine
Unseal Vault using three of the five keys, then authenticate with the root token.
# Unseal with 3 different keys (replace with your actual keys)
vault operator unseal
vault operator unseal
vault operator unseal
Login with root token
vault auth
Enable database secrets engine
vault secrets enable database
Configure PostgreSQL database connection
Set up Vault to connect to PostgreSQL and generate dynamic credentials.
# Configure database connection (adjust connection details)
vault write database/config/postgresql-db \
plugin_name=postgresql-database-plugin \
connection_url="postgresql://{{username}}:{{password}}@localhost:5432/postgres?sslmode=disable" \
allowed_roles="awx-role" \
username="vault_admin" \
password="secure_password123!"
Create role for AWX with limited permissions
vault write database/roles/awx-role \
db_name=postgresql-db \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";"\
default_ttl="1h" \
max_ttl="24h"
Create Vault policy for AWX
Define access policy that allows AWX to read database credentials and KV secrets.
path "database/creds/awx-role" {
capabilities = ["read"]
}
path "secret/data/awx/*" {
capabilities = ["read", "list"]
}
path "auth/token/lookup-self" {
capabilities = ["read"]
}
path "auth/token/renew-self" {
capabilities = ["update"]
}
# Apply the policy
vault policy write awx-policy /tmp/awx-policy.hcl
Create token for AWX with specific policy
vault token create -policy=awx-policy -ttl=8760h
Enable KV secrets engine for static secrets
Configure Key-Value store for API keys and certificates that AWX playbooks need.
# Enable KV v2 secrets engine
vault secrets enable -path=secret kv-v2
Store sample API keys and certificates
vault kv put secret/awx/api-keys \
github_token="ghp_example123456789" \
slack_webhook="https://hooks.slack.com/services/example"
vault kv put secret/awx/certificates \
ssl_cert="-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----" \
ssl_key="-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----"
Install AWX Vault collection
Add the HashiCorp Vault collection to AWX for Vault integration capabilities.
# On AWX server, install collection in virtual environment
sudo su - awx
source /var/lib/awx/venv/awx/bin/activate
ansible-galaxy collection install community.hashi_vault
Create Vault credential type in AWX
Define custom credential type in AWX for HashiCorp Vault authentication.
fields:
- id: vault_url
type: string
label: Vault URL
help_text: "HashiCorp Vault server URL (e.g., http://vault.example.com:8200)"
- id: vault_token
type: string
label: Vault Token
secret: true
help_text: "Vault authentication token with appropriate policies"
- id: vault_namespace
type: string
label: Vault Namespace (Optional)
help_text: "Vault namespace for Vault Enterprise (leave empty for OSS)"
required: false
required:
- vault_url
- vault_token
env:
VAULT_ADDR: '{{ vault_url }}'
VAULT_TOKEN: '{{ vault_token }}'
VAULT_NAMESPACE: '{{ vault_namespace }}'
extra_vars:
vault_url: '{{ vault_url }}'
vault_token: '{{ vault_token }}'
Create Vault credential in AWX
Set up the actual credential using the custom Vault credential type.
In AWX web interface, navigate to Resources > Credentials and create new credential:
- Name: HashiCorp Vault
- Credential Type: HashiCorp Vault (custom type created above)
- Vault URL: http://127.0.0.1:8200
- Vault Token: (paste the token created earlier)
Create sample playbook with dynamic secrets
Create an Ansible playbook that retrieves dynamic database credentials from Vault.
---
- name: Example playbook using Vault dynamic database secrets
hosts: localhost
connection: local
gather_facts: false
vars:
vault_url: "{{ ansible_env.VAULT_ADDR }}"
vault_token: "{{ ansible_env.VAULT_TOKEN }}"
tasks:
- name: Get dynamic PostgreSQL credentials from Vault
community.hashi_vault.vault_read:
url: "{{ vault_url }}"
token: "{{ vault_token }}"
path: database/creds/awx-role
register: db_creds
no_log: true
- name: Display database username (password hidden)
debug:
msg: "Database username: {{ db_creds.data.username }}"
- name: Connect to database with dynamic credentials
postgresql_query:
login_host: localhost
login_user: "{{ db_creds.data.username }}"
login_password: "{{ db_creds.data.password }}"
db: postgres
query: "SELECT version();"
register: db_version
- name: Show database version
debug:
msg: "PostgreSQL version: {{ db_version.query_result[0].version }}"
- name: Get API keys from Vault KV store
community.hashi_vault.vault_kv2_get:
url: "{{ vault_url }}"
token: "{{ vault_token }}"
path: awx/api-keys
register: api_secrets
no_log: true
- name: Use GitHub API token (example)
uri:
url: "https://api.github.com/user"
method: GET
headers:
Authorization: "token {{ api_secrets.data.data.github_token }}"
register: github_response
ignore_errors: true
- name: Show API response
debug:
msg: "GitHub API status: {{ github_response.status | default('Failed') }}"
Create job template in AWX
Set up AWX job template to run the Vault-integrated playbook with proper credentials.
In AWX web interface, create new Job Template:
- Name: Vault Database Example
- Inventory: Demo Inventory
- Project: (your project containing the playbook)
- Playbook: vault-database-example.yml
- Credentials: HashiCorp Vault (the credential created earlier)
- Verbosity: 1 (Normal)
Configure automatic secret rotation
Set up shorter TTL values for more frequent credential rotation in production.
# Update role with shorter TTL for production security
vault write database/roles/awx-role \
db_name=postgresql-db \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";"\
default_ttl="30m" \
max_ttl="2h"
Create scheduled job template for credential refresh
This ensures AWX gets fresh credentials before expiration
Set up Vault audit logging
Enable audit logging to track secret access from AWX for security compliance.
# Enable file audit backend
sudo mkdir -p /var/log/vault
sudo chown vault:vault /var/log/vault
vault audit enable file file_path=/var/log/vault/audit.log
Enable syslog audit for centralized logging
vault audit enable syslog
Configure advanced Vault integration
Set up Vault Agent for token renewal
Configure Vault Agent on AWX server to automatically renew tokens and cache secrets.
vault {
address = "http://127.0.0.1:8200"
}
auto_auth {
method "token_file" {
config = {
token_file_path = "/opt/vault-agent/token"
}
}
sink "file" {
config = {
path = "/opt/vault-agent/sink"
}
}
}
cache {
use_auto_auth_token = true
}
listener "tcp" {
address = "127.0.0.1:8100"
tls_disable = true
}
template {
source = "/opt/vault-agent/db-creds.tpl"
destination = "/opt/vault-agent/db-creds.json"
command = "systemctl reload awx"
}
Create database credential template
Set up template for automatic credential file generation with rotation.
{{- with secret "database/creds/awx-role" -}}
{
"username": "{{ .Data.username }}",
"password": "{{ .Data.password }}",
"generated_at": "{{ now.Format "2006-01-02T15:04:05Z" }}"
}
{{- end -}}
Verify your setup
Test the complete integration to ensure AWX can retrieve secrets from Vault successfully.
# Check Vault status
vault status
Verify database role works
vault read database/creds/awx-role
Test KV secret retrieval
vault kv get secret/awx/api-keys
Check AWX can connect to Vault
curl -H "X-Vault-Token: $VAULT_TOKEN" \
-X GET \
http://127.0.0.1:8200/v1/sys/health
Run the AWX job template and verify it completes without credential errors:
# Check AWX job output in web interface or via API
failed_when: "'FAILED' in job_output or 'ERROR' in job_output"
success_when: "'PLAY RECAP' in job_output and 'failed=0' in job_output"
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| AWX job fails with "connection refused" | Vault server not accessible | Check firewall rules: sudo ufw allow 8200 |
| "permission denied" reading secrets | Insufficient Vault policy permissions | Update policy with required paths: vault policy write awx-policy /path/to/policy.hcl |
| Database credentials don't work | PostgreSQL user creation failed | Check PostgreSQL logs and verify vault_admin user has CREATEROLE privilege |
| Vault token expires during job | Token TTL too short | Create token with longer TTL: vault token create -policy=awx-policy -ttl=168h |
| Collection not found error | HashiVault collection not installed | Install in AWX venv: ansible-galaxy collection install community.hashi_vault |
| SSL verification errors | Self-signed certificates | Add validate_certs: false to vault tasks or configure proper CA |
Production hardening
For production deployments, consider using Vault PKI backend for automatic certificate management and auto-unseal with cloud KMS for enhanced security.
Next steps
- Configure AWX backup and disaster recovery procedures
- Advanced Vault dynamic secrets configuration
- Scale AWX for enterprise automation workflows
- Integrate AWX with LDAP authentication
- Set up Vault HA cluster with Consul backend
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# AWX 24.6 + HashiCorp Vault Integration Install Script
# Production-ready installation with multi-distro support
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Default values
VAULT_ADDR=${1:-"127.0.0.1"}
VAULT_PORT=${2:-"8200"}
DB_HOST=${3:-"localhost"}
DB_NAME=${4:-"awx"}
DB_USER=${5:-"awx"}
# Usage message
usage() {
echo "Usage: $0 [VAULT_ADDR] [VAULT_PORT] [DB_HOST] [DB_NAME] [DB_USER]"
echo "Example: $0 127.0.0.1 8200 localhost awx awx"
exit 1
}
# Logging functions
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Error handling
cleanup() {
if [ $? -ne 0 ]; then
log_error "Installation failed. Cleaning up..."
systemctl stop vault 2>/dev/null || true
systemctl disable vault 2>/dev/null || true
rm -f /etc/vault.d/vault.hcl 2>/dev/null || true
fi
}
trap cleanup ERR
# Check if running as root or with sudo
check_privileges() {
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root or with sudo"
exit 1
fi
}
# Detect distribution and set package manager
detect_distro() {
if [ ! -f /etc/os-release ]; then
log_error "/etc/os-release not found. Cannot detect distribution."
exit 1
fi
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update"
DISTRO_FAMILY="debian"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf makecache"
DISTRO_FAMILY="rhel"
# Fallback to yum if dnf not available
if ! command -v dnf &> /dev/null; then
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum makecache"
fi
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum makecache"
DISTRO_FAMILY="rhel"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
log_info "Detected distribution: $ID ($DISTRO_FAMILY family)"
}
# Install HashiCorp Vault
install_vault() {
log_info "[1/8] Installing HashiCorp Vault..."
if [ "$DISTRO_FAMILY" = "debian" ]; then
curl -fsSL https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" > /etc/apt/sources.list.d/hashicorp.list
$PKG_UPDATE
$PKG_INSTALL vault
else
$PKG_INSTALL dnf-plugins-core 2>/dev/null || $PKG_INSTALL yum-utils
if [ "$PKG_MGR" = "dnf" ]; then
dnf config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
else
yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
fi
$PKG_INSTALL vault
fi
log_success "Vault installed successfully"
}
# Configure Vault
configure_vault() {
log_info "[2/8] Configuring Vault server..."
# Create vault user if not exists
if ! id vault &>/dev/null; then
useradd -r -d /opt/vault -s /bin/false vault
fi
# Create directories
mkdir -p /opt/vault/data /etc/vault.d
chown -R vault:vault /opt/vault
chmod 750 /opt/vault/data
# Create Vault configuration
cat > /etc/vault.d/vault.hcl << EOF
storage "file" {
path = "/opt/vault/data"
}
listener "tcp" {
address = "0.0.0.0:${VAULT_PORT}"
tls_disable = 1
}
api_addr = "http://${VAULT_ADDR}:${VAULT_PORT}"
cluster_addr = "https://${VAULT_ADDR}:8201"
ui = true
disable_mlock = true
EOF
chown vault:vault /etc/vault.d/vault.hcl
chmod 640 /etc/vault.d/vault.hcl
# Create systemd service
cat > /etc/systemd/system/vault.service << EOF
[Unit]
Description=HashiCorp Vault
Documentation=https://www.vaultproject.io/docs/
Requires=network-online.target
After=network-online.target
ConditionFileNotEmpty=/etc/vault.d/vault.hcl
[Service]
Type=notify
User=vault
Group=vault
ProtectSystem=full
ProtectHome=read-only
PrivateTmp=yes
PrivateDevices=yes
SecureBits=keep-caps
AmbientCapabilities=CAP_IPC_LOCK
NoNewPrivileges=yes
ExecStart=/usr/bin/vault server -config=/etc/vault.d/vault.hcl
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
StartLimitBurst=3
LimitNOFILE=65536
LimitMEMLOCK=infinity
[Install]
WantedBy=multi-user.target
EOF
log_success "Vault configured successfully"
}
# Start Vault service
start_vault() {
log_info "[3/8] Starting Vault service..."
systemctl daemon-reload
systemctl enable vault
systemctl start vault
# Wait for Vault to start
sleep 5
if systemctl is-active --quiet vault; then
log_success "Vault service started successfully"
else
log_error "Failed to start Vault service"
systemctl status vault
exit 1
fi
}
# Initialize Vault
initialize_vault() {
log_info "[4/8] Initializing Vault..."
export VAULT_ADDR="http://${VAULT_ADDR}:${VAULT_PORT}"
# Wait for Vault API to be ready
for i in {1..30}; do
if vault status &>/dev/null; then
break
fi
sleep 2
done
# Check if already initialized
if vault status | grep -q "Initialized.*true"; then
log_warning "Vault is already initialized"
return
fi
# Initialize Vault
INIT_OUTPUT=$(vault operator init -key-shares=5 -key-threshold=3 -format=json)
# Save keys and token securely
UNSEAL_KEYS=$(echo "$INIT_OUTPUT" | jq -r '.unseal_keys_b64[]')
ROOT_TOKEN=$(echo "$INIT_OUTPUT" | jq -r '.root_token')
# Create secure file for keys
cat > /etc/vault.d/vault-keys << EOF
# Vault Initialization Data - KEEP SECURE
ROOT_TOKEN=$ROOT_TOKEN
UNSEAL_KEYS=$(echo "$UNSEAL_KEYS" | tr '\n' ',' | sed 's/,$//')
EOF
chown vault:vault /etc/vault.d/vault-keys
chmod 600 /etc/vault.d/vault-keys
log_success "Vault initialized successfully"
log_warning "Keys saved to /etc/vault.d/vault-keys - KEEP THIS FILE SECURE!"
}
# Unseal Vault and configure
unseal_and_configure() {
log_info "[5/8] Unsealing Vault and enabling engines..."
export VAULT_ADDR="http://${VAULT_ADDR}:${VAULT_PORT}"
# Source the keys
. /etc/vault.d/vault-keys
# Unseal Vault with first 3 keys
IFS=',' read -ra KEYS <<< "$UNSEAL_KEYS"
for i in {0..2}; do
vault operator unseal "${KEYS[$i]}"
done
# Authenticate with root token
vault auth "$ROOT_TOKEN"
# Enable database secrets engine
vault secrets enable database 2>/dev/null || log_warning "Database engine already enabled"
# Enable KV v2 secrets engine
vault secrets enable -path=secret kv-v2 2>/dev/null || log_warning "KV engine already enabled"
log_success "Vault unsealed and engines enabled"
}
# Configure database connection
configure_database() {
log_info "[6/8] Configuring database connection..."
read -p "Enter database admin username: " -r DB_ADMIN_USER
read -s -p "Enter database admin password: " -r DB_ADMIN_PASS
echo
# Configure PostgreSQL connection
vault write database/config/postgresql-db \
plugin_name=postgresql-database-plugin \
connection_url="postgresql://{{username}}:{{password}}@${DB_HOST}:5432/${DB_NAME}?sslmode=disable" \
allowed_roles="awx-role" \
username="$DB_ADMIN_USER" \
password="$DB_ADMIN_PASS"
# Create AWX role
vault write database/roles/awx-role \
db_name=postgresql-db \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl="1h" \
max_ttl="24h"
log_success "Database connection configured"
}
# Create AWX policy and token
create_awx_policy() {
log_info "[7/8] Creating AWX policy and token..."
# Create AWX policy
cat > /tmp/awx-policy.hcl << EOF
path "database/creds/awx-role" {
capabilities = ["read"]
}
path "secret/data/awx/*" {
capabilities = ["read", "list"]
}
path "auth/token/lookup-self" {
capabilities = ["read"]
}
path "auth/token/renew-self" {
capabilities = ["update"]
}
EOF
vault policy write awx-policy /tmp/awx-policy.hcl
rm -f /tmp/awx-policy.hcl
# Create token for AWX
AWX_TOKEN=$(vault token create -policy=awx-policy -ttl=8760h -format=json | jq -r '.auth.client_token')
# Save AWX token securely
echo "AWX_VAULT_TOKEN=$AWX_TOKEN" >> /etc/vault.d/vault-keys
log_success "AWX policy and token created"
}
# Verification
verify_installation() {
log_info "[8/8] Verifying installation..."
# Check Vault status
if ! vault status | grep -q "Sealed.*false"; then
log_error "Vault is not unsealed"
exit 1
fi
# Test database credentials generation
if ! vault read database/creds/awx-role &>/dev/null; then
log_error "Cannot generate database credentials"
exit 1
fi
# Test KV engine
vault kv put secret/awx/test key=value
if ! vault kv get secret/awx/test | grep -q "value"; then
log_error "KV engine test failed"
exit 1
fi
vault kv delete secret/awx/test
log_success "Installation verified successfully"
echo
log_info "Vault is running on http://${VAULT_ADDR}:${VAULT_PORT}"
log_info "Configuration files: /etc/vault.d/"
log_info "Data directory: /opt/vault/data"
log_warning "Keep /etc/vault.d/vault-keys file secure - it contains root token!"
}
# Main execution
main() {
check_privileges
detect_distro
install_vault
configure_vault
start_vault
initialize_vault
unseal_and_configure
configure_database
create_awx_policy
verify_installation
log_success "AWX-Vault integration installation completed successfully!"
}
# Validate arguments
if [[ $# -gt 5 ]]; then
usage
fi
main "$@"
Review the script before running. Execute with: bash install.sh