Related docs:
PRINCIPAL PLUGIN PRINCIPAL CAPABILITIES IDM VAULT USE CASES DOCS MAP
This page contains worked examples for eigenstate.ipa.principal against
FreeIPA/IdM.
Use the capability guide to choose the right pattern. Use this page when you need the corresponding playbook.
flowchart LR
need["State to verify"]
single["Show one principal"]
bulk["Show many\nor find by type"]
gate["Assert or report"]
act["Continue with keytab,\ncert, or conditional logic"]
need --> single
need --> bulk
single --> gate
bulk --> gate
gate --> act
Verify that a service principal exists and has Kerberos keys registered in IdM before issuing a keytab. Without this check, a missing principal or an unkeyed principal produces a silent failure downstream.
flowchart LR
check["principal show\nHTTP/web01.corp.example.com"] --> exists{"exists?"}
exists -->|no| fail["fail — service not registered in IdM"]
exists -->|yes| keys{"has_keytab?"}
keys -->|no| fail2["fail — no keys yet, cannot issue keytab"]
keys -->|yes| pass["proceed"]
- name: Pre-flight check before issuing keytab
hosts: localhost
gather_facts: false
tasks:
- name: Check service principal state
ansible.builtin.set_fact:
principal_state: "{{ lookup('eigenstate.ipa.principal',
'HTTP/web01.corp.example.com',
server='idm-01.corp.example.com',
kerberos_keytab='/runner/env/ipa/admin.keytab',
verify='/etc/ipa/ca.crt') }}"
- name: Assert principal is ready
ansible.builtin.assert:
that:
- principal_state.exists
- principal_state.has_keytab
fail_msg: >-
Service principal HTTP/web01.corp.example.com is
{{ 'missing' if not principal_state.exists else 'present but has no keys' }}.
Register the principal in IdM before issuing a keytab.
Confirm a host is enrolled in IdM and its principal has keys before requesting a certificate or distributing a keytab.
flowchart LR
check["principal show\nhost/node01.corp.example.com"] --> enrolled{"exists?"}
enrolled -->|no| fail["fail — host not enrolled"]
enrolled -->|yes| keys{"has_keytab?"}
keys -->|no| warn["warn — enrolled but no keys yet"]
keys -->|yes| next["proceed to cert or keytab work"]
- name: Verify host enrollment before cert request
hosts: localhost
gather_facts: false
tasks:
- name: Check host principal
ansible.builtin.set_fact:
host_state: "{{ lookup('eigenstate.ipa.principal',
'host/node01.corp.example.com',
server='idm-01.corp.example.com',
kerberos_keytab='/runner/env/ipa/admin.keytab',
verify='/etc/ipa/ca.crt') }}"
- name: Fail if not enrolled
ansible.builtin.fail:
msg: >-
host/node01.corp.example.com is not enrolled in IdM.
Run ipa-client-install on the target before requesting a cert.
when: not host_state.exists
- name: Warn if enrolled but no keys yet
ansible.builtin.debug:
msg: >-
Warning: host/node01.corp.example.com is enrolled but has no
Kerberos keys. Keytab issuance may not be possible yet.
when: host_state.exists and not host_state.has_keytab
Confirm a service account is active before a play runs automation under that account or grants it access. A locked account will fail at the Kerberos auth step; catching this earlier produces a clearer error.
- name: Confirm deployment service account is active
hosts: localhost
gather_facts: false
tasks:
- name: Check service account state
ansible.builtin.set_fact:
svc_state: "{{ lookup('eigenstate.ipa.principal',
'svc-deploy',
server='idm-01.corp.example.com',
kerberos_keytab='/runner/env/ipa/admin.keytab',
verify='/etc/ipa/ca.crt') }}"
- name: Fail if account is disabled
ansible.builtin.fail:
msg: >-
Service account svc-deploy is disabled in IdM.
Re-enable it before running the deployment job.
when: svc_state.disabled | bool
- name: Warn if last auth is unknown
ansible.builtin.debug:
msg: >-
Warning: no last_auth timestamp for svc-deploy.
IdM audit logging may not be enabled on this server.
when: svc_state.last_auth is none
- name: Show last authentication timestamp
ansible.builtin.debug:
msg: "svc-deploy last authenticated: {{ svc_state.last_auth }}"
when: svc_state.last_auth is not none
Use operation=find to enumerate all service principals and report which
ones do not yet have keytab keys. This is useful for day-2 audits and
pre-release gates.
- name: Audit service principals for missing keytabs
hosts: localhost
gather_facts: false
tasks:
- name: Find all service principals
ansible.builtin.set_fact:
all_services: "{{ lookup('eigenstate.ipa.principal',
server='idm-01.corp.example.com',
kerberos_keytab='/runner/env/ipa/admin.keytab',
operation='find',
principal_type='service',
verify='/etc/ipa/ca.crt') }}"
- name: Identify services without keys
ansible.builtin.set_fact:
unkeyed: "{{ all_services | selectattr('has_keytab', 'equalto', false) | list }}"
- name: Report unkeyed services
ansible.builtin.debug:
msg: "Service principal without keys: {{ item.canonical }}"
loop: "{{ unkeyed }}"
when: unkeyed | length > 0
- name: Fail if any service is unkeyed
ansible.builtin.fail:
msg: >-
{{ unkeyed | length }} service principal(s) have no keytab keys.
Issue keytabs for: {{ unkeyed | map(attribute='canonical') | join(', ') }}
when: unkeyed | length > 0
To audit hosts instead of services, change principal_type='service' to
principal_type='host'.
Use a single assert task to block an entire pipeline when any required
principal is absent. The map_record format makes each principal accessible
by name rather than by position.
- name: Pipeline pre-flight — confirm all required principals
hosts: localhost
gather_facts: false
vars:
required_principals:
- HTTP/web01.corp.example.com
- ldap/ldap01.corp.example.com
- nfs/nfs01.corp.example.com
tasks:
- name: Check all required service principals
ansible.builtin.set_fact:
principal_states: "{{ lookup('eigenstate.ipa.principal',
*required_principals,
server='idm-01.corp.example.com',
kerberos_keytab='/runner/env/ipa/admin.keytab',
result_format='map_record',
verify='/etc/ipa/ca.crt') }}"
- name: Assert all principals exist and have keys
ansible.builtin.assert:
that:
- principal_states[item].exists
- principal_states[item].has_keytab
fail_msg: >-
Principal {{ item }} is
{{ 'missing from IdM' if not principal_states[item].exists
else 'registered but has no keys' }}.
loop: "{{ required_principals }}"
When the list of services is not known until runtime, build the principal names dynamically from inventory and check them all in one lookup.
- name: Pre-flight check for all web tier services
hosts: localhost
gather_facts: false
tasks:
- name: Build service principal names from web hosts
ansible.builtin.set_fact:
web_principals: >-
{{ groups['web'] | map('regex_replace', '^(.*)$', 'HTTP/\1') | list }}
- name: Check all web service principals
ansible.builtin.set_fact:
web_states: "{{ lookup('eigenstate.ipa.principal',
*web_principals,
server='idm-01.corp.example.com',
kerberos_keytab='/runner/env/ipa/admin.keytab',
result_format='map_record',
verify='/etc/ipa/ca.crt') }}"
- name: Fail for any missing principal
ansible.builtin.fail:
msg: "Service principal {{ item }} is not registered in IdM."
loop: "{{ web_principals }}"
when: not web_states[item].exists
Combine eigenstate.ipa.principal and eigenstate.ipa.keytab in the same
play. The principal check gates the keytab retrieval and produces a clear
error when the service is not ready.
- name: Distribute service keytab with pre-flight check
hosts: web
gather_facts: false
tasks:
- name: Pre-flight — check service principal
ansible.builtin.set_fact:
svc_state: "{{ lookup('eigenstate.ipa.principal',
'HTTP/' + inventory_hostname,
server='idm-01.corp.example.com',
kerberos_keytab='/runner/env/ipa/admin.keytab',
verify='/etc/ipa/ca.crt') }}"
delegate_to: localhost
- name: Abort if service principal is not ready
ansible.builtin.assert:
that:
- svc_state.exists
- svc_state.has_keytab
fail_msg: >-
HTTP/{{ inventory_hostname }} is
{{ 'not registered in IdM' if not svc_state.exists
else 'registered but has no keys' }}.
Cannot retrieve a keytab until the principal has keys.
- name: Retrieve and install keytab
ansible.builtin.copy:
content: "{{ lookup('eigenstate.ipa.keytab',
'HTTP/' + inventory_hostname,
server='idm-01.corp.example.com',
kerberos_keytab='/runner/env/ipa/admin.keytab',
retrieve_mode='retrieve',
verify='/etc/ipa/ca.crt') | b64decode }}"
dest: /etc/http.keytab
mode: "0600"
owner: apache
group: apache
become: true
[!NOTE] The principal pre-flight check only proves the target principal exists and has keys. The caller still needs IdM retrieve-keytab rights for the keytab lookup itself.
Combine eigenstate.ipa.principal and eigenstate.ipa.cert when issuing a
certificate for a host. The principal check confirms IdM enrollment before
submitting a CSR to the CA.
- name: Request host certificate with enrollment pre-flight
hosts: appservers
gather_facts: false
tasks:
- name: Pre-flight — confirm host is enrolled in IdM
ansible.builtin.set_fact:
host_state: "{{ lookup('eigenstate.ipa.principal',
'host/' + inventory_hostname,
server='idm-01.corp.example.com',
kerberos_keytab='/runner/env/ipa/admin.keytab',
verify='/etc/ipa/ca.crt') }}"
delegate_to: localhost
- name: Abort if host is not enrolled
ansible.builtin.fail:
msg: >-
host/{{ inventory_hostname }} is not enrolled in IdM.
Run ipa-client-install before requesting a certificate.
when: not host_state.exists
- name: Generate private key
community.crypto.openssl_privatekey:
path: /etc/pki/tls/private/{{ inventory_hostname }}.key
size: 4096
become: true
- name: Generate CSR
community.crypto.openssl_csr:
path: /etc/pki/tls/certs/{{ inventory_hostname }}.csr
privatekey_path: /etc/pki/tls/private/{{ inventory_hostname }}.key
common_name: "{{ inventory_hostname }}"
become: true
- name: Read CSR content
ansible.builtin.slurp:
src: /etc/pki/tls/certs/{{ inventory_hostname }}.csr
register: csr_content
become: true
- name: Request certificate from IdM CA
ansible.builtin.set_fact:
issued_cert: "{{ lookup('eigenstate.ipa.cert',
'host/' + inventory_hostname,
server='idm-01.corp.example.com',
kerberos_keytab='/runner/env/ipa/admin.keytab',
operation='request',
csr=csr_content.content | b64decode,
verify='/etc/ipa/ca.crt') }}"
delegate_to: localhost
- name: Install certificate
ansible.builtin.copy:
content: "{{ issued_cert }}"
dest: /etc/pki/tls/certs/{{ inventory_hostname }}.pem
mode: "0644"
become: true
Use eigenstate.ipa.principal as the post-enrollment verification step in a
bootstrap play. A successful ipa-client-install does not always mean the
IdM record is in the expected state; verify before moving on.
- name: Bootstrap and verify IdM enrollment
hosts: new_hosts
gather_facts: false
tasks:
- name: Run ipa-client-install
ansible.builtin.command:
cmd: >
ipa-client-install
--server idm-01.corp.example.com
--domain corp.example.com
--principal admin
--password {{ ipa_admin_password }}
--unattended
become: true
no_log: true
- name: Wait briefly for IdM to register the host record
ansible.builtin.pause:
seconds: 10
- name: Verify host is registered in IdM
ansible.builtin.set_fact:
host_state: "{{ lookup('eigenstate.ipa.principal',
'host/' + inventory_hostname,
server='idm-01.corp.example.com',
kerberos_keytab='/runner/env/ipa/admin.keytab',
verify='/etc/ipa/ca.crt') }}"
delegate_to: localhost
- name: Fail if host is not in IdM
ansible.builtin.fail:
msg: >-
ipa-client-install reported success but
host/{{ inventory_hostname }} is not visible in IdM.
Check the IPA client install log on the target.
when: not host_state.exists
- name: Report enrollment state
ansible.builtin.debug:
msg: >-
host/{{ inventory_hostname }} enrolled successfully.
has_keytab={{ host_state.has_keytab }}
Use map_record format to build a structured audit report of all service
principals, keyed by canonical principal name. This is suitable for a
scheduled compliance job that writes a report rather than immediately failing.
- name: Weekly service principal audit
hosts: localhost
gather_facts: false
tasks:
- name: Find all service principals
ansible.builtin.set_fact:
all_services: "{{ lookup('eigenstate.ipa.principal',
server='idm-01.corp.example.com',
kerberos_keytab='/runner/env/ipa/admin.keytab',
operation='find',
principal_type='service',
result_format='map_record',
verify='/etc/ipa/ca.crt') }}"
- name: Summarize key state by service
ansible.builtin.debug:
msg: >-
{{ item.key }}:
exists={{ item.value.exists }},
has_keytab={{ item.value.has_keytab }}
loop: "{{ all_services | dict2items }}"
- name: Build list of unkeyed services for report
ansible.builtin.set_fact:
unkeyed_services: >-
{{ all_services | dict2items
| selectattr('value.has_keytab', 'equalto', false)
| map(attribute='key') | list }}
- name: Write audit report
ansible.builtin.copy:
content: |
Service Principal Keytab Audit
Date: {{ ansible_date_time.iso8601 | default(lookup('pipe', 'date -Iseconds')) }}
Total principals: {{ all_services | length }}
Missing keytabs: {{ unkeyed_services | length }}
{% if unkeyed_services %}
Principals without keys:
{% for svc in unkeyed_services %}
- {{ svc }}
{% endfor %}
{% else %}
All service principals have keytab keys.
{% endif %}
dest: /var/log/ipa-principal-audit.txt
mode: "0640"