role_include_vault-check
An include-only Ansible role to check if HashiCorp Vault variables truly exist
Import
Whenever you work with HashiCorp Vault data stored in a kv secrets engine you may want to import this role into whatever playbook you're running.
Given a role directory create a meta subdirectory underneath it with a file named requirements.yml.
role
├── meta <--- Create this
│ └── requirements.yml <--- Create this
└── tasks
└── main.yml
In requirements.yml add:
- src: 'https://quico.space/quico-ansible/role_include_vault-check.git'
version: 'master'
Now whenver you import role for example via ansible-galaxy install ... you'll automatically get this one downloaded as well. You can optionally leave out version: 'master' since this is the default version anyways, meaning the role_include_vault-check newest master commit. The version: attribute helps you pin a version, for example as version: 'v1.0.0' which will instead pull role_include_vault-check Git tag v1.0.0. Side note, this role follows the Semantic Versioning standard. A Git tag name v1.0.0 refers to Semantic Version 1.0.0.
Use it
Generic setup
From your role call this one like so:
- name: 'If a secret is missing: Fail progress'
import_role:
name: 'role_include_vault-check'
vars:
- vault_check_base_path: '{{ vault_check_base_path }}'
- vault_check_inc_vault_data: '{{ vault_check_vault_data }}'
- vault_check_fail_checks:
- 'password',
- 'password_salt'
This role_include_vault-check expects two variables in your import_role task for example via the vars statement:
-
vault_check_base_path: The path in HashiCorp Vault'skvsecrets engine where secrets are located. Has cosmetic purpose only to inform the user where a key-value check succeeded or failed. -
vault_check_inc_vault_data: The Vault data dictionary we want checked. -
vault_check_fail_checks: A list of keys located atvault_check_base_pathfor which you want to confirm that they are non-empty.Can either be defined in place like so:
- vault_check_fail_checks: - 'password' - 'password_salt'Or can use a list variable defined elsewhere:
- vault_check_fail_checks: '{{ some_list }}'
In context
In a real-world use case you'll likely first query HashiCorp Vault for key-value pairs for example like so:
- name: 'Get secrets'
no_log: 'true'
loop_control:
loop_var: 'server'
with_community.hashi_vault.vault_kv2_get: '{{ local_os_password_vault_paths }}'
ansible.builtin.set_fact:
vault_data: '{{ vault_data | default({}) | combine (server.secret) }}'
The vault_kv2_get lookup plug-in (see vault_kv2_get lookup documentation) iterates over variables you want loaded from Vault. For each iteration it stores the iteration's output in loop_var: 'server'. From that output we only really care about the server.secret dictionary. We append that to a vault_data dictionary which is first initialized as an empty dictionary and then expanded per iteration. When done vault_data contains key-values pair for all Vault variables.
The next step can be this role_include_vault-check to hard-fail in case a key turned out to have an empty value.
- name: 'If a secret is missing: Fail progress'
import_role:
name: 'role_include_vault-check'
vars:
- vault_check_base_path: '{{ local_os_password_vault_base }}'
- vault_check_inc_vault_data: '{{ vault_data }}'
- vault_check_fail_checks: '{{ local_os_password_vault_vars }}'
Output
Ansible's task output will be for example:
TASK [...] ****************************************************************************************
ok: ...
TASK [role_include_vault-check : If a secret is missing: Fail progress] ***************************
ok: [fully.qualified.domain.name] => (item=password) => {
"msg": "Vault has secret 'password' at 'name/domain/qualified/fully/os/root'"
}
ok: [fully.qualified.domain.name] => (item=password_salt) => {
"msg": "Vault has secret 'password_salt' at 'name/domain/qualified/fully/os/root'"
}
TASK [...] ****************************************************************************************
ok: ...