Related docs:
IDM VAULT PLUGIN INVENTORY CAPABILITIES AAP INTEGRATION DOCS MAP
Use this guide to choose the major IdM vault retrieval pattern exposed by
eigenstate.ipa.vault.
It is the IdM-vault-side companion to the inventory capabilities guide.
flowchart LR
vaults["IdM vaults"]
scopes["user / service / shared scope"]
auth["Kerberos auth"]
lookup["eigenstate.ipa.vault"]
tasks["Tasks, templates,\ncredential injection"]
vaults --> lookup
scopes --> lookup
auth --> lookup
lookup --> tasks
Choose the vault pattern that matches both:
Use a shared standard vault when the same secret must be consumed by automation across many hosts or jobs and no extra decryption material should be required at lookup time.
Typical cases:
flowchart LR
V["Shared standard vault"] --> L["lookup(..., shared=true)"] --> U["utf-8 secret for task/template use"]
Example:
- name: Read a shared database password
ansible.builtin.set_fact:
db_password: "{{ lookup('eigenstate.ipa.vault',
'database-password',
server='idm-01.corp.example.com',
ipaadmin_password=lookup('env', 'IPA_ADMIN_PASSWORD'),
shared=true,
verify='/etc/ipa/ca.crt') }}"
Why this pattern fits:
Use a symmetric vault when the automation should retrieve a centrally owned secret, but the vault itself should still require a second password for decryption.
Typical cases:
flowchart LR
V["Shared symmetric vault"] --> P["vault_password\nor vault_password_file"] --> L["lookup"] --> U["decrypted secret"]
Example:
- name: Read a symmetric vault using a password file
ansible.builtin.set_fact:
api_key: "{{ lookup('eigenstate.ipa.vault',
'partner-api-key',
server='idm-01.corp.example.com',
kerberos_keytab='/runner/env/ipa/admin.keytab',
shared=true,
vault_password_file='/runner/env/ipa/partner-api.pass',
verify='/etc/ipa/ca.crt') }}"
Why this pattern fits:
Use an asymmetric vault when the consuming automation should only succeed if it also possesses a corresponding private key.
Typical cases:
flowchart LR
V["Shared asymmetric vault"] --> K["private_key_file"] --> L["lookup"] --> U["decrypted output"]
Example:
- name: Read a TLS private key from an asymmetric vault
ansible.builtin.copy:
content: "{{ lookup('eigenstate.ipa.vault',
'wildcard-tls-key',
server='idm-01.corp.example.com',
kerberos_keytab='/runner/env/ipa/admin.keytab',
shared=true,
private_key_file='/runner/env/ipa/tls-recovery.pem',
verify='/etc/ipa/ca.crt') }}"
dest: /etc/pki/tls/private/wildcard.key
mode: "0600"
Why this pattern fits:
Use a user vault when the secret belongs to one named user principal rather than to a shared automation domain.
Typical cases:
flowchart LR
V["User vault"] --> S["username='appuser'"] --> L["lookup"] --> U["principal-scoped secret"]
Example:
- name: Read a user-owned secret
ansible.builtin.debug:
msg: "{{ lookup('eigenstate.ipa.vault',
'my-bootstrap-token',
server='idm-01.corp.example.com',
kerberos_keytab='/runner/env/ipa/admin.keytab',
username='appuser',
verify='/etc/ipa/ca.crt') }}"
Why this pattern fits:
Use a service vault when the secret belongs to a service principal boundary.
Typical cases:
flowchart LR
V["Service vault"] --> S["service='HTTP/app.example.com'"] --> L["lookup"] --> U["service-scoped secret"]
Example:
- name: Read a service-principal secret
ansible.builtin.set_fact:
service_secret: "{{ lookup('eigenstate.ipa.vault',
'oidc-client-secret',
server='idm-01.corp.example.com',
ipaadmin_password=lookup('env', 'IPA_ADMIN_PASSWORD'),
service='HTTP/app.corp.example.com',
verify='/etc/ipa/ca.crt') }}"
Why this pattern fits:
Use encoding='base64' when the vault content is not reliably text.
Typical cases:
flowchart LR
V["Binary vault data"] --> E["encoding='base64'"] --> L["lookup"] --> D["b64decode in consuming task"]
Example:
- name: Install service keytab from IdM vault
ansible.builtin.copy:
content: "{{ lookup('eigenstate.ipa.vault',
'service-keytab',
server='idm-01.corp.example.com',
kerberos_keytab='/runner/env/ipa/admin.keytab',
shared=true,
encoding='base64',
verify='/etc/ipa/ca.crt') | b64decode }}"
dest: /etc/krb5.keytab
mode: "0600"
Why this pattern fits:
[!CAUTION] Do not rely on the default
utf-8return mode for binary payloads. Usebase64intentionally when the content is a keytab, archive, bundle, or any other opaque byte sequence.
Use IdM vault lookups when you need automation to consume the latest rotated secret without changing repository content.
Typical cases:
The operational advantage is:
That keeps secret version changes out of Git and out of static inventory.
Use operation='show' or operation='find' when the caller needs to inspect
vault metadata before deciding whether payload retrieval is appropriate.
Typical cases:
flowchart LR
find["operation=find"] --> inspect["metadata records"]
show["operation=show"] --> inspect
inspect --> retrieve["optional later retrieve"]
Why this pattern fits:
Use a consistent metadata shape when the vault is holding an opaque artifact that will be brokered by Ansible and consumed elsewhere.
Recommended fields:
payments-bootstrap-bundle or cluster-sealed-payloadsealed bootstrap bundle for payments-agentstandard for opaque sealed blobs unless the vault itself needs a
second unlock materialExample convention:
sealed_artifact:
vault_name: payments-bootstrap-bundle
description: sealed bootstrap bundle for payments-agent
consumer: payments-agent
final_boundary: target-host
format: base64
Why this pattern fits:
Use this pattern when the vault is storing an encrypted or sealed artifact that Ansible should transport, but not interpret.
Typical cases:
flowchart LR
vault["IdM vault with sealed payload"] --> lookup["lookup(..., encoding='base64', include_metadata=true)"]
lookup --> broker["Ansible brokers payload plus metadata"]
broker --> consumer["downstream agent, controller, or target host"]
Why this pattern fits:
This is not a HashiCorp Vault-style sealing controller or an Ansible Vault replacement. The collection retrieves and brokers the artifact. The downstream system is still responsible for final consumption or unseal.
The sections above describe the brokered delivery pattern conceptually. This section covers the full end-to-end workflow with commands, for both the IPA admin path and a delegated operator path where admin rights are not required after initial vault setup.
A sealed artifact can only be opened by the target host’s private key. That key never moves. The sealer only needs the target’s public certificate — which IdM can issue for an enrolled host and which is not sensitive material.
In practice, enrollment and certificate materialization are separate concerns:
ipa-client-install gives the host its enrolled IdM identityDo not assume the target already has a decryptable host cert just because it is
enrolled. Verify with getcert list or ipa-getcert list first.
flowchart LR
hostpair["Host cert + private key\nstay on target"]
seal["Seal blob with host cert"]
store["Archive sealed blob to vault"]
broker["Vault lookup returns opaque blob"]
deliver["Deliver blob to target"]
unseal["Target decrypts locally"]
hostpair --> seal --> store --> broker --> deliver --> unseal
Before sealing anything, confirm the target already has a CMS-usable host certificate and matching private key. If not, request one explicitly.
Example on the target host:
getcert list
getcert request \
-I appserv-01-sealed-artifact \
-c IPA \
-k /etc/pki/tls/private/appserv-01-sealed-artifact.key \
-f /etc/pki/tls/certs/appserv-01-sealed-artifact.pem \
-K host/appserv-01.idm.corp.lan@IDM.CORP.LAN \
-D appserv-01.idm.corp.lan \
-w
Use the resulting cert/key paths consistently in both the seal verification and the final decrypt step.
flowchart LR
prereq["Verify target cert/key"]
export["Export public cert"]
seal["Seal payload"]
archive["Create vault + archive blob"]
retrieve["Vault lookup retrieves blob"]
copy["Copy blob to target"]
unseal["Target decrypts"]
cleanup["Remove blob"]
prereq --> export --> seal --> archive --> retrieve --> copy --> unseal --> cleanup
Use this when the operator has full IdM admin rights and owns the vault directly.
Step 1 — Ensure the target has a usable host cert/key pair
getcert list
If no suitable cert exists yet, request one on the target host:
getcert request \
-I appserv-01-sealed-artifact \
-c IPA \
-k /etc/pki/tls/private/appserv-01-sealed-artifact.key \
-f /etc/pki/tls/certs/appserv-01-sealed-artifact.pem \
-K host/appserv-01.idm.corp.lan@IDM.CORP.LAN \
-D appserv-01.idm.corp.lan \
-w
Step 2 — Export the host certificate
kinit admin
# Use whichever export path your environment supports.
# Example: copy the cert from the target after certmonger issued it.
scp root@appserv-01.idm.corp.lan:/etc/pki/tls/certs/appserv-01-sealed-artifact.pem \
appserv-01.pem
openssl x509 -in appserv-01.pem -noout -subject -dates
The host certificate is not sensitive. Any authenticated principal can read it.
Step 3 — Seal the artifact
openssl cms -encrypt \
-in artifact.bin \
-out sealed.bin \
-recip appserv-01.pem \
-aes256 \
-binary \
-outform DER
Only appserv-01 can decrypt this. The operator cannot unseal what they just
sealed.
Step 4 — Create the vault and archive the sealed blob
ipa vault-add appserv-01-bootstrap \
--type standard \
--shared
ipa vault-archive appserv-01-bootstrap \
--in sealed.bin \
--shared
ipa vault-show appserv-01-bootstrap --shared
Step 5 — Retrieve and deliver via Ansible
- name: Deliver sealed bootstrap artifact
hosts: appserv-01.idm.corp.lan
gather_facts: false
tasks:
- name: Retrieve sealed blob from IdM vault
ansible.builtin.set_fact:
sealed_artifact: "{{ lookup('eigenstate.ipa.vault',
'appserv-01-bootstrap',
server='idm-01.idm.corp.lan',
kerberos_keytab='/runner/env/ipa/admin.keytab',
shared=true,
encoding='base64',
result_format='record',
include_metadata=true,
verify='/etc/ipa/ca.crt') }}"
delegate_to: localhost
run_once: true
- name: Write sealed blob to target
ansible.builtin.copy:
content: "{{ sealed_artifact.value | b64decode }}"
dest: /var/lib/bootstrap/sealed.bin
mode: "0600"
owner: root
group: root
- name: Unseal artifact on target
ansible.builtin.command:
cmd: >
openssl cms -decrypt
-in /var/lib/bootstrap/sealed.bin
-recip /etc/pki/tls/certs/appserv-01-sealed-artifact.pem
-inkey /etc/pki/tls/private/appserv-01-sealed-artifact.key
-inform DER
-out /var/lib/bootstrap/artifact.bin
- name: Remove sealed blob
ansible.builtin.file:
path: /var/lib/bootstrap/sealed.bin
state: absent
Do not assume a cert at /etc/pki/tls/certs/ exists automatically just because
the host is enrolled. Verify the exact cert/key paths with getcert list or
ipa-getcert list and use those paths consistently.
If you are validating the decrypt step directly on the automation controller
instead of on the target host, make sure the play has permission to read the
certificate and private key. In the lab, controller-side decrypt on
the example controller required become: true because the host key material was
root-readable only.
flowchart LR
admin["One-time vault setup"]
prereq["Target cert/key exists"]
seal["Seal artifact"]
archive["Archive blob"]
retrieve["Automation retrieves blob"]
decrypt["Target decrypts"]
admin --> seal
prereq --> seal
seal --> archive --> retrieve --> decrypt
Use this when the operator does not have IdM admin rights. An admin creates the vault and delegates access once. All subsequent operations run under the operator’s own principal.
| Step | Requires IdM admin? | Who acts |
|---|---|---|
| Read host cert | No | Any authenticated principal |
| Seal artifact | No | Any operator |
| Create vault | Yes | IdM admin, one time |
| Delegate vault access | Yes | IdM admin, one time |
| Archive sealed blob | No | Delegated operator |
| Retrieve via Ansible | No | Service principal with vault membership |
| Unseal on host | No | Target host or local process |
Admin one-time setup
kinit admin
ipa vault-add appserv-01-bootstrap \
--type standard \
--shared
# Grant the delegated operator write access
ipa vault-add-member appserv-01-bootstrap \
--shared \
--users operator-principal
# Grant the automation service principal read access
ipa vault-add-member appserv-01-bootstrap \
--shared \
--services "HTTP/automation.idm.corp.lan"
Operator steps — no admin rights required after this point
# Authenticate as the delegated operator
kinit operator-principal
# Ensure the target cert/key pair exists first
getcert list
# Export or copy the host certificate using a method your environment supports
scp root@appserv-01.idm.corp.lan:/etc/pki/tls/certs/appserv-01-sealed-artifact.pem \
appserv-01.pem
# Seal the artifact locally
openssl cms -encrypt \
-in artifact.bin \
-out sealed.bin \
-recip appserv-01.pem \
-aes256 \
-binary \
-outform DER
# Archive to the vault — operator is a member, no admin ticket required
ipa vault-archive appserv-01-bootstrap \
--in sealed.bin \
--shared
The Ansible retrieval and unseal steps are identical to the admin path. The only difference is which principal the service keytab belongs to.
In this pattern, the collection returns the sealed artifact as a base64-encoded structured record with:
encoding='base64'result_format='record'include_metadata=trueand the resulting blob is only decryptable by the intended target that holds the matching private key.
[!NOTE] For a tighter ownership boundary, use a service-scoped vault tied to
host/appserv-01.idm.corp.lanrather than a shared vault. This limits retrieval to principals explicitly granted access to that service scope.
[!CAUTION] If the host certificate is renewed or replaced after the artifact is sealed, the sealed blob cannot be decrypted with the new key pair. Re-seal against the current cert and re-archive before the next delivery run.
Use the text-normalization helpers when the vault payload is text, but the consumer really wants structured data or newline-free values.
Typical cases:
flowchart LR
text["UTF-8 vault payload"] --> helper["decode_json or strip_trailing_newline"] --> task["task or template consumes normalized value"]
Why this pattern fits:
| Need | Best pattern |
|---|---|
| Simple centrally shared text secret | shared standard vault |
| Centrally shared secret with extra unlock material | shared symmetric vault |
| Secret gated by private-key possession | shared asymmetric vault |
| Principal-owned secret | user vault |
| Service-identity-owned secret | service vault |
| Binary artifact | any of the above with encoding='base64' |
| Inspect before retrieving | operation='show' or operation='find' |
| Brokered sealed artifact | consistent metadata convention plus include_metadata=true |
| Opaque encrypted artifact with routing context | encoding='base64' plus include_metadata=true |
| Parsed JSON or trimmed text | decode_json=true or strip_trailing_newline=true |
For option-level behavior, failure modes, and exact lookup syntax, return to IDM VAULT PLUGIN.