Ansible Vault
Last updated: July 1, 2025
My Journey with Sensitive Data in Automation
Early in my automation journey, I made a critical mistake that still makes me cringe. I was working on automating server deployments and database configurations, and I had hardcoded passwords, API keys, and SSH private keys directly into my playbooks. Everything worked perfectly in development, and I was proud of my automation scripts.
Then came the moment of sharing. When I pushed my "brilliant" automation code to our Git repository, a colleague immediately pulled me aside. "You just committed database passwords and API keys to version control," he said quietly. My heart sank as I realized the security implications of what I'd done.
That incident taught me a valuable lesson about the importance of securing sensitive data in automation. It was also when I discovered Ansible Vault – a built-in encryption system that allows you to protect passwords, keys, and other sensitive information while still maintaining the power and flexibility of Ansible automation.
In this blog post, I'll share what I've learned about using Ansible Vault effectively across Linux and Windows environments. Whether you're managing database credentials, API tokens, or SSL certificates, mastering Ansible Vault is essential for secure automation.
Understanding Ansible Vault
Ansible Vault is a feature that allows you to encrypt sensitive data at rest. Instead of storing passwords and keys in plain text, Vault uses AES256 encryption to protect your sensitive information. The encrypted files can be safely stored in version control systems and shared with team members.
Key features of Ansible Vault:
File-level encryption - Encrypt entire files containing sensitive data
Variable-level encryption - Encrypt individual variables within files
Multiple password support - Use different passwords for different environments
Seamless integration - Works transparently with playbooks and roles
AES256 encryption - Industry-standard encryption for data security
Basic Vault Operations
Creating Encrypted Files
The simplest way to start with Ansible Vault is to create encrypted files from scratch:
# Create a new encrypted file
ansible-vault create secrets.yml
This command opens your default editor with an empty file. When you save and exit, the file is automatically encrypted.
Encrypting Existing Files
You can also encrypt existing files:
# Encrypt an existing file
ansible-vault encrypt database_passwords.yml
Let's look at a practical example. Suppose you have a file with database credentials:
database_passwords.yml (before encryption)
---
mysql_root_password: "MySuperSecretPassword123!"
mysql_app_password: "AppUser2023@#"
postgres_admin_password: "PostgresAdmin456$"
redis_password: "RedisCache789%"
After running ansible-vault encrypt database_passwords.yml
, the file becomes:
$ANSIBLE_VAULT;1.1;AES256
66386439653331363538653738316233366436666134383936643265333234633536356534656265
3136373533346631616631623930366632656235656637390a313634646565353533363761316638
35636366636264626337336132336234666439393166653036343534643762623838636534303737
6639636364396265370a353138663632653764313463396362373031343638376530376137623933
...
Viewing Encrypted Content
To view the content of encrypted files without decrypting them permanently:
# View encrypted file content
ansible-vault view secrets.yml
# Edit encrypted file content
ansible-vault edit secrets.yml
Decrypting Files
To permanently decrypt files (use with caution):
# Decrypt a file permanently
ansible-vault decrypt secrets.yml
Linux Automation Examples
Example 1: Linux User Management with Encrypted Passwords
Let's create a comprehensive example for managing Linux users with encrypted password data:
user_secrets.yml (encrypted vault file)
---
# User account passwords
user_passwords:
alice: "$6$rounds=4096$salt$hashed_password_here"
bob: "$6$rounds=4096$salt$another_hashed_password"
charlie: "$6$rounds=4096$salt$yet_another_password"
# SSH keys for deployment
deployment_ssh_key: |
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAFwAAAAdzc2gtcn
...
-----END OPENSSH PRIVATE KEY-----
# Database connection details
database_credentials:
mysql:
host: "mysql.internal.company.com"
port: 3306
username: "app_user"
password: "SecureMySQL2023!"
postgresql:
host: "postgres.internal.company.com"
port: 5432
username: "pg_app_user"
password: "PostgresSecure456$"
linux_users.yml (main playbook)
---
- name: Manage Linux users with encrypted credentials
hosts: linux_servers
become: yes
vars_files:
- user_secrets.yml
vars:
users_to_create:
- name: alice
groups: ['sudo', 'developers']
shell: /bin/bash
- name: bob
groups: ['developers']
shell: /bin/bash
- name: charlie
groups: ['operators']
shell: /bin/zsh
tasks:
- name: Create user groups
group:
name: "{{ item }}"
state: present
loop:
- developers
- operators
- name: Create users with encrypted passwords
user:
name: "{{ item.name }}"
password: "{{ user_passwords[item.name] }}"
groups: "{{ item.groups }}"
shell: "{{ item.shell }}"
create_home: yes
state: present
loop: "{{ users_to_create }}"
- name: Set up SSH key for deployment user
authorized_key:
user: "{{ item.name }}"
key: "{{ deployment_ssh_key }}"
state: present
loop: "{{ users_to_create }}"
when: "'developers' in item.groups"
- name: Configure database connection for applications
template:
src: database_config.j2
dest: "/opt/app/config/database.conf"
owner: "{{ item.name }}"
group: "{{ item.name }}"
mode: '0600'
loop: "{{ users_to_create }}"
when: "'developers' in item.groups"
Example 2: SSL Certificate Management
ssl_secrets.yml (encrypted vault file)
---
ssl_certificates:
company_cert:
private_key: |
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC7...
-----END PRIVATE KEY-----
certificate: |
-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKoK/OvD6ngDMA0GCSqGSIb3DQEBCwUA...
-----END CERTIFICATE-----
ca_certificate: |
-----BEGIN CERTIFICATE-----
MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQsw...
-----END CERTIFICATE-----
ssl_keystore_password: "KeystoreSecure2023!"
ssl_setup.yml
---
- name: Configure SSL certificates on Linux servers
hosts: web_servers
become: yes
vars_files:
- ssl_secrets.yml
tasks:
- name: Create SSL directory
file:
path: /etc/ssl/company
state: directory
owner: root
group: root
mode: '0755'
- name: Install private key
copy:
content: "{{ ssl_certificates.company_cert.private_key }}"
dest: /etc/ssl/company/server.key
owner: root
group: root
mode: '0600'
notify: restart apache
- name: Install certificate
copy:
content: "{{ ssl_certificates.company_cert.certificate }}"
dest: /etc/ssl/company/server.crt
owner: root
group: root
mode: '0644'
notify: restart apache
- name: Install CA certificate
copy:
content: "{{ ssl_certificates.company_cert.ca_certificate }}"
dest: /etc/ssl/company/ca.crt
owner: root
group: root
mode: '0644'
handlers:
- name: restart apache
service:
name: "{{ apache_service_name }}"
state: restarted
vars:
apache_service_name: "{{ 'httpd' if ansible_facts['os_family'] == 'RedHat' else 'apache2' }}"
Windows Automation Examples
Example 3: Windows Service Account Management
windows_secrets.yml (encrypted vault file)
---
# Windows service account credentials
service_accounts:
sql_service:
username: "DOMAIN\\sql_service_account"
password: "SQLService2023#Strong!"
web_service:
username: "DOMAIN\\iis_service_account"
password: "WebService2023$Secure!"
backup_service:
username: "DOMAIN\\backup_service_account"
password: "BackupService2023%Safe!"
# Windows local administrator passwords
local_admin_passwords:
web_servers: "WebAdmin2023!Local"
db_servers: "DBAdmin2023@Local"
file_servers: "FileAdmin2023#Local"
# API keys for Windows applications
api_credentials:
monitoring_api: "mon_12345abcdef67890ghijklmnop"
backup_api: "bak_98765zyxwvu43210fedcba9876"
antivirus_api: "av_qwertyuiop1234567890asdfgh"
windows_services.yml
---
- name: Configure Windows services with encrypted credentials
hosts: windows_servers
vars_files:
- windows_secrets.yml
tasks:
- name: Create service accounts in local users (if not domain joined)
win_user:
name: "{{ item.value.username.split('\\')[-1] }}"
password: "{{ item.value.password }}"
groups:
- Users
password_never_expires: yes
user_cannot_change_password: yes
state: present
loop: "{{ service_accounts | dict2items }}"
when: not ansible_facts['domain_role'] is defined or ansible_facts['domain_role'] == 'workgroup'
- name: Set local administrator password
win_user:
name: Administrator
password: "{{ local_admin_passwords[group_names[0]] }}"
state: present
when: group_names | length > 0
- name: Configure SQL Server service account
win_service:
name: MSSQLSERVER
username: "{{ service_accounts.sql_service.username }}"
password: "{{ service_accounts.sql_service.password }}"
state: restarted
when: "'sql_servers' in group_names"
- name: Configure IIS application pool identity
win_iis_webapppool:
name: DefaultAppPool
attributes:
processModel.identityType: SpecificUser
processModel.userName: "{{ service_accounts.web_service.username }}"
processModel.password: "{{ service_accounts.web_service.password }}"
state: present
when: "'web_servers' in group_names"
- name: Install monitoring agent with API key
win_package:
path: C:\temp\monitoring-agent.msi
arguments: '/quiet APIKEY="{{ api_credentials.monitoring_api }}"'
state: present
Example 4: Windows Registry and Certificate Management
windows_security_secrets.yml (encrypted vault file)
---
# Windows certificates with private keys
windows_certificates:
server_auth:
pfx_data: "{{ lookup('file', 'certificates/server.pfx') | b64encode }}"
pfx_password: "CertPassword2023!Secure"
code_signing:
pfx_data: "{{ lookup('file', 'certificates/codesign.pfx') | b64encode }}"
pfx_password: "CodeSign2023@Strong!"
# Registry security settings
security_registry:
audit_settings:
- key: "HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Lsa"
value: "AuditBaseObjects"
data: 1
type: dword
password_policy:
- key: "HKLM:\\SYSTEM\\CurrentControlSet\\Services\\Netlogon\\Parameters"
value: "RequireSignOrSeal"
data: 1
type: dword
# Windows firewall configuration
firewall_rules:
web_traffic:
port: 443
protocol: tcp
action: allow
description: "HTTPS traffic for web applications"
sql_traffic:
port: 1433
protocol: tcp
action: allow
description: "SQL Server database connections"
windows_security.yml
---
- name: Configure Windows security with encrypted settings
hosts: windows_servers
vars_files:
- windows_security_secrets.yml
tasks:
- name: Import server authentication certificate
win_certificate_store:
state: present
cert_store: cert:\LocalMachine\My
cert_data: "{{ windows_certificates.server_auth.pfx_data }}"
cert_password: "{{ windows_certificates.server_auth.pfx_password }}"
file_type: pkcs12
when: "'web_servers' in group_names"
- name: Import code signing certificate
win_certificate_store:
state: present
cert_store: cert:\LocalMachine\My
cert_data: "{{ windows_certificates.code_signing.pfx_data }}"
cert_password: "{{ windows_certificates.code_signing.pfx_password }}"
file_type: pkcs12
when: "'dev_servers' in group_names"
- name: Configure security registry settings
win_regedit:
path: "{{ item.key }}"
name: "{{ item.value }}"
data: "{{ item.data }}"
type: "{{ item.type }}"
state: present
loop: "{{ security_registry.audit_settings + security_registry.password_policy }}"
- name: Configure Windows Firewall rules
win_firewall_rule:
name: "{{ item.value.description }}"
localport: "{{ item.value.port }}"
protocol: "{{ item.value.protocol }}"
action: "{{ item.value.action }}"
direction: in
state: present
enabled: yes
loop: "{{ firewall_rules | dict2items }}"
Sequence Diagram: Ansible Vault Workflow
Here's a sequence diagram illustrating how Ansible Vault processes encrypted content during playbook execution:
This diagram shows the critical security aspects:
Passwords are only in memory during execution
Encrypted files remain encrypted on disk
Data transmission uses encrypted channels
Memory is cleared after execution
Managing Vault Passwords
Single Password Approach
For simple environments, you can use a single vault password:
# Interactive password prompt
ansible-playbook site.yml --ask-vault-pass
# Password file approach
ansible-playbook site.yml --vault-password-file ~/.vault_pass
Multiple Vault IDs
For more complex environments, use vault IDs to manage different passwords:
# Encrypt with specific vault ID
ansible-vault encrypt --vault-id prod@prompt secrets/prod.yml
ansible-vault encrypt --vault-id dev@~/.vault_dev secrets/dev.yml
# Use multiple vault IDs in playbooks
ansible-playbook site.yml --vault-id prod@prompt --vault-id dev@~/.vault_dev
Password Scripts
You can use scripts to retrieve passwords from external systems:
vault_password_script.py
#!/usr/bin/env python3
import subprocess
import sys
def get_vault_password():
# Example: Get password from AWS Secrets Manager
try:
result = subprocess.run([
'aws', 'secretsmanager', 'get-secret-value',
'--secret-id', 'ansible-vault-password',
'--query', 'SecretString',
'--output', 'text'
], capture_output=True, text=True, check=True)
return result.stdout.strip()
except subprocess.CalledProcessError:
print("Failed to retrieve vault password from AWS", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
print(get_vault_password())
Usage:
# Make script executable
chmod +x vault_password_script.py
# Use script for vault password
ansible-playbook site.yml --vault-password-file ./vault_password_script.py
Variable-Level Encryption
Instead of encrypting entire files, you can encrypt individual variables:
# Encrypt a single variable
ansible-vault encrypt_string 'SecretPassword123!' --name 'db_password'
This produces output like:
db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
66386439653331363538653738316233366436666134383936643265333234633536356534656265
3136373533346631616631623930366632656235656637390a313634646565353533363761316638
35636366636264626337336132336234666439393166653036343534643762623838636534303737
6639636364396265370a353138663632653764313463396362373031343638376530376137623933
You can then use this directly in your playbooks:
---
- name: Database setup with encrypted password
hosts: db_servers
vars:
db_user: app_user
db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
66386439653331363538653738316233366436666134383936643265333234633536356534656265
3136373533346631616631623930366632656235656637390a313634646565353533363761316638
35636366636264626337336132336234666439393166653036343534643762623838636534303737
6639636364396265370a353138663632653764313463396362373031343638376530376137623933
tasks:
- name: Create database user
mysql_user:
name: "{{ db_user }}"
password: "{{ db_password }}"
priv: "appdb.*:ALL"
state: present
Advanced Vault Techniques
Vault in Roles
You can use vault-encrypted files within roles:
roles/
database/
vars/
main.yml # Non-sensitive variables
vault.yml # Encrypted sensitive variables
tasks/
main.yml
defaults/
main.yml
roles/database/vars/vault.yml (encrypted)
---
database_root_password: "RootPassword2023!"
database_replication_password: "ReplicationPass456$"
database_backup_password: "BackupPass789%"
roles/database/tasks/main.yml
---
- name: Include vault variables
include_vars: vault.yml
- name: Set MySQL root password
mysql_user:
name: root
password: "{{ database_root_password }}"
login_user: root
login_password: "{{ database_root_password }}"
check_implicit_admin: yes
priv: "*.*:ALL,GRANT"
host: localhost
state: present
Rekey Operations
Change vault passwords when needed:
# Change password interactively
ansible-vault rekey secrets.yml
# Change password using password files
ansible-vault rekey secrets.yml --vault-password-file old_pass.txt --new-vault-password-file new_pass.txt
Vault and CI/CD Integration
For automated deployments, integrate vault passwords securely:
GitLab CI Example (.gitlab-ci.yml)
deploy_production:
stage: deploy
script:
- echo "$VAULT_PASSWORD" > /tmp/vault_pass
- ansible-playbook
--inventory production/inventory
--vault-password-file /tmp/vault_pass
site.yml
- rm /tmp/vault_pass
environment:
name: production
only:
- main
variables:
VAULT_PASSWORD: $PRODUCTION_VAULT_PASSWORD # Set in GitLab CI/CD variables
Configuration File Integration
Set default vault behavior in ansible.cfg
:
[defaults]
vault_password_file = ~/.ansible_vault_pass
# Multiple vault IDs
[vault_secrets]
vault_password_file = scripts/vault_password.py
vault_identity_list = prod@~/.vault_prod, dev@~/.vault_dev
Best Practices and Security Considerations
Security Best Practices
Use Strong Passwords: Vault passwords should be complex and unique
Rotate Passwords Regularly: Use the rekey feature to change passwords periodically
Separate Environments: Use different vault passwords for dev, staging, and production
Limit Access: Only grant vault password access to authorized personnel
Monitor Usage: Log and audit vault password usage
Development Workflow
Never commit vault passwords: Add password files to
.gitignore
Use vault IDs: Organize secrets by environment or purpose
Document requirements: Clearly indicate which vault passwords are needed
Test encryption: Verify that sensitive data is properly encrypted before committing
Common Pitfalls to Avoid
Don't use weak passwords: Avoid simple or predictable vault passwords
Don't share passwords insecurely: Use secure channels for password distribution
Don't decrypt permanently: Avoid using
ansible-vault decrypt
unless absolutely necessaryDon't log sensitive output: Use
no_log: true
for tasks handling sensitive data
- name: Create user with sensitive password
user:
name: "{{ username }}"
password: "{{ user_password }}"
state: present
no_log: true # Prevents password from appearing in logs
Real-World Multi-Environment Example
Here's a comprehensive example showing how to manage multiple environments with different vault configurations:
Directory Structure
ansible-project/
├── inventories/
│ ├── development/
│ │ ├── inventory
│ │ └── group_vars/
│ │ └── all/
│ │ ├── vars.yml
│ │ └── vault.yml (encrypted with dev vault ID)
│ ├── staging/
│ │ ├── inventory
│ │ └── group_vars/
│ │ └── all/
│ │ ├── vars.yml
│ │ └── vault.yml (encrypted with staging vault ID)
│ └── production/
│ ├── inventory
│ └── group_vars/
│ └── all/
│ ├── vars.yml
│ └── vault.yml (encrypted with prod vault ID)
├── site.yml
└── scripts/
└── get_vault_password.py
site.yml
---
- name: Multi-environment deployment with vault
hosts: all
become: yes
pre_tasks:
- name: Verify vault variables are available
assert:
that:
- database_password is defined
- api_key is defined
- ssl_certificate is defined
fail_msg: "Required vault variables are not defined"
roles:
- database
- webserver
- monitoring
post_tasks:
- name: Verify services are running
service:
name: "{{ item }}"
state: started
loop:
- "{{ database_service }}"
- "{{ webserver_service }}"
- "{{ monitoring_service }}"
Deployment Commands
# Development deployment
ansible-playbook -i inventories/development site.yml --vault-id dev@prompt
# Staging deployment
ansible-playbook -i inventories/staging site.yml --vault-id staging@scripts/get_vault_password.py
# Production deployment (with multiple vault IDs)
ansible-playbook -i inventories/production site.yml \
--vault-id prod@prompt \
--vault-id ssl@~/.vault_ssl_pass
Conclusion
Ansible Vault has become an indispensable part of my automation toolkit. What started as a lesson learned from accidentally committing sensitive data has evolved into a comprehensive security practice that protects credentials across all my automation projects.
The beauty of Ansible Vault lies in its simplicity and seamless integration. You don't need to fundamentally change how you write playbooks or structure your automation. Instead, you simply encrypt the sensitive parts and provide the appropriate passwords during execution.
Remember that security is not just about the tools you use, but also about the processes and practices you follow. Use strong passwords, rotate them regularly, separate environments appropriately, and always follow the principle of least privilege when sharing vault access.
As you implement Ansible Vault in your own projects, start small with a few encrypted variables and gradually expand to more complex multi-environment setups. The investment in learning these security practices will pay dividends in the long run, protecting both your infrastructure and your organization from security breaches.
Last updated