Related docs:
KEYTAB PLUGIN KEYTAB USE CASES IDM VAULT CAPABILITIES ROTATION CAPABILITIES AAP INTEGRATION DOCS MAP
Use this guide to choose the right Kerberos keytab retrieval pattern for your automation.
eigenstate.ipa.keytab fills the gap between service principal creation and
keytab deployment. Today that step is manual or handled by ad-hoc scripting.
This plugin brings it into the Ansible automation lifecycle.
flowchart LR
principal["Service or host principal"]
auth["Kerberos auth"]
lookup["eigenstate.ipa.keytab"]
getkeytab["ipa-getkeytab"]
idm["IdM / FreeIPA"]
out["Base64 keytab\nfor copy or injection"]
principal --> lookup
auth --> lookup
lookup --> getkeytab
getkeytab --> idm
idm --> out
Choose the pattern that matches:
When a service principal has just been created in IdM, it may not have keys
yet. Use retrieve_mode='generate' for the first keytab issuance.
flowchart LR
create["ipa service-add HTTP/webserver.example.com"]
issue["lookup(..., retrieve_mode='generate')"]
deploy["ansible.builtin.copy → /etc/httpd/conf/httpd.keytab"]
create --> issue --> deploy
[!WARNING]
generaterotates the principal’s keys. If any other service holds a keytab for this principal, it will be invalidated immediately. For first issuance of a brand-new principal this is safe because no prior keytabs exist.
Example — register service and issue keytab in one play:
- name: Add HTTP service principal
redhat.rhel_idm.ipaservice:
ipaadmin_password: "{{ lookup('env', 'IPA_PASSWORD') }}"
name: HTTP/webserver.idm.corp.lan
state: present
- name: Issue initial keytab
ansible.builtin.set_fact:
http_keytab_b64: "{{ lookup('eigenstate.ipa.keytab',
'HTTP/webserver.idm.corp.lan',
server='idm-01.idm.corp.lan',
kerberos_keytab='/runner/env/ipa/admin.keytab',
retrieve_mode='generate',
verify='/etc/ipa/ca.crt') }}"
Once a principal has keys, use the default retrieve_mode='retrieve'. This
only reads existing keys and never rotates them.
flowchart LR
existing["Existing IPA service principal\nwith keys already issued"]
retrieve["lookup(..., retrieve_mode='retrieve')"]
use["Inject keytab into new host\nor AAP credential"]
existing --> retrieve --> use
Example — retrieve without rotation:
- name: Retrieve NFS service keytab
ansible.builtin.set_fact:
nfs_keytab_b64: "{{ lookup('eigenstate.ipa.keytab',
'nfs/storage.idm.corp.lan',
server='idm-01.idm.corp.lan',
kerberos_keytab='/runner/env/ipa/admin.keytab',
verify='/etc/ipa/ca.crt') }}"
The call fails with a clear error if the principal has no keys yet. That is the right behavior: it prevents silent issuance of unexpected new keys in day-2 automation.
The plugin always returns base64-encoded content. Decode it before writing to
disk with the b64decode filter.
flowchart LR
lookup["lookup returns base64 keytab"]
decode["| b64decode"]
copy["ansible.builtin.copy → dest with mode 0600"]
service["Service reads keytab from disk"]
lookup --> decode --> copy --> service
Example — deploy HTTP keytab:
- name: Deploy HTTP service keytab
ansible.builtin.copy:
content: "{{ lookup('eigenstate.ipa.keytab',
'HTTP/webserver.idm.corp.lan',
server='idm-01.idm.corp.lan',
kerberos_keytab='/runner/env/ipa/admin.keytab',
verify='/etc/ipa/ca.crt') | b64decode }}"
dest: /etc/httpd/conf/httpd.keytab
mode: "0600"
owner: apache
group: apache
Keytab files should always be deployed with mode: "0600" and owned by the
service user. A world-readable keytab is a Kerberos impersonation risk.
When the same play must deploy keytabs to multiple services, retrieve them all
in one query call with result_format='map' and address each by principal
name.
flowchart LR
lookup["One query\nresult_format='map'"]
web01["web-01 keytab"]
web02["web-02 keytab"]
lookup --> web01
lookup --> web02
Example — retrieve and iterate:
- name: Retrieve keytabs for all web services
ansible.builtin.set_fact:
web_keytabs: "{{ query('eigenstate.ipa.keytab',
'HTTP/web-01.idm.corp.lan',
'HTTP/web-02.idm.corp.lan',
server='idm-01.idm.corp.lan',
kerberos_keytab='/runner/env/ipa/admin.keytab',
result_format='map',
verify='/etc/ipa/ca.crt') | first }}"
- name: Deploy keytab to each web server
ansible.builtin.copy:
content: "{{ web_keytabs['HTTP/' ~ inventory_hostname ~ '.idm.corp.lan'] | b64decode }}"
dest: /etc/httpd/conf/httpd.keytab
mode: "0600"
owner: apache
group: apache
delegate_to: "{{ item }}"
loop: "{{ groups['webservers'] }}"
[!NOTE]
result_format='map'returns a one-element list containing the dict. Use| firstto unwrap it before subscripting by principal name. The validated structured-return path for keytabs isquery(...), not barelookup(...).
Key rotation invalidates all existing keytabs for a principal. Plan the rotation so that the new keytab is deployed to all consuming services in the same play before anything tries to re-authenticate.
flowchart LR
rotate["lookup(..., retrieve_mode='generate')\nRotates keys — old keytabs immediately invalid"]
deploy["Deploy new keytab to all consumers\nbefore they re-authenticate"]
verify["klist -kt /path/to/new.keytab\nKinit to verify new keys work"]
rotate --> deploy --> verify
Example — rotate and redeploy in one play:
- name: Rotate HTTP service keytab
ansible.builtin.set_fact:
new_http_keytab_b64: "{{ lookup('eigenstate.ipa.keytab',
'HTTP/webserver.idm.corp.lan',
server='idm-01.idm.corp.lan',
kerberos_keytab='/runner/env/ipa/admin.keytab',
retrieve_mode='generate',
verify='/etc/ipa/ca.crt') }}"
- name: Deploy rotated keytab
ansible.builtin.copy:
content: "{{ new_http_keytab_b64 | b64decode }}"
dest: /etc/httpd/conf/httpd.keytab
mode: "0600"
owner: apache
group: apache
notify: Restart httpd
[!WARNING] If the play fails between the rotation step and the deploy step, services will be holding an invalidated keytab. Always run rotation and deployment in the same play with no blocking tasks in between.
In environments with strict cryptographic policy, restrict the keytab to
specific Kerberos encryption types using enctypes.
- name: Retrieve AES-256-only keytab for compliance
ansible.builtin.set_fact:
keytab_b64: "{{ lookup('eigenstate.ipa.keytab',
'HTTP/webserver.idm.corp.lan',
server='idm-01.idm.corp.lan',
kerberos_keytab='/runner/env/ipa/admin.keytab',
enctypes=['aes256-cts'],
verify='/etc/ipa/ca.crt') }}"
Typical enctypes values:
| Value | Algorithm |
|---|---|
aes256-cts |
AES-256-CTS-HMAC-SHA1-96 |
aes128-cts |
AES-128-CTS-HMAC-SHA1-96 |
aes256-sha2 |
AES-256-CTS-HMAC-SHA384-192 (RFC 8009) |
aes128-sha2 |
AES-128-CTS-HMAC-SHA256-128 (RFC 8009) |
Empty enctypes delegates encryption type selection to the IPA server policy.
That is the right default for most environments.
If the workload fits Kerberos, keytabs give the collection a stronger answer to short-lived machine credentials than ordinary static passwords.
flowchart LR
issue["retrieve or generate keytab"] --> ticket["use keytab to obtain Kerberos tickets"]
ticket --> work["perform controller-side work"]
work --> retire["rotate principal keys again"]
retire --> dead["prior keytab material invalidated"]
Why this matters:
principal and keytab together let automation inspect and drive that lifecycleBoundary:
This is the right mental model when you want a stronger machine-identity story without claiming dynamic-secret semantics that IdM does not provide.
The recommended AAP deployment pattern is:
kerberos_keytab at that mounted pathipa-getkeytab in the EE package list
(on the validated RHEL 10 path, that is ipa-client)flowchart LR
cred["Controller keytab"]
ee["Execution environment"]
lookup["eigenstate.ipa.keytab"]
idm["IdM / FreeIPA"]
target["Service keytab on target"]
cred --> ee
ee --> lookup
lookup --> idm
idm --> target
EE package list additions:
dependencies:
system:
- ipa-client # RHEL 10 validated path
- krb5-workstation
On the validated RHEL 10 path, this gives the EE the ipa-getkeytab binary
and kinit. On other releases, install the package that provides
/usr/sbin/ipa-getkeytab. This avoids pulling in the full
python3-ipaclient stack that the vault plugin requires.
A service cannot retrieve its own secrets from IdM vault until it has a valid Kerberos credential. The keytab plugin is what delivers that credential. The two plugins chain: the keytab lookup authenticates using the admin keytab, retrieves the service keytab, and that service keytab is then used as the authenticating credential for a subsequent vault lookup scoped to the same service principal.
flowchart LR
admin_kt["Admin keytab"]
keytab_lookup["Keytab lookup"]
svc_kt["Service keytab"]
vault_lookup["Vault lookup"]
blob["Sealed bundle"]
target["Target decrypts locally"]
admin_kt --> keytab_lookup
keytab_lookup --> svc_kt
svc_kt --> vault_lookup
vault_lookup --> blob
blob --> target
This matters for the audit trail. When the vault lookup authenticates as
HTTP/app.example.com, IdM records that the service principal retrieved its
own secrets — not that an admin retrieved secrets on its behalf.
The vault must exist and the service principal must have membership in it before the bootstrap play runs.
kinit admin
# Create a service-scoped vault owned by the service principal
ipa vault-add app-bootstrap-bundle \
--type standard \
--service HTTP/app.example.com
# Or use a shared vault and grant the service access to it
ipa vault-add app-bootstrap-bundle --type standard --shared
ipa vault-add-member app-bootstrap-bundle \
--shared \
--services "HTTP/app.example.com"
# Seal the bootstrap payload against the target host certificate and archive it
openssl cms -encrypt \
-aes-256-cbc \
-in bootstrap-bundle.tar.gz \
-out bootstrap-bundle.cms \
-outform DER \
-recip /etc/pki/tls/certs/app.example.com.pem
ipa vault-archive app-bootstrap-bundle \
--service HTTP/app.example.com \
--in bootstrap-bundle.cms
See IdM Vault Capabilities — Section 11 for the full sealing workflow including certmonger prerequisites and decryption commands.
- name: Retrieve service keytab for HTTP service
ansible.builtin.set_fact:
app_keytab_b64: "{{ lookup('eigenstate.ipa.keytab',
'HTTP/app.example.com',
server='idm-01.idm.corp.lan',
kerberos_keytab='/runner/env/ipa/admin.keytab',
verify='/etc/ipa/ca.crt') }}"
- name: Write service keytab to temporary path
ansible.builtin.copy:
content: "{{ app_keytab_b64 | b64decode }}"
dest: /tmp/app-svc.keytab
mode: "0600"
- name: Retrieve sealed bootstrap bundle from service-scoped vault
ansible.builtin.set_fact:
sealed_bundle_b64: "{{ lookup('eigenstate.ipa.vault',
'app-bootstrap-bundle',
server='idm-01.idm.corp.lan',
kerberos_keytab='/tmp/app-svc.keytab',
service='HTTP/app.example.com',
encoding='base64',
verify='/etc/ipa/ca.crt') }}"
- name: Deploy sealed bundle to target
ansible.builtin.copy:
content: "{{ sealed_bundle_b64 | b64decode }}"
dest: /tmp/bootstrap-bundle.cms
mode: "0600"
delegate_to: app.example.com
- name: Unseal bootstrap bundle on target
ansible.builtin.command:
argv:
- openssl
- cms
- -decrypt
- -in
- /tmp/bootstrap-bundle.cms
- -inform
- DER
- -inkey
- /etc/pki/tls/private/app.example.com.key
- -recip
- /etc/pki/tls/certs/app.example.com.pem
- -out
- /tmp/bootstrap-bundle.tar.gz
delegate_to: app.example.com
- name: Remove temporary service keytab from control node
ansible.builtin.file:
path: /tmp/app-svc.keytab
state: absent
- name: Remove sealed blob from target
ansible.builtin.file:
path: /tmp/bootstrap-bundle.cms
state: absent
delegate_to: app.example.com
[!NOTE] The service keytab at
/tmp/app-svc.keytabis only needed for the vault retrieval step. Remove it as soon as the sealed bundle has been delivered. It should never be deployed to disk as a persistent credential on the control node or execution environment.
[!NOTE] If the service principal has no keys yet, the keytab lookup will fail in
retrievemode. Useretrieve_mode='generate'for the initial keytab issuance, then switch toretrievefor subsequent runs. See Section 1.
| Goal | Pattern |
|---|---|
| First keytab for a brand-new principal | retrieve_mode='generate' — generates initial keys |
| Re-deploy keytab to a rebuilt host | retrieve_mode='retrieve' (default) — existing keys, no rotation |
| Explicit key rotation | retrieve_mode='generate' — rotate and redeploy in same play |
| Deploy to one host | result_format='value' + b64decode in copy task |
| Deploy to multiple services | result_format='map' + iterate with principal names |
| Compliance enctype restriction | enctypes=['aes256-cts'] or similar |
| Non-interactive auth in AAP | kerberos_keytab pointing to Controller-mounted keytab |
| Full service bootstrap with vault-delivered secrets | Admin keytab → eigenstate.ipa.keytab → service keytab → eigenstate.ipa.vault as service principal |