Configure Ansible Vault integration with HashiCorp Vault for secrets management

Advanced 45 min Sep 05, 2026 332 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Learn how to combine Ansible Vault with HashiCorp Vault using the community.hashi_vault collection, AppRole authentication, and least-privilege policies for production-grade secrets management in playbooks.

Prerequisites

  • A running HashiCorp Vault server (or ability to install one)
  • Ansible control node with ansible-core 2.14 or later
  • sudo or root access on target hosts
  • Basic familiarity with Ansible playbooks and YAML
  • Network connectivity between the Ansible control node and Vault API on port 8200

What this solves

Ansible Vault encrypts static files at rest, but it does not rotate secrets, audit access, or issue short-lived credentials. HashiCorp Vault solves dynamic secrets and centralized access control, while Ansible Vault remains useful as an encrypted fallback for bootstrap tokens.

This tutorial configures the community.hashi_vault collection with AppRole authentication, retrieves secrets dynamically in playbooks with the hashi_vault lookup plugin, and implements least-privilege policies so automation only reads what it needs.

Overview: Ansible Vault vs HashiCorp Vault architecture

Ansible Vault is a file encryption feature built into ansible-core. It encrypts YAML files, group_vars, or single strings using a passphrase or a key file. There is no server, no audit log, and no dynamic secret generation. It is a good fit for encrypting a small number of static values checked into version control.

HashiCorp Vault is a full secrets management server with authentication backends, secrets engines, audit logging, leases, and revocation. It can generate short-lived database credentials, cloud IAM tokens, and PKI certificates on demand.

In a production Ansible setup, the pattern is: Vault holds live secrets and issues them dynamically through the hashi_vault lookup plugin at playbook runtime. Ansible Vault only encrypts the small bootstrap credential (an AppRole secret_id or a wrapped token) needed to authenticate to HashiCorp Vault in the first place.

Note: If you already manage Kubernetes secrets with Vault, the authentication and policy concepts here are the same ones covered in configuring Kubernetes secrets management with External Secrets Operator and HashiCorp Vault.

Step-by-step configuration

Install HashiCorp Vault server prerequisites

Vault needs its own repository on both Debian-based and RHEL-based systems. Add the HashiCorp repo before installing.

sudo apt update
sudo apt install -y gnupg software-properties-common curl
curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo 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" | sudo tee /etc/apt/sources.list.d/hashicorp.list
sudo apt update
sudo apt install -y vault
sudo dnf install -y dnf-plugins-core
sudo dnf config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo dnf install -y vault

Configure Vault storage and listener

For production, Vault should run with a real storage backend (Raft integrated storage is common) and TLS enabled. This minimal config uses Raft for a single-node dev-to-prod path.

storage "raft" {
  path    = "/opt/vault/data"
  node_id = "vault-node-1"
}

listener "tcp" {
  address       = "0.0.0.0:8200"
  tls_cert_file = "/etc/vault.d/tls/vault-cert.pem"
  tls_key_file  = "/etc/vault.d/tls/vault-key.pem"
}

api_addr     = "https://vault.internal.example.com:8200"
cluster_addr = "https://vault.internal.example.com:8201"
ui           = true

Create the data directory with correct ownership before starting the service. Vault runs as its own system user, not root.

sudo mkdir -p /opt/vault/data /etc/vault.d/tls
sudo chown -R vault:vault /opt/vault/data
sudo chmod 750 /opt/vault/data

Start Vault and initialize it

Start the service, then initialize and unseal it. Store the unseal keys and root token somewhere secure, not in shell history.

sudo systemctl enable --now vault
export VAULT_ADDR='https://vault.internal.example.com:8200'
vault operator init -key-shares=5 -key-threshold=3
Warning: The root token from initialization has unrestricted access. Use it only to bootstrap AppRole and policies, then revoke it. Never store the root token in playbooks or Ansible Vault files that are shared with automation users.

For production clusters, look at configuring Vault auto-unseal with AWS KMS to avoid manual unseal operations after every restart.

