eigenstate-ipa

Principal Plugin

Related docs:

  PRINCIPAL CAPABILITIES     PRINCIPAL USE CASES     IDM VAULT PLUGIN     AAP INTEGRATION     DOCS MAP  

Purpose

eigenstate.ipa.principal queries Kerberos principal state from FreeIPA/IdM from Ansible.

This reference covers:

The principal does not need to be a global IdM administrator. It only needs rights to query the specific object types it will inspect.

Contents

Lookup Model

flowchart LR
    task["Ansible task or assert"]
    auth["Kerberos auth"]
    lookup["eigenstate.ipa.principal"]
    ipa["ipalib principal APIs"]
    out["State record\nexists, has_keytab, disabled, last_auth"]

    task --> lookup
    auth --> lookup
    lookup --> ipa
    ipa --> out

The lookup uses the IdM Python client libraries directly through ipalib. All queries go through the same authenticated RPC transport as the vault and keytab plugins.

Authentication Model

The lookup always operates with a Kerberos credential cache.

It can get there in three ways:

[!IMPORTANT] This plugin requires python3-ipalib and python3-ipaclient on the Ansible controller or execution environment. Install with dnf install python3-ipalib python3-ipaclient.

TLS behavior:

Principal Type Detection

When principal_type is left at the default auto, the plugin infers the IdM object type from the principal name format:

Name form Detected type Lookup argument
HTTP/web.example.com service full name as-is
HTTP/web.example.com@REALM service name stripped of realm
host/web.example.com host FQDN after host/
host/web.example.com@REALM host FQDN after host/, realm stripped
admin user uid as-is
admin@REALM user uid before @
flowchart LR
    name["Principal name"]
    slash{"Contains /?"}
    hostpfx{"Starts with host/?"}
    svc["Service principal"]
    host["Host principal"]
    user["User principal"]

    name --> slash
    slash -->|yes| hostpfx
    slash -->|no| user
    hostpfx -->|yes| host
    hostpfx -->|no| svc

When principal_type is set explicitly to user, host, or service, detection is skipped and the name is handled accordingly.

find with principal_type=auto raises an error. You must specify the type explicitly for find operations.

Operations

The lookup supports two operations:

Result Record Fields

Each state record contains:

Field Type Notes
name str Input term as given
canonical str Full principal with @REALM; for hosts, host/fqdn@REALM when available
type str user, host, or service
exists bool false when IdM returned NotFound
has_keytab bool false when exists=false
disabled bool or null true when nsaccountlock=true (users only); null for host and service principals
last_auth str or null ISO 8601 timestamp from krblastsuccessfulauth (users only, requires IdM audit enabled); null otherwise

Return Shapes

record (default)

Returns a list of state dictionaries, one per input principal:

principal_states: "{{ lookup('eigenstate.ipa.principal',
                       'HTTP/web01.example.com',
                       'ldap/ldap01.example.com',
                       server='idm-01.example.com',
                       kerberos_keytab='/etc/admin.keytab') }}"
# principal_states[0].name  == 'HTTP/web01.example.com'
# principal_states[1].name  == 'ldap/ldap01.example.com'

For a single principal, lookup(...) returns the record directly as a mapping. query(...) returns a one-element list, so use | first there if you want the record itself.

map_record

Returns a dictionary keyed by input principal name:

state_map: "{{ lookup('eigenstate.ipa.principal',
                'HTTP/web01.example.com',
                'ldap/ldap01.example.com',
                server='idm-01.example.com',
                kerberos_keytab='/etc/admin.keytab',
                result_format='map_record') }}"
# state_map['HTTP/web01.example.com'].exists  == true
# state_map['ldap/ldap01.example.com'].exists == false

The map form is useful when several principals are checked in one call and the playbook should not depend on positional list ordering.

Minimal Examples

Single service principal existence check:

- ansible.builtin.assert:
    that:
      - principal_state.exists
      - principal_state.has_keytab
    fail_msg: "Service principal missing or has no keys"
  vars:
    principal_state: "{{ lookup('eigenstate.ipa.principal',
                          'HTTP/web01.corp.example.com',
                          server='idm-01.corp.example.com',
                          ipaadmin_password=lookup('env', 'IPA_ADMIN_PASSWORD'),
                          verify='/etc/ipa/ca.crt') }}"

Host enrollment check before requesting a cert:

- 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') }}"

User lock state before automation:

- ansible.builtin.set_fact:
    user_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') }}"

Multiple principals with named-map output:

- ansible.builtin.set_fact:
    states: "{{ lookup('eigenstate.ipa.principal',
                'HTTP/web01.corp.example.com',
                'ldap/ldap01.corp.example.com',
                server='idm-01.corp.example.com',
                kerberos_keytab='/runner/env/ipa/admin.keytab',
                result_format='map_record',
                verify='/etc/ipa/ca.crt') }}"

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') }}"

Find service principals matching a criteria string:

- ansible.builtin.set_fact:
    http_services: "{{ lookup('eigenstate.ipa.principal',
                        server='idm-01.corp.example.com',
                        kerberos_keytab='/runner/env/ipa/admin.keytab',
                        operation='find',
                        principal_type='service',
                        criteria='HTTP',
                        verify='/etc/ipa/ca.crt') }}"

Failure Boundaries

Common failure classes:

[!NOTE] A principal that does not exist in IdM returns a state record with exists=false rather than raising an error. Use this for conditional pre-flight logic without ignore_errors.

When To Read The Scenario Guide

Use PRINCIPAL CAPABILITIES when you need operator patterns rather than option-by-option reference: