Related docs:
VAULT WRITE CAPABILITIES VAULT WRITE USE CASES IDM VAULT PLUGIN DOCS MAP
eigenstate.ipa.vault_write manages the lifecycle of FreeIPA/IdM vaults from
Ansible — create, archive, modify, and delete.
This reference covers:
For the complementary read path, see IDM VAULT PLUGIN.
flowchart LR
module["eigenstate.ipa.vault_write"]
auth["Kerberos ticket\nexisting, password-derived, or keytab-derived"]
api["ipalib vault APIs"]
vaults["IdM vaults\nuser, service, shared"]
result["Return: changed + vault state"]
module --> auth
auth --> api
api --> vaults
vaults --> result
The module uses the same ipalib-based connection stack as the vault lookup
plugin. Kerberos credential handling and ipalib bootstrap are managed by the
shared module_utils/ipa_client.py layer.
The module always operates with a Kerberos credential cache.
It can get there in three ways:
ipaadmin_password:
kerberos_keytab:
[!IMPORTANT] The module requires
python3-ipalibandpython3-ipaclienton the Ansible controller or inside the Execution Environment. Sensitive files (kerberos_keytab,vault_password_file,vault_public_key_file) should have mode0600or more restrictive. The module warns when it detects broader permissions.
TLS behavior:
verify: /path/to/ca.crt enables explicit certificate verificationverify: false disables verification explicitly, with a warningverify first tries /etc/ipa/ca.crtSelect exactly one vault scope:
usernameserviceshared: trueIf none is selected, the module uses the default scope behavior of the IdM API for the authenticated principal.
In practice:
shared: true for estate-wide automation secretsservice when a service principal owns the vaultusername for principal-scoped private vaultsstate: present (default)Ensures the vault exists. Creates it if absent. Updates description if
changed. Does not change the vault type of an existing vault.
state: absentRemoves the vault. No-op if the vault does not exist.
state: archivedEnsures the vault exists (equivalent to state: present first), then
stores the supplied payload in it.
For standard vaults: retrieves the current payload and compares it to the incoming payload. Skips the archive step if the payloads are identical.
For symmetric and asymmetric vaults: always archives. The current
ciphertext cannot be safely compared without decryption, so the module
treats these vault types as write-always for state: archived. Document this
behavior in rotation automation that needs to reason about change tracking.
No encryption beyond IdM authorization. The vault accepts any bytes as payload. Retrieval requires only a valid Kerberos principal with access.
Use for most secrets: passwords, tokens, configuration blobs, certificates, and keytabs.
The vault encrypts the payload with a password. Creating, archiving, and
retrieving all require the password (vault_password or
vault_password_file).
Use when an additional control layer is required beyond IdM authorization.
The vault encrypts the payload with an RSA public key. Retrieval requires the
corresponding private key. The public key is supplied at vault creation time
only (vault_public_key or vault_public_key_file).
Use for sealed artifact storage where retrieval should be gated by private-key possession.
| State | Change condition |
|---|---|
present |
vault did not exist, or description differed |
absent |
vault existed |
archived (standard) |
vault did not exist, or payload differed from current content |
archived (symmetric/asymmetric) |
vault did not exist, or state: archived was requested |
[!NOTE] Symmetric and asymmetric vaults report
changed: trueon everyarchivedrun because the module cannot compare the current encrypted payload without the decryption key. See VAULT WRITE CAPABILITIES for operational patterns that account for this.
members and members_absent operate as delta-only adjustments. The module
reads the current member list from IdM and only adds or removes the
principals that need to change.
members: add these principals if not already membersmembers_absent: remove these principals if currently membersBoth lists accept user, group, and service principal names. Member
reconciliation runs after the state operation completes, during any run where
the vault exists (or was just created).
The module classifies each entry and passes it to the matching IPA member
argument set before making the member API call. On the live IPA API that
means user, group, and services.
Benign errors from IdM — “already a member” or “not a member” — are silently filtered. Only unexpected failures cause the module to fail.
changed: true | false
vault:
name: string
scope: string # "shared", "username=X", "service=X", or "default"
type: string # standard | symmetric | asymmetric
description: string # empty string when not set
members: list[str] # sorted list of member principals
owners: list[str] # sorted list of owner principals
vault is returned for state: present and state: archived. It is not
returned for state: absent.
In check mode, the vault field reflects the projected state after the
operation, not the actual current state.
| Option | Required | Default | Description |
|---|---|---|---|
name |
yes | — | Vault name |
state |
no | present |
present, absent, or archived |
server |
yes | $IPA_SERVER |
FQDN of the IPA server |
ipaadmin_principal |
no | admin |
Kerberos principal to authenticate as |
ipaadmin_password |
no | $IPA_ADMIN_PASSWORD |
Password for the principal |
kerberos_keytab |
no | $IPA_KEYTAB |
Path to a keytab file; takes precedence over password |
verify |
no | $IPA_CERT → /etc/ipa/ca.crt |
Path to IPA CA certificate for TLS verification |
username |
no | — | User vault scope; mutually exclusive with service and shared |
service |
no | — | Service vault scope; mutually exclusive with username and shared |
shared |
no | false |
Shared vault scope; mutually exclusive with username and service |
vault_type |
no | standard |
standard, symmetric, or asymmetric; only applies at creation |
description |
no | — | Human-readable vault description |
vault_public_key |
no | — | RSA public key PEM string; for asymmetric vaults; mutually exclusive with vault_public_key_file |
vault_public_key_file |
no | — | Path to RSA public key PEM file; mutually exclusive with vault_public_key |
data |
no | — | Secret payload to archive; no_log; mutually exclusive with data_file |
data_file |
no | — | Path to file to archive as payload; mutually exclusive with data |
vault_password |
no | — | Symmetric vault password for create/archive; no_log; mutually exclusive with vault_password_file |
vault_password_file |
no | — | Path to file containing symmetric vault password; mutually exclusive with vault_password |
members |
no | [] |
Principals to ensure are vault members (delta-only) |
members_absent |
no | [] |
Principals to ensure are not vault members (delta-only) |
Create a standard shared vault:
- name: Ensure vault exists
eigenstate.ipa.vault_write:
name: rotation-target
state: present
shared: true
description: "Credential targeted for rotation automation"
server: idm-01.example.com
kerberos_keytab: /etc/ipa/automation.keytab
ipaadmin_principal: automation@EXAMPLE.COM
Archive a secret into a standard vault:
- name: Archive database password
eigenstate.ipa.vault_write:
name: db-password
state: archived
shared: true
data: "{{ new_db_password }}"
server: idm-01.example.com
ipaadmin_password: "{{ lookup('env', 'IPA_ADMIN_PASSWORD') }}"
Rotation pattern — retrieve current, generate new, archive back:
- name: Retrieve current secret
ansible.builtin.set_fact:
current_secret: >-
{{ lookup('eigenstate.ipa.vault', 'app-secret',
server='idm-01.example.com',
shared=true,
ipaadmin_password=ipa_password) }}
- name: Generate new secret
ansible.builtin.set_fact:
new_secret: "{{ lookup('community.general.random_string', length=32) }}"
- name: Archive rotated secret
eigenstate.ipa.vault_write:
name: app-secret
state: archived
shared: true
data: "{{ new_secret }}"
server: idm-01.example.com
ipaadmin_password: "{{ ipa_password }}"
Create a symmetric vault:
- name: Create symmetric vault
eigenstate.ipa.vault_write:
name: encrypted-credential
state: present
vault_type: symmetric
shared: true
vault_password: "{{ vault_encryption_password }}"
server: idm-01.example.com
ipaadmin_password: "{{ ipa_password }}"
- name: Archive into symmetric vault
eigenstate.ipa.vault_write:
name: encrypted-credential
state: archived
shared: true
data: "{{ sensitive_value }}"
vault_password: "{{ vault_encryption_password }}"
server: idm-01.example.com
ipaadmin_password: "{{ ipa_password }}"
Create an asymmetric vault for sealed artifact storage:
- name: Create asymmetric vault
eigenstate.ipa.vault_write:
name: cert-private-key
state: present
vault_type: asymmetric
vault_public_key_file: /etc/pki/ipa/automation-public.pem
shared: true
description: "Private key sealed after certificate issuance"
server: idm-01.example.com
kerberos_keytab: /etc/ipa/automation.keytab
ipaadmin_principal: automation@EXAMPLE.COM
Add a service account as a vault member:
- name: Grant app-server read access
eigenstate.ipa.vault_write:
name: app-secret
state: present
shared: true
members:
- HTTP/app01.example.com@EXAMPLE.COM
server: idm-01.example.com
ipaadmin_password: "{{ ipa_password }}"
Delete a vault:
- name: Remove decommissioned vault
eigenstate.ipa.vault_write:
name: old-service-credential
state: absent
shared: true
server: idm-01.example.com
ipaadmin_password: "{{ ipa_password }}"
Check mode pre-flight for rotation:
- name: Preview rotation impact
eigenstate.ipa.vault_write:
name: app-secret
state: archived
shared: true
data: "{{ candidate_secret }}"
server: idm-01.example.com
ipaadmin_password: "{{ ipa_password }}"
check_mode: true
register: rotation_preview
Common failure classes:
ipalib libraries on the controller or in the Execution Environmentvault_password or vault_password_file when creating or
archiving a symmetric vaultvault_public_key or vault_public_key_file for a new asymmetric vaultstate: archived called without data or data_fileEmptyModlist from IdM when no property actually changed; the module treats
this as changed: false[!NOTE] An ownership-scope mismatch is the most common source of unexpected not-found failures. If a vault exists in IdM but the module reports it does not, recheck
username,service, andsharedfirst.