Vault with Ansible
How to use Vault with Ansible
Use Plays with HashiCorp Vault
Integrating Hashicorp Vault with Ansible allows you to manage sensitive data and secrets securely. Vault provides a centralized way to store and access secrets, such as API
keys, passwords
, and certificates
, which can be dynamically retrieved during playbook execution. This enhances security by avoiding hard-coded secrets in your playbooks and inventory files.
Authentification
To access Vault, you first need to authenticate to Vault. To do so, you need to have an input token or a userpass connection.
I will only talk about a userpass
connection there but it is easy to modify it to an input token
connection.
This documentation will focus only on retrieving the password for the hosts and creating an inventory from it. First, let's define some variables to store our username
and password
:
---
- name: Get Userpass
gather_facts: false
connection: local
hosts: localhost
vars_prompt:
- name: "username"
prompt: "Enter your username"
private: no
- name: "password"
prompt: "Enter your password"
private: yes
tasks:
- name: Print Username
debug:
msg: "{{ username }}"
delegate_to: localhost
- name: Print Password
debug:
msg: "{{ password }}"
delegate_to: localhost
connection: local
: don't connect viassh
to any hosts, stay local.hosts: localhost
: don't execute the playbook on any hosts, only on the hostlocalhost
.gather_facts: fasle
: don't connect viassh
, even to thelocalhost
.
Now we have the username
and password
of the user who wants to connect to Vault stored in a variable.
Get the User Token
To continue the authentication process, we need to store the Vault address in a variable. Then we make a POST
request to obtain the token for this user.
---
- name: Connection Vault
gather_facts: false
connection: local
hosts: localhost
vars:
vault_addr: 'https://vault.skignes.fr:8200'
vars_prompt:
- name: "vault_username"
prompt: "Enter your Vault username"
private: no
- name: "vault_password"
prompt: "Enter your Vault password"
private: yes
tasks:
- name: Authentificate with Vault
uri:
url: "{{ vault_addr }}/v1/auth/userpass/login/{{ vault_username}}"
method: POST
body: "{{ {'password': vault_password} | to_json }}"
headers:
Content-Type: "applications/json"
status_code: 200
return_content: yes
register: vault_auth_response
changed_when: false
no_log: true
- name: Set Vault token
set_fact:
vault_token: "{{ vault_auth_response.json.auth.client_token }}"
no_log: true
Authentification with Vault
: make aPOST
request to the Vault to get the response of the login.Set Vault token
: get the token of the user to access thesecrets
inside the Vault.no_log: true
: don't log any information about these tasks, even indebug
mode.
Add Hosts inside Inventory
We have a connection between our Ansible Playbook and our Vault. We now need to get the secrets
from Vault.
Get a SSH password
To get the secrets
from Vault we need to make a new request to the Vault server.
---
- name: Get SSH Password
gather_facts: false
connection: local
hosts: localhost
vars:
vault_addr: 'https://vault.skignes.fr:8200'
# API path for the directory where the secrets are
password_path: '/kv/data/my_directory'
vars_prompt:
- name: "vault_username"
prompt: "Enter your Vault username"
private: no
- name: "vault_password"
prompt: "Enter your Vault password"
private: yes
tasks:
- name: Authenticate with Vault
uri:
url: "{{ vault_addr }}/v1/auth/userpass/login/{{ vault_username }}"
method: POST
body: "{{ {'password': vault_password} | to_json }}"
headers:
Content-Type: "application/json"
status_code: 200
return_content: yes
register: vault_auth_response
changed_when: false
no_log: true
- name: Set Vault token
set_fact:
vault_token: "{{ vault_auth_response.json.auth.client_token }}"
no_log: true
- name: Fetch Password
set_fact:
ssh_password: "{{ lookup('community.hashi_vault.hashi_vault', 'secret={{ password_path }} token={{ vault_token }} url={{ vault_addr }}')['key'] }}"
no_log: true
password_path
: It is a new variable, and it represents the API path to the directory of secrets.['key']
: Inside theFetch Password
task, there is['key']
at the end. It is the name of the secret you want to retrieve (for example:skignes
).
Add it inside the Inventory
So when we got the password and we know the username
for the machine we can add it to the inventory.
---
- name: Create Inventory
gather_facts: false
connection: local
hosts: localhost
vars:
vault_addr: 'http://vault.skignes.fr:8200'
# API path for the directory where the secrets are
password_path: '/kv/data/my_directory'
vars_prompt:
- name: "vault_username"
prompt: "Enter your Vault username"
private: no
- name: "vault_password"
prompt: "Enter your Vault password"
private: yes
tasks:
- name: Authenticate with Vault
uri:
url: "{{ vault_addr }}/v1/auth/userpass/login/{{ vault_username }}"
method: POST
body: "{{ {'password': vault_password} | to_json }}"
headers:
Content-Type: "application/json"
status_code: 200
return_content: yes
register: vault_auth_response
changed_when: false
no_log: true
- name: Set Vault token
set_fact:
vault_token: "{{ vault_auth_response.json.auth.client_token }}"
no_log: true
- name: Fetch Password
set_fact:
ssh_password: "{{ lookup('community.hashi_vault.hashi_vault', 'secret={{ password_path }} token={{ vault_token }} url={{ vault_addr }}')['key'] }}"
no_log: true
- name: Save SSH connection
add_host:
name: 127.0.0.1
ansible_ssh_user: "skignes"
ansible_ssh_pass: "{{ ssh_password }}"
ansible_ssh_port: 12345
changed_when: false
no_log: true
So if you read the Save SSH connection
, you should have seen that it uses the same variables as when you create a plain text inventory.
name
: The IP address of the connection you want to add (it can be a FQDN).ansible_ssh_user
: It is the username to connect to the host.ansible_ssh_pass
: It is the password to connect to the host.ansible_ssh_port
: It is the port to connect to the host.
When this new host is created, we can use it inside other plays as a normal host, just as if we had defined it in a plain text inventory.
The new host is not available inside the Play where we are defining the new hosts. Only on a new play.
Final Playbook
So now that we have everything to create an Inventory file, we can write a nice playbook to execute the ls
command on every host.
---
- name: List everything
gather_facts: false
connection: local
hosts: localhost
vars:
vault_addr: 'http://vault.skignes.fr:8200'
# API path for the directory where the secrets are
password_path: '/kv/data/my_directory'
vars_prompt:
- name: "vault_username"
prompt: "Enter your Vault username"
private: no
- name: "vault_password"
prompt: "Enter your Vault password"
private: yes
tasks:
- name: Authenticate with Vault
uri:
url: "{{ vault_addr }}/v1/auth/userpass/login/{{ vault_username }}"
method: POST
body: "{{ {'password': vault_password} | to_json }}"
headers:
Content-Type: "application/json"
status_code: 200
return_content: yes
register: vault_auth_response
changed_when: false
no_log: true
- name: Set Vault token
set_fact:
vault_token: "{{ vault_auth_response.json.auth.client_token }}"
no_log: true
- name: Fetch Password
set_fact:
ssh_password: "{{ lookup('community.hashi_vault.hashi_vault', 'secret={{ password_path }} token={{ vault_token }} url={{ vault_addr }}')['key'] }}"
no_log: true
- name: Save SSH connection
add_host:
name: 127.0.0.1
ansible_ssh_user: "skignes"
ansible_ssh_pass: "{{ ssh_password }}"
ansible_ssh_port: 12345
changed_when: false
no_log: true
- name: Execute a command
hosts: all,!localhost
tasks:
- name: Execute the 'ls' command
command: chdir=/home/ ls
register: ls_output
changed_when: false
- name: Print the output of the 'ls' command
debug:
var: ls_output.stdout_lines
You need to exclude localhost
because it is the host that will execute the playbook if no inventory file is provided.
Now you have a dynamic inventory with Hashicorp Vault !