Enable the KV secrets engine

Enable KV version 2 at a dedicated path for Ansible-managed secrets. Version 2 gives you versioning and soft-delete.

vault login
vault secrets enable -path=ansible kv-v2
vault kv put ansible/app/db username="appuser" password="Xk9$mQ2vLp7@wRz4"
vault kv get ansible/app/db

Create an AppRole for Ansible automation

AppRole authentication is designed for machine-to-machine auth, which fits Ansible control nodes better than human login methods.

vault auth enable approle
vault write auth/approle/role/ansible-automation \
  token_policies="ansible-readonly" \
  token_ttl=15m \
  token_max_ttl=1h \
  secret_id_ttl=90d \
  secret_id_num_uses=0

Retrieve the role_id and generate a secret_id. The role_id is not secret by itself, but the secret_id must be protected.

vault read auth/approle/role/ansible-automation/role-id
vault write -f auth/approle/role/ansible-automation/secret-id

Write a least-privilege policy

Scope the policy to only the paths automation needs. Avoid wildcard-everything policies attached to automation roles.

path "ansible/data/app/*" {
  capabilities = ["read"]
}

path "ansible/metadata/app/*" {
  capabilities = ["list"]
}
vault policy write ansible-readonly ansible-readonly.hcl

This mirrors the policy scoping approach used in configuring advanced Consul ACL policies for production security hardening: grant read-only access to the exact secret paths a workload touches, nothing broader.

Install the community.hashi_vault Ansible collection

Install Ansible itself if it is not already present, then add the collection and its Python dependency.

sudo apt update
sudo apt install -y python3-pip pipx
pipx install --include-deps ansible
pipx runpip ansible install hvac requests
sudo dnf install -y python3-pip
pip3 install --user ansible hvac requests
ansible-galaxy collection install community.hashi_vault
ansible-galaxy collection list community.hashi_vault

Store the AppRole secret_id with Ansible Vault as a fallback

The role_id and secret_id are the only credentials Ansible needs on disk. Encrypt the secret_id with Ansible Vault so it never sits in plaintext in your repository.

ansible-vault create group_vars/all/vault_approle.yml
vault_role_id: "9a8f3c2e-1234-4d5e-9f6a-abc123def456"
vault_secret_id: "b7e6d5c4-9876-4321-8888-fedcba098765"

Restrict who can decrypt this file by using a dedicated vault password file with strict permissions, not a password shared in chat.

touch ~/.ansible_vault_pass
chmod 600 ~/.ansible_vault_pass
echo "a-strong-unique-vault-password" > ~/.ansible_vault_pass
Never use chmod 777. It gives every user on the system full access to your files. Instead, fix ownership with chown and use minimal permissions such as 600 for private key material and vault password files.

Configure ansible.cfg to point to this password file so playbook runs can decrypt automatically in CI.

[defaults]
vault_password_file = ~/.ansible_vault_pass
collections_path = ~/.ansible/collections

Retrieve secrets dynamically with the hashi_vault lookup plugin

Reference the encrypted role_id and secret_id, then use the lookup plugin to pull live secrets from Vault at playbook run time instead of storing the final values anywhere.

---
- name: Deploy application with dynamic Vault secrets
  hosts: app_servers
  become: true
  vars:
    vault_addr: "https://vault.internal.example.com:8200"

  tasks:
    - name: Fetch database credentials from Vault
      ansible.builtin.set_fact:
        db_creds: "{{ lookup('community.hashi_vault.hashi_vault',
          'secret=ansible/data/app/db:data',
          url=vault_addr,
          auth_method='approle',
          role_id=vault_role_id,
          secret_id=vault_secret_id) }}"
      no_log: true

    - name: Write application environment file
      ansible.builtin.template:
        src: templates/app.env.j2
        dest: /etc/myapp/app.env
        owner: myapp
        group: myapp
        mode: '0640'
      no_log: true

The no_log: true directive stops decrypted secret values from appearing in Ansible output or logs. Always set it on tasks that handle secret material.

