Ansible Secrets Management: Best Practices for Enterprise Credential Security
By Luca Berton · Published 2024-01-01 · Category: troubleshooting
Master Ansible secrets management for enterprise environments. Ansible Vault, external secret managers, no_log, credential rotation, and security best.
Introduction
Every Ansible playbook eventually needs secrets — database passwords, API tokens, SSH keys, certificates. How you manage these secrets determines whether your automation is a security asset or liability. This guide covers every approach to secrets management in Ansible, from built-in Vault encryption to enterprise integrations.
See also: Ansible Container Security: Image Scanning, Runtime Protection, and Supply Chain Security
Ansible Vault
Encrypt a Variable File
# Create encrypted file
ansible-vault create group_vars/production/vault.yml
# Encrypt existing file
ansible-vault encrypt group_vars/production/secrets.yml
# Edit encrypted file
ansible-vault edit group_vars/production/vault.yml
# View without editing
ansible-vault view group_vars/production/vault.yml
# Decrypt (use sparingly)
ansible-vault decrypt group_vars/production/vault.yml
Encrypt Individual Variables
# Encrypt a single string
ansible-vault encrypt_string 'SuperSecretP@ss!' --name 'db_password'
# Output:
# db_password: !vault |
# $ANSIBLE_VAULT;1.1;AES256
# 62616463...
Use in variables:
# group_vars/production/vault.yml
vault_db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
62616463653266383137353...
vault_api_token: !vault |
$ANSIBLE_VAULT;1.1;AES256
39363865316332613562...
Reference Pattern
# group_vars/production/vars.yml (not encrypted)
db_password: "{{ vault_db_password }}"
api_token: "{{ vault_api_token }}"
smtp_password: "{{ vault_smtp_password }}"
# group_vars/production/vault.yml (encrypted)
vault_db_password: "ActualPassword123!"
vault_api_token: "ghp_xxxxxxxxxxxx"
vault_smtp_password: "SmtpPass456!"
This pattern lets you grep for variable usage without decrypting.
Multiple Vault IDs
# Different passwords for different environments
ansible-playbook site.yml \
--vault-id dev@prompt \
--vault-id prod@/path/to/prod-vault-pass
# In vault files, specify which ID:
ansible-vault encrypt --vault-id prod@prompt secrets.yml
Vault Password Sources
# Interactive prompt
ansible-playbook site.yml --ask-vault-pass
# Password file
ansible-playbook site.yml --vault-password-file ~/.vault_pass
# Script (dynamic password from secret manager)
ansible-playbook site.yml --vault-password-file vault-pass.sh
# Environment variable (CI/CD)
ANSIBLE_VAULT_PASSWORD_FILE=~/.vault_pass ansible-playbook site.yml
#!/bin/bash
# vault-pass.sh — fetch vault password from external source
aws secretsmanager get-secret-value \
--secret-id ansible-vault-password \
--query SecretString --output text
no_log: Protecting Output
- name: Create database user
community.postgresql.postgresql_user:
name: app_user
password: "{{ db_password }}"
no_log: true # Prevents password from appearing in logs
- name: Deploy API configuration
ansible.builtin.template:
src: api-config.j2
dest: /etc/myapp/config.yml
mode: '0600'
no_log: true # Template contains secrets
- name: Call external API with token
ansible.builtin.uri:
url: "{{ api_endpoint }}"
headers:
Authorization: "Bearer {{ api_token }}"
no_log: true
register: api_result
# Show non-sensitive parts of result
- name: Display API response status
ansible.builtin.debug:
msg: "API returned status {{ api_result.status }}"
Global no_log
# For entire play
- name: Secret-heavy deployment
hosts: all
no_log: true # All tasks hidden
tasks:
- name: This output is hidden
ansible.builtin.debug:
msg: "You won't see this"
See also: Ansible Zero Trust Security: Implement Zero Trust Architecture for Enterprise Infrastructure
External Secret Managers
HashiCorp Vault
- name: Fetch secrets from HashiCorp Vault
vars:
db_creds: "{{ lookup('community.hashi_vault.hashi_vault',
'secret/data/myapp/database',
url='https://vault.example.com',
auth_method='approle',
role_id=lookup('env', 'VAULT_ROLE_ID'),
secret_id=lookup('env', 'VAULT_SECRET_ID')
) }}"
tasks:
- name: Use dynamic credentials
ansible.builtin.template:
src: db-config.j2
dest: /etc/myapp/db.conf
vars:
db_user: "{{ db_creds.data.username }}"
db_pass: "{{ db_creds.data.password }}"
no_log: true
AWS Secrets Manager
- name: Fetch from AWS Secrets Manager
ansible.builtin.set_fact:
aws_secret: "{{ lookup('amazon.aws.aws_secret',
'myapp/production/database',
region='us-east-1') | from_json }}"
no_log: true
- name: Use AWS secret
ansible.builtin.template:
src: config.j2
dest: /etc/myapp/config.yml
vars:
db_password: "{{ aws_secret.password }}"
no_log: true
Azure Key Vault
- name: Fetch from Azure Key Vault
ansible.builtin.set_fact:
azure_secret: "{{ lookup('azure.azcollection.azure_keyvault_secret',
'db-password',
vault_url='https://mykeyvault.vault.azure.net') }}"
no_log: true
CyberArk
- name: Fetch from CyberArk
ansible.builtin.set_fact:
cyberark_creds: "{{ lookup('cyberark.conjur.conjur_variable',
'production/database/password') }}"
no_log: true
AAP Credential Management
# AAP Credential Types:
# - Machine (SSH)
# - Source Control (Git)
# - Vault (Ansible Vault password)
# - Network (Router/Switch)
# - Cloud (AWS, Azure, GCP)
# - Custom (any external system)
# Credentials in AAP are:
# ✅ Encrypted at rest (AES-256)
# ✅ Never exposed in job output
# ✅ RBAC-controlled (Use vs Admin)
# ✅ Injected at runtime (env vars or extra vars)
# ✅ Rotatable without changing playbooks
Custom Credential Type for External Secrets
# Input Configuration
fields:
- id: secret_server_url
type: string
label: Secret Server URL
- id: secret_server_token
type: string
label: API Token
secret: true
- id: secret_path
type: string
label: Secret Path
# Injector Configuration
env:
SECRET_SERVER_URL: "{{ secret_server_url }}"
SECRET_SERVER_TOKEN: "{{ secret_server_token }}"
extra_vars:
secret_lookup_path: "{{ secret_path }}"
See also: HashiCorp Vault Integration with Ansible Automation Platform: Credential Management at Scale
Credential Rotation
- name: Rotate database credentials
hosts: localhost
tasks:
- name: Generate new password
ansible.builtin.set_fact:
new_password: "{{ lookup('password', '/dev/null length=32 chars=ascii_letters,digits,punctuation') }}"
no_log: true
- name: Update password in database
community.postgresql.postgresql_user:
login_host: "{{ db_host }}"
login_user: admin
login_password: "{{ vault_db_admin_pass }}"
name: app_user
password: "{{ new_password }}"
no_log: true
- name: Store new password in Vault
community.hashi_vault.vault_kv2_write:
url: "{{ vault_url }}"
path: "secret/myapp/database"
data:
username: app_user
password: "{{ new_password }}"
no_log: true
- name: Update application config
ansible.builtin.template:
src: db-config.j2
dest: /etc/myapp/db.conf
delegate_to: "{{ item }}"
loop: "{{ groups['app_servers'] }}"
notify: restart application
no_log: true
- name: Verify application connectivity
ansible.builtin.uri:
url: "http://{{ item }}:8080/health"
status_code: 200
loop: "{{ groups['app_servers'] }}"
Security Anti-Patterns
# ❌ NEVER: Hardcode secrets
- name: Bad practice
ansible.builtin.command: mysql -u root -pMyPassword123
# ❌ NEVER: Secrets in task names
- name: "Connect to DB with password {{ db_pass }}"
ansible.builtin.debug:
# ❌ NEVER: Log secrets via debug
- name: Show credentials
ansible.builtin.debug:
var: db_password
# ❌ NEVER: Secrets in command line
# ansible-playbook site.yml -e "password=Secret123" # Shows in ps output!
# ✅ CORRECT: Use variable files + vault + no_log
- name: Deploy securely
ansible.builtin.template:
src: config.j2
dest: /etc/myapp/config.yml
no_log: true
Git Security
# .gitignore — never commit these
*.vault_pass
vault-password*
*.pem
*.key
id_rsa*
.env
secrets.yml.dec
# Pre-commit hook to prevent unencrypted secrets
#!/bin/bash
# .git/hooks/pre-commit
for file in $(git diff --cached --name-only | grep vault); do
if ! head -1 "$file" | grep -q '$ANSIBLE_VAULT'; then
echo "ERROR: $file is not encrypted!"
exit 1
fi
done
Best Practices
Encrypt at rest — All secrets in Vault-encrypted files or external secret managersno_log: true everywhere — Every task that touches secrets
Vault prefix convention — vault_ prefix for encrypted vars, reference in plain vars
External secret managers for production — Ansible Vault for dev; HashiCorp Vault/AWS SM for prod
Rotate regularly — Automate credential rotation with playbooks
Never commit plaintext — Pre-commit hooks to catch unencrypted vault files
Separate vault passwords per environment — Different encryption keys for dev/staging/prod
AAP credential isolation — Teams can Use credentials without seeing secret values
Audit secret access — Log who accessed what secret and when
FAQ
Ansible Vault vs HashiCorp Vault?
Ansible Vault encrypts files at rest (symmetric AES-256). HashiCorp Vault is a full secret management platform with dynamic secrets, rotation, and audit. Use Ansible Vault for simple cases; HashiCorp Vault for enterprise.
Can secrets leak through Ansible facts?
Yes — if a task registers output containing secrets and facts are cached. Always use no_log: true and avoid caching sensitive registered variables.
How to handle secrets in Molecule tests?
Use test-specific vault files with known test passwords, or mock external secret lookups in test scenarios.
Conclusion
Secrets management is the foundation of secure automation. By combining Ansible Vault encryption, external secret managers, no_log protection, and automated credential rotation, you can build automation pipelines that handle sensitive data safely at enterprise scale.
Related Articles
• Ansible Vault Complete Guide • HashiCorp Vault Integration with AAP • Ansible Zero Trust SecurityCategory: troubleshooting