Introduction
HashiCorp Vault manages secrets, encryption, and dynamic credentials. Ansible integrates through lookup plugins and modules — read static secrets, generate dynamic database credentials, issue PKI certificates, and manage Vault policies. This keeps secrets out of playbooks and inventory while providing audit trails for every access.
Note: HashiCorp Vault is different from Ansible Vault. Ansible Vault encrypts files at rest; HashiCorp Vault is a centralized secrets management platform.
Prerequisites
ansible-galaxy collection install community.hashi_vault
pip install hvac
Read Secrets with Lookup Plugin
---
- name: Use HashiCorp Vault secrets
hosts: webservers
become: true
vars:
vault_addr: https://vault.example.com:8200
db_password: "{{ lookup('community.hashi_vault.hashi_vault',
'secret/data/myapp:db_password',
url=vault_addr,
token=vault_token) }}"
tasks:
- name: Deploy app config with secrets
ansible.builtin.template:
src: app.conf.j2
dest: /etc/myapp/config.yml
mode: '0600'
no_log: true
Authentication Methods
Token Auth
# Set via environment
# export VAULT_TOKEN=hvs.xxx
# export VAULT_ADDR=https://vault.example.com:8200
- name: Read secret with token
ansible.builtin.set_fact:
api_key: "{{ lookup('community.hashi_vault.hashi_vault',
'secret/data/api:key',
token=lookup('env', 'VAULT_TOKEN')) }}"
AppRole Auth
- name: Read secret with AppRole
ansible.builtin.set_fact:
db_creds: "{{ lookup('community.hashi_vault.hashi_vault',
'secret/data/database',
auth_method='approle',
role_id=vault_role_id,
secret_id=vault_secret_id) }}"
Kubernetes Auth
# For pods running in Kubernetes
- name: Read secret with K8s auth
ansible.builtin.set_fact:
app_secret: "{{ lookup('community.hashi_vault.hashi_vault',
'secret/data/myapp',
auth_method='kubernetes',
role='ansible-role',
jwt=lookup('file', '/var/run/secrets/kubernetes.io/serviceaccount/token')) }}"
Deploy HashiCorp Vault
---
- name: Deploy HashiCorp Vault
hosts: vault_servers
become: true
vars:
vault_version: "1.17.0"
tasks:
- name: Add HashiCorp repository
ansible.builtin.apt_repository:
repo: "deb https://apt.releases.hashicorp.com {{ ansible_distribution_release }} main"
state: present
- name: Install Vault
ansible.builtin.package:
name: vault
state: present
- name: Deploy Vault config
ansible.builtin.template:
src: vault.hcl.j2
dest: /etc/vault.d/vault.hcl
mode: '0640'
owner: vault
group: vault
notify: restart vault
- name: Start Vault
ansible.builtin.service:
name: vault
state: started
enabled: true
handlers:
- name: restart vault
ansible.builtin.service:
name: vault
state: restarted
# templates/vault.hcl.j2
ui = true
listener "tcp" {
address = "0.0.0.0:8200"
tls_cert_file = "/etc/vault.d/tls/vault.crt"
tls_key_file = "/etc/vault.d/tls/vault.key"
}
storage "raft" {
path = "/opt/vault/data"
node_id = "{{ inventory_hostname }}"
{% for host in groups['vault_servers'] if host != inventory_hostname %}
retry_join {
leader_api_addr = "https://{{ hostvars[host].ansible_default_ipv4.address }}:8200"
}
{% endfor %}
}
api_addr = "https://{{ ansible_default_ipv4.address }}:8200"
cluster_addr = "https://{{ ansible_default_ipv4.address }}:8201"
Dynamic Database Credentials
# Configure database secrets engine (one-time setup)
- name: Enable database secrets engine
community.hashi_vault.vault_write:
url: "{{ vault_addr }}"
token: "{{ vault_root_token }}"
path: sys/mounts/database
data:
type: database
- name: Configure PostgreSQL connection
community.hashi_vault.vault_write:
url: "{{ vault_addr }}"
token: "{{ vault_root_token }}"
path: database/config/myapp-db
data:
plugin_name: postgresql-database-plugin
connection_url: "postgresql://{{'{{'}}username{{'}}'}}:{{'{{'}}password{{'}}'}}@db.example.com:5432/myapp"
allowed_roles: "myapp-role"
username: vault_admin
password: "{{ vault_db_admin_password }}"
- name: Create database role
community.hashi_vault.vault_write:
url: "{{ vault_addr }}"
token: "{{ vault_root_token }}"
path: database/roles/myapp-role
data:
db_name: myapp-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"
# Use dynamic credentials in playbooks
- name: Get dynamic database credentials
ansible.builtin.set_fact:
db_creds: "{{ lookup('community.hashi_vault.vault_read',
'database/creds/myapp-role',
url=vault_addr,
auth_method='approle',
role_id=vault_role_id,
secret_id=vault_secret_id) }}"
- name: Deploy app with dynamic credentials
ansible.builtin.template:
src: database.yml.j2
dest: /etc/myapp/database.yml
mode: '0600'
vars:
db_user: "{{ db_creds.data.username }}"
db_pass: "{{ db_creds.data.password }}"
no_log: true
PKI Certificates
- name: Issue TLS certificate from Vault PKI
community.hashi_vault.vault_write:
url: "{{ vault_addr }}"
token: "{{ vault_token }}"
path: pki/issue/webserver
data:
common_name: "{{ inventory_hostname }}.example.com"
alt_names: "{{ inventory_hostname }}"
ttl: "720h"
register: cert_result
- name: Deploy certificate
ansible.builtin.copy:
content: "{{ cert_result.data.data.certificate }}"
dest: /etc/ssl/certs/server.crt
mode: '0644'
- name: Deploy private key
ansible.builtin.copy:
content: "{{ cert_result.data.data.private_key }}"
dest: /etc/ssl/private/server.key
mode: '0600'
no_log: true
Manage Vault Policies
- name: Create application policy
community.hashi_vault.vault_write:
url: "{{ vault_addr }}"
token: "{{ vault_root_token }}"
path: sys/policies/acl/myapp-policy
data:
policy: |
# Read application secrets
path "secret/data/myapp/*" {
capabilities = ["read", "list"]
}
# Generate dynamic DB credentials
path "database/creds/myapp-role" {
capabilities = ["read"]
}
# Issue TLS certificates
path "pki/issue/webserver" {
capabilities = ["create", "update"]
}
KV Secrets Engine
# Write secrets
- name: Store secret in Vault
community.hashi_vault.vault_write:
url: "{{ vault_addr }}"
token: "{{ vault_token }}"
path: secret/data/myapp/config
data:
data:
api_key: "{{ api_key }}"
db_password: "{{ db_password }}"
encryption_key: "{{ encryption_key }}"
# Read multiple secrets
- name: Read all app secrets
ansible.builtin.set_fact:
app_secrets: "{{ lookup('community.hashi_vault.vault_read',
'secret/data/myapp/config',
url=vault_addr,
token=vault_token) }}"
- name: Use specific secret
ansible.builtin.debug:
msg: "API key starts with {{ app_secrets.data.data.api_key[:4] }}..."
Transit Encryption
# Encrypt data with Vault Transit engine
- name: Encrypt sensitive data
community.hashi_vault.vault_write:
url: "{{ vault_addr }}"
token: "{{ vault_token }}"
path: transit/encrypt/myapp-key
data:
plaintext: "{{ sensitive_data | b64encode }}"
register: encrypted
- name: Store encrypted value
ansible.builtin.set_fact:
encrypted_data: "{{ encrypted.data.data.ciphertext }}"
Troubleshooting
"Permission denied" Errors
Check the Vault policy allows the path you're accessing:
vault token capabilities secret/data/myapp
Token Expired
- name: Check token validity
ansible.builtin.uri:
url: "{{ vault_addr }}/v1/auth/token/lookup-self"
headers:
X-Vault-Token: "{{ vault_token }}"
register: token_info
ignore_errors: true
Related Articles
- Ansible Vault Encrypt/Decrypt
- Ansible Let's Encrypt SSL
- Ansible SSH Key Management
- Ansible Compliance Guide
Conclusion
HashiCorp Vault + Ansible provides centralized secrets management with full audit trails. Use the hashi_vault lookup plugin to read secrets in playbooks, dynamic database credentials for short-lived access, PKI certificates for automatic TLS, and Transit encryption for data protection. Authenticate via token, AppRole, or Kubernetes auth depending on your environment. Secrets never touch disk or version control.