Alternative: use the hashi_vault lookup inline in variable files

For secrets referenced across many roles, define the lookup once in group_vars rather than repeating set_fact tasks.

db_password: "{{ lookup('community.hashi_vault.hashi_vault',
  'secret=ansible/data/app/db:data.password',
  url='https://vault.internal.example.com:8200',
  auth_method='approle',
  role_id=vault_role_id,
  secret_id=vault_secret_id) }}"
Note: Lookups in group_vars are evaluated lazily per play, so Vault is queried fresh on every run. This keeps credentials current without a manual sync step, similar to how dynamic inventories work in configuring Ansible dynamic inventory for AWS, Azure and GCP.

Restrict token TTL and rotate the AppRole secret_id

Short-lived tokens limit the blast radius of a leaked credential. Rotate the secret_id on a schedule instead of leaving it valid indefinitely.

vault write -f auth/approle/role/ansible-automation/secret-id
vault list auth/approle/role/ansible-automation/secret-id-accessor

Revoke an old secret_id accessor once the new one is deployed to your Ansible Vault file.

vault write auth/approle/role/ansible-automation/secret-id-accessor/destroy \
  secret_id_accessor="a1b2c3d4-role-accessor-id"

Verify your setup

Confirm Vault is unsealed, the AppRole authenticates correctly, and Ansible can retrieve a secret before running it against production hosts.

vault status
vault write auth/approle/login role_id="9a8f3c2e-1234-4d5e-9f6a-abc123def456" secret_id="b7e6d5c4-9876-4321-8888-fedcba098765"
ansible-playbook deploy-app.yml --check --diff --ask-vault-pass

Run a minimal test playbook that only performs a lookup and prints a masked confirmation, to validate connectivity without exposing secret values.

---
- name: Test Vault connectivity
  hosts: localhost
  tasks:
    - name: Confirm secret retrieval
      ansible.builtin.set_fact:
        secret_retrieved: "{{ lookup('community.hashi_vault.hashi_vault',
          'secret=ansible/data/app/db:data',
          url='https://vault.internal.example.com:8200',
          auth_method='approle',
          role_id=vault_role_id,
          secret_id=vault_secret_id) is defined }}"
      no_log: true

    - name: Report result
      ansible.builtin.debug:
        msg: "Vault lookup succeeded: {{ secret_retrieved }}"

Common issues

SymptomCauseFix
permission denied on secret pathAppRole policy does not include the exact KV pathCheck vault policy read ansible-readonly and add the missing path with capabilities = ["read"]
invalid role or secret ID error during loginsecret_id expired or was single-use and already consumedGenerate a new secret_id with vault write -f auth/approle/role/ansible-automation/secret-id and update the Ansible Vault file
hashi_vault lookup plugin not foundCollection not installed or wrong collections_path in ansible.cfgRun ansible-galaxy collection list community.hashi_vault and confirm the path matches ansible.cfg
connection refused to Vault APIFirewall blocking port 8200 or wrong VAULT_ADDRAllow the specific source IP range on port 8200 with a scoped firewall rule, do not disable the firewall
secrets appear in Ansible output logsMissing no_log directive on tasks handling secret valuesAdd no_log: true to every task that fetches or templates secret data
Vault sealed after server rebootManual unseal required, no auto-unseal configuredConfigure auto-unseal with a cloud KMS or run vault operator unseal with quorum key shares

Next steps

Running this in production?

Want this handled for you? Running this at scale adds a second layer of work: secret rotation schedules, Vault cluster failover drills, policy audits, and on-call coverage when a token expires mid-deployment. See how we run infrastructure like this for European teams.

Automated install script

Run this to automate the entire setup

不想自己管理这些吗?

我们为依赖稳定运行时间的企业管理基础设施。全托管服务,配备一位熟悉您系统架构的固定联系人。

您将拥有一位了解您整体架构的固定联系人

Rotterdam 22:35 · 一条消息即可联系我们,无需填写工单表单