Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

FreeIPA Co-deployment

Ahdapa can run directly on an IPA server, sharing the existing Apache httpd instance that already serves /ipa (the IPA web API) and /ca, /kra, /acme (Dogtag PKI). The pattern follows the same drop-in conf.d model IPA uses for Dogtag: one extra Apache config file, one gssproxy entry, one ahdapa TOML file.

The resulting deployment serves ahdapa at https://<ipa-fqdn>/idp/.

How it fits together

graph TD
    B["Browser / OAuth2 client"]

    subgraph httpd["Apache httpd (IPA-managed) — HTTPS :443"]
        R1["/ipa/* → mod_wsgi (IPA API)"]
        R2["/ca/* → AJP → Dogtag"]
        R3["/idp/* → mod_proxy → ahdapa"]
    end

    E["ahdapa process<br/>(plain HTTP, Unix socket)"]

    DS["IPA Directory Server<br/>(slapd — ldapi://)"]
    KDC["KDC<br/>(GSSAPI / SPNEGO)"]

    B -->|"HTTPS :443"| httpd
    R3 -->|"Unix socket"| E
    E -->|"GSSAPI SASL<br/>(service cred / S4U2Self)"| DS
    E -->|"SPNEGO acceptor<br/>S4U2Self initiator"| KDC

Ahdapa handles its own Kerberos SPNEGO, password, OTP, and passkey authentication internally — Apache does not apply mod_auth_gssapi to the /idp path. TLS is terminated by Apache; ahdapa runs over plain HTTP on the Unix socket.

For LDAP attribute lookups, OTP token management, and passkey writes, ahdapa connects to the local Directory Server socket and authenticates via GSSAPI SASL — the same mechanism IPA tools use, with no special group membership required.

Two distinct LDAP credential modes are used:

  • Service principal credential — used for all pre-authentication lookups (e.g. GET /api/auth/federated-hint, the ipauserauthtype gate at the start of every token flow). The HTTP service principal binds directly to LDAP without impersonating the user. This is required because FreeIPA LDAP ACLs prevent users from reading their own ipaidpconfiglink and related IdP attributes.
  • S4U2Self — used for post-authentication operations (OTP token management, passkey writes, profile attribute lookups) where the request is scoped to an already-authenticated user. S4U2Self impersonation ensures that FreeIPA’s standard self-service ACIs apply and that the audit trail reflects the user, not the service.

Prerequisites

  • FreeIPA is installed and the IPA server is running.
  • mod_proxy and mod_proxy_http are loaded (they are standard on Fedora / RHEL).
  • The ahdapa package is installed (/usr/bin/ahdapa, /usr/share/ahdapa/webui/).
  • The ahdapa system user exists (useradd -r -s /sbin/nologin ahdapa).

Runtime directory/run/ahdapa/ is created automatically by systemd-tmpfiles from ahdapa-tmpfiles.conf (mode 2750, owner ahdapa, group apache). The setgid bit causes the Unix socket created inside to inherit group apache; combined with UMask=0117 in the service unit, the socket ends up ahdapa:apache 0660 so Apache mod_proxy can connect without any explicit group membership changes.

Automated deployment with Ansible

For deployments that manage a full FreeIPA cluster, the Ansible playbooks under contrib/demo/ipa/ansible/ automate every step described in this page across all IPA nodes simultaneously.

# 1. Copy and edit the inventory.
cp contrib/demo/ipa/ansible/inventory.ini.example inventory.ini
$EDITOR inventory.ini   # set hostnames, ipa_domain, ipa_realm, passwords

# 2. Run the full site playbook.
ansible-playbook -i inventory.ini contrib/demo/ipa/ansible/site.yml

The site.yml entry point runs four phases in order:

PhasePlaybookWhat it does
1playbooks/ipa_server.ymlInstalls the IPA server (via freeipa.ansible_freeipa.ipaserver)
2playbooks/ipa_replica.ymlInstalls replicas one at a time (via freeipa.ansible_freeipa.ipareplica)
3playbooks/ahdapa.ymlEnables the abbra/synta COPR, installs ahdapa, deploys Jinja2-rendered configs, enables the service
4playbooks/ipa_permissions.ymlCreates the “Ahdapa Topology Read” and “Ahdapa IdP Read” privileges and the “Ahdapa Services” role via ipaprivilege/iparole modules; adds all node HTTP principals as role members

After site.yml completes, ahdapa is running on every IPA node, the gossip layer discovers peers automatically via IPA replication topology, and Kerberos self-registration seeds each node’s ML-KEM-768 key on its peers before the first gossip round — no manual bootstrap is needed. See contrib/demo/ipa/ansible/README.md for inventory variables, vault encryption, and troubleshooting guidance.

The manual steps below remain accurate for single-node or non-Ansible deployments.

Files to install

Source fileInstall path
contrib/demo/ipa/ahdapa-gssproxy.conf/etc/gssproxy/20-ahdapa.conf
contrib/demo/ipa/ipa-idp-proxy.conf/etc/httpd/conf.d/ipa-idp-proxy.conf
contrib/demo/ipa/ahdapa.toml/etc/ahdapa/ahdapa.toml

Step-by-step setup

1. Install the gssproxy drop-in

IPA manages the HTTP service keytab at /var/lib/ipa/gssproxy/http.keytab through gssproxy (/etc/gssproxy/10-ipa.conf). Ahdapa reuses the same keytab via its own gssproxy service entry; no separate keytab extraction is needed.

cp contrib/demo/ipa/ahdapa-gssproxy.conf /etc/gssproxy/20-ahdapa.conf
chmod 0600 /etc/gssproxy/20-ahdapa.conf
systemctl restart gssproxy

The drop-in grants the ahdapa process:

  • allow_protocol_transition = true — S4U2Self: obtain a Kerberos credential on behalf of an authenticated user for LDAP lookups and OTP/passkey self-service.
  • allow_constrained_delegation = true — S4U2Proxy: forward that credential to the IPA LDAP service under constrained delegation.
  • cred_usage = both — accept Kerberos service tickets from browsers (SPNEGO) and initiate credentials for S4U2Self.

2. Edit ahdapa.toml

Replace the placeholder strings throughout the sample config file:

PlaceholderWhereReplace with
ipa.example.com (in [server] issuer)Optional — omit when gssapi.initiator_principal is set; ahdapa derives https://<node-fqdn>/idp automatically from the principal. Set explicitly to use the stable ipa-ca.<domain> alias (recommended for multi-node clusters)The stable ipa-ca.<domain> alias or a per-node FQDN
ipa.example.com (in [gssapi] initiator_principal)RequiredThe IPA server’s FQDN (output of hostname -f)
EXAMPLE.COMRequiredThe Kerberos realm (output of ipa env realm)
slapd-EXAMPLE-COM (in [ipa] uri)RequiredThe realm with dots replaced by hyphens

The ldapi:// URI uses percent-encoded slashes in the socket path. Read the canonical value from /etc/ipa/default.conf (ldap_uri key), which already contains the correctly encoded URI for the local server. For realm IPA.TEST the encoded URI is ldapi://%2Fvar%2Frun%2Fdirsrv%2Fslapd-IPA-TEST.socket.

Install the config:

install -o root -g ahdapa -m 0640 \
    contrib/demo/ipa/ahdapa.toml /etc/ahdapa/ahdapa.toml

3. Install the Apache config

cp contrib/demo/ipa/ipa-idp-proxy.conf /etc/httpd/conf.d/ipa-idp-proxy.conf
apachectl configtest && systemctl reload httpd

The Apache config:

  • Strips the /idp prefix before forwarding to ahdapa’s Unix socket.
  • Rewrites the Path attribute on ahdapa’s session cookie from / to /idp/ so it does not collide with the ipa_session cookie at /ipa.
  • Injects X-Forwarded-Proto: https so ahdapa generates correct https:// URIs in OAuth2 responses.
  • Redirects HTTP requests for /idp/ to HTTPS using the RewriteEngine that ipa-rewrite.conf already enables.

4. Enable and start ahdapa

systemctl enable --now ahdapa

On first boot ahdapa initialises the database and generates a JWT signing key using the configured algorithm (default: ES256 / ECDSA P-256). Check startup logs:

journalctl -u ahdapa -n 50

Verification

# OIDC discovery document — issuer must end with /idp
curl -s https://ipa.example.com/idp/.well-known/openid-configuration | python3 -m json.tool

# Confirm the IPA web API is unaffected
curl -s https://ipa.example.com/ipa/json | python3 -m json.tool

# HTTP → HTTPS redirect
curl -I http://ipa.example.com/idp/
# Expected: HTTP/1.1 301 Moved Permanently
# Location: https://ipa.example.com/idp/

# WebUI
firefox https://ipa.example.com/idp/ui/

After logging in, open browser DevTools → Application → Cookies and confirm:

  • ipa_session has Path: /ipa (IPA’s cookie, unchanged).
  • session has Path: /idp (ahdapa’s cookie, scoped by the proxy).

Configuration notes

issuer is optional for IPA co-deployments

When gssapi.initiator_principal is set, [server] issuer is optional. Ahdapa derives it automatically as https://<node-fqdn>/idp where <node-fqdn> is the hostname extracted from the principal — for example, "HTTP/ipa1.ipa.test@IPA.TEST" yields https://ipa1.ipa.test/idp.

For multi-node clusters, set issuer explicitly to the stable ipa-ca.<domain> DNS alias so that all nodes mint tokens with the same iss:

[server]
# Optional when gssapi.initiator_principal is set.
# Derived automatically as https://<node-fqdn>/idp otherwise.
# Set explicitly to the ipa-ca alias for consistent iss across all nodes.
issuer = "https://ipa-ca.ipa.test/idp"

When issuer is absent and gssapi.initiator_principal is not set, Config::load() returns a validation error and ahdapa refuses to start.

Regardless of how issuer is determined, it must include the path prefix (/idp for IPA co-deployments). Every token’s iss claim, every redirect URI, and the OIDC Discovery and OAuth2 Authorisation Server Metadata documents are derived from this value. Clients validate iss against it, so the prefix must be present end-to-end.

ldapi:// uses GSSAPI SASL, not EXTERNAL

Ahdapa connects to the Directory Server via the Unix socket but still authenticates with GSSAPI SASL — the same as connecting over LDAPS. IPA Directory Server accepts GSSAPI SASL binds over ldapi:// without any special configuration or group membership for the ahdapa process user. The ldapi:// URI simply replaces the TCP connection with a Unix socket connection; the authentication layer is identical.

The socket path in the URI must use percent-encoded slashes (%2F). The correctly encoded URI for the local server is in /etc/ipa/default.conf under the ldap_uri key:

grep ldap_uri /etc/ipa/default.conf
# ldap_uri = ldapi://%2Fvar%2Frun%2Fdirsrv%2Fslapd-IPA-TEST.socket

Because the URI starts with ldapi://, ahdapa automatically uses direct LDAP for all attribute lookups, OTP management, and passkey writes (the IPA JSON-RPC API dispatch path is not activated).

gssproxy and the HTTP service principal

IPA’s HTTP/ipa.example.com service principal is shared by several processes:

Processgssproxy entry
Apache httpd (mod_auth_gssapi)[service/ipa-httpd]
IPA API daemon[service/ipa-api]
ahdapa[service/ahdapa] (the drop-in)

All three entries in /etc/gssproxy/10-ipa.conf and /etc/gssproxy/20-ahdapa.conf point at the same keytab. Gssproxy authorises each service separately by euid; there is no conflict.

GSS_USE_PROXY is set automatically

When gssproxy = true in [gssapi], ahdapa calls std::env::set_var("GSS_USE_PROXY", "yes") before any GSSAPI operation. This routes all credential acquisition through gssproxy, which is required because the HTTP keytab at /var/lib/ipa/gssproxy/http.keytab is readable only by gssproxy (mode 0600, owner gssproxy), not by the ahdapa process user.

No manual environment variable export is needed in the unit file or shell.

URL prefix from issuer

Ahdapa derives its internal redirect base path from the path component of the issuer URL. With issuer = "https://ipa.example.com/idp" (whether set explicitly or auto-derived), every login redirect, consent page URL, device endpoint, and session cookie path is automatically prefixed with /idp. No additional configuration is required.

Passkey RP ID is derived automatically

FreeIPA derives the WebAuthn Relying Party ID from the Kerberos realm lowercased (e.g. IPA.TESTipa.test). Ahdapa applies the same derivation when passkey_rp_id is not set, so for a standard IPA deployment the key can be omitted from [ipa] entirely:

[ipa]
uri = "ldapi://%2Fvar%2Frun%2Fdirsrv%2Fslapd-IPA-TEST.socket"
# passkey_rp_id is not required — derived from [server] realm = "IPA.TEST"

Set passkey_rp_id explicitly only when you need to override the derived value (e.g. "localhost" for local development or "corp.example.com" for a custom domain).

The WebAuthn origin checked during passkey registration and assertion is always scheme://host — no path. Ahdapa strips the path component from the issuer URL automatically, so issuer = "https://ipa.example.com/idp" yields expected origin https://ipa.example.com. No additional configuration is needed.

Multi-node HA

For a high-availability setup with multiple IPA replicas each running ahdapa, you can either list peers manually or use automatic topology-based discovery.

Recommended: IPA topology-based discovery with Kerberos self-registration

Enable ipa_topology and ahdapa will query the IPA replication topology from LDAP and automatically gossip with all directly connected replica peers. No manual peer list is needed, and the peer list stays up to date as replicas are added or removed.

When gssapi.initiator_principal is also set, ahdapa additionally performs Kerberos self-registration after each topology refresh: for every newly-discovered peer that does not yet have this node’s ML-KEM-768 public key, ahdapa calls POST /api/gossip/register-kem on that peer authenticated with a Kerberos AP-REQ for HTTP@<peer_host>. This seeds the KEM key before the first gossip push, eliminating both the TOFU squatting window and the two-round bootstrap delay that static-peer deployments require. No database copying or manual key seeding is needed when bringing up a new replica.

[gossip]
ipa_topology              = true
ipa_topology_interval_secs = 300   # re-query every 5 minutes (default)
interval_secs             = 5

[gssapi]
service              = "HTTP"
gssproxy             = true
initiator_principal  = "HTTP/ipa.example.com@EXAMPLE.COM"
ccache               = "FILE:/run/ahdapa/ahdapa.ccache"

This requires granting the HTTP service principal several IPA permissions. The full set of required privileges is:

PrivilegeIPA permissionPurpose
Ahdapa Topology ReadSystem: Read Topology SegmentsPeer discovery via IPA replication topology
Ahdapa IdP ReadAhdapa - Read user IdP attributesRead ipauserauthtype, ipaidpconfiglink, and ipaidpsub on user objects — needed to enforce ipauserauthtype=idp and resolve federated users via their IPA-stored external identity
Ahdapa IdP ReadSystem: Read External IdP serverBuilt-in IPA permission — read all ipaIdP entries under cn=idp,<suffix> for automatic IdP discovery (fetch_ipa_idps)

These lookups use the service principal credential directly (no S4U2Self impersonation) because the target attributes (ipaidpconfiglink, ipaIdP entries) are not visible to users reading their own entries.

With Ansible (recommended for multi-node deployments), all three privileges are provisioned idempotently by playbooks/ipa_permissions.yml:

ansible-playbook -i inventory.ini contrib/demo/ipa/ansible/playbooks/ipa_permissions.yml

For manual or single-node deployments, run once as an IPA admin (replace the principal name as appropriate):

# Topology privilege
ipa privilege-add "Ahdapa Topology Read" \
    --desc="Allows ahdapa to read IPA replication topology"
ipa privilege-add-permission "Ahdapa Topology Read" \
    --permission="System: Read Topology Segments"

# Custom permission for reading per-user IdP attributes
ipa permission-add "Ahdapa - Read user IdP attributes" \
    --right=read --right=search --right=compare \
    --attrs=ipauserauthtype --attrs=ipaidpconfiglink --attrs=ipaidpsub \
    --type=user

# IdP read privilege (one custom + one built-in permission)
ipa privilege-add "Ahdapa IdP Read" \
    --desc="Allows ahdapa to read IPA IdP configuration and user IdP attributes"
ipa privilege-add-permission "Ahdapa IdP Read" \
    --permission="Ahdapa - Read user IdP attributes"
ipa privilege-add-permission "Ahdapa IdP Read" \
    --permission="System: Read External IdP server"

# Role
ipa role-add "Ahdapa Services" \
    --desc="Role for ahdapa service accounts"
ipa role-add-privilege "Ahdapa Services" \
    --privilege="Ahdapa Topology Read"
ipa role-add-privilege "Ahdapa Services" \
    --privilege="Ahdapa IdP Read"
ipa role-add-member "Ahdapa Services" \
    --services="HTTP/ipa.example.com@EXAMPLE.COM"

389-ds equality indexes (recommended)

The LDAP filter ahdapa uses to resolve federated users is:

(&(objectClass=ipaIdpUser)(ipaIdpConfigLink=<dn>)(ipaIdpSub=<value>))

Without equality indexes on ipaIdpConfigLink and ipaIdpSub, every federated login triggers a full scan of cn=accounts,<suffix>, emitting notes=U in the 389-ds access log and adding hundreds of milliseconds to each login. Add the indexes once on the primary IPA server:

# Replace IPA-EXAMPLE-COM with your realm, dots replaced by dashes.
dsconf slapd-IPA-EXAMPLE-COM backend index add \
    --attr ipaIdpConfigLink --index-type eq userRoot
dsconf slapd-IPA-EXAMPLE-COM backend index add \
    --attr ipaIdpSub --index-type eq userRoot
dsconf slapd-IPA-EXAMPLE-COM backend index reindex \
    --attr ipaIdpConfigLink --attr ipaIdpSub --wait userRoot

The Ansible playbook playbooks/ipa_permissions.yml creates these indexes automatically.

Stable cluster hostname (ipa-ca)

FreeIPA creates a DNS alias ipa-ca.<domain> (e.g. ipa-ca.ipa.test) that resolves (round-robin) to all CA-capable IPA servers. Setting this as the OIDC issuer gives external IdPs a single redirect URI that survives individual node failures.

When gssapi.initiator_principal is configured (the standard IPA co-deployment), the [server] issuer field is optional. When absent, ahdapa derives it from the principal’s hostname (e.g. "HTTP/ipa1.ipa.test@IPA.TEST"https://ipa1.ipa.test/idp). For multi-node clusters set it explicitly to the stable ipa-ca.<domain> alias so all nodes issue tokens with the same iss value:

[server]
# Recommended for multi-node clusters — all nodes use the same iss.
# When absent, derived from the principal FQDN (node-specific).
issuer = "https://ipa-ca.ipa.test/idp"

Ahdapa also automatically derives two issuer aliases at startup:

  1. Node FQDN — extracted from the principal (e.g. HTTP/ipa1.ipa.test@IPA.TESThttps://ipa1.ipa.test/idp).
  2. ipa-ca.<domain> — derived from the Kerberos realm lowercased (e.g. IPA.TESTipa-ca.ipa.test).

No issuer_aliases entry is needed for IPA deployments. Add issuer_aliases only for additional hostnames beyond these two (e.g. a load-balancer VIP).

Tokens are always minted with iss = https://ipa-ca.ipa.test/idp regardless of which node handles the request. The auto-derived per-node alias is accepted for:

  • WebAuthn passkey origin: a user’s browser connecting directly to ipa1.ipa.test sends origin = https://ipa1.ipa.test in the WebAuthn assertion — accepted without error.
  • Backchannel logout aud: upstream IdPs that have the alias registered as the RP client URL send aud = https://ipa1.ipa.test/idp in logout notifications — accepted.
  • client_assertion JWT aud (RFC 7521): local services configured against the per-node FQDN may put https://ipa1.ipa.test/idp (or /token) in aud — accepted.

Discovery documents served from any alias hostname always advertise the canonical issuer value, so OIDC libraries see a consistent issuer regardless of which node they contacted.

Standalone (non-replica) ahdapa nodes

When ahdapa runs on a host that is not an IPA replica (for example, a dedicated IdP node enrolled in the IPA realm but not running dirsrv / KDC), S4U2Proxy delegation must be set up explicitly. IPA replicas already inherit the necessary delegation via the replica-join process; standalone nodes require the following one-time setup on the IPA server (performed as an IPA admin):

# Enable ok-to-auth-as-delegate on the standalone node's HTTP principal
ipa service-mod HTTP/ahdapa.example.com@EXAMPLE.COM \
    --ok-to-auth-as-delegate=True

# Create a service delegation target that includes the LDAP service
ipa servicedelegationtarget-add ahdapa-standalone-target
ipa servicedelegationtarget-add-member ahdapa-standalone-target \
    --principals=ldap/ipa.example.com@EXAMPLE.COM

# Create a service delegation rule authorising the standalone HTTP principal
ipa servicedelegationrule-add ahdapa-standalone-s4u2proxy
ipa servicedelegationrule-add-member ahdapa-standalone-s4u2proxy \
    --principals=HTTP/ahdapa.example.com@EXAMPLE.COM
ipa servicedelegationrule-add-target ahdapa-standalone-s4u2proxy \
    --servicedelegationtargets=ahdapa-standalone-target

With the Ansible playbooks, add the standalone node to the [ahdapa_standalone] inventory group and re-run playbooks/ipa_permissions.yml; the playbook handles all three steps idempotently for every host in that group.

Alternative: static peer list

If you prefer to manage peers explicitly, add the other nodes to peers using their full /idp URIs:

[gossip]
peers = [
    "https://ipa2.example.com/idp",
    "https://ipa3.example.com/idp",
]

Each node must use the same [db] cluster wrapping key. See Multi-node Cluster for the key synchronisation procedure.

FreeIPA external IdP auto-discovery

When [ipa] gssapi = true, Ahdapa automatically reads all ipaIdP objects from FreeIPA LDAP at startup and refreshes them every 300 seconds, making them available as upstream IdPs without any TOML configuration.

See Federation — FreeIPA auto-discovery for the full description, federated user resolution, and the recommended LDAP indexes.

IPA upstream IdP ACR/AMR overrides

IPA-sourced IdPs often do not return acr or amr claims. Per-IdP defaults can be set via the admin panel and are stored in the CRDT.

See Federation — IPA upstream IdP ACR/AMR overrides for the admin UI and API details.

SSSD id_provider = idp — secretless deployment with kerberos_client_auth

For large-scale SSSD deployments, ahdapa supports a template client registration that lets every FreeIPA-enrolled machine authenticate as an OAuth2 client using its existing Kerberos host keytab (host/hostname@REALM). No per-machine client_secret needs to be generated, distributed, or rotated.

How it works

  1. An administrator registers one template OAuth2 client with kerberos_principal_pattern = "host/*@REALM".
  2. Each enrolled machine’s SSSD is configured with that single idp_client_id and no idp_client_secret.
  3. At token time SSSD presents a Kerberos AP-REQ (the machine’s TGS for the ahdapa HTTP service) in Authorization: Negotiate. Ahdapa verifies the token via SPNEGO, extracts the machine principal, checks it against the glob pattern, and issues a client_credentials access token.

Step-by-step setup

1. Register the template client

curl -s -b session.jar \
  -X POST -H 'Content-Type: application/json' \
  -d '{
    "client_name": "SSSD template client",
    "token_endpoint_auth_method": "kerberos_client_auth",
    "kerberos_principal_pattern": "host/*@EXAMPLE.COM",
    "scopes": ["openid", "directory.read"]
  }' \
  https://idp.example.com/api/admin/clients | python3 -m json.tool

Note the returned client_id — this is the value machines place in idp_client_id.

The directory.read scope is required for SSSD’s identity lookups via the /api/identity/ endpoints. SSSD performs a two-phase lookup: Phase 1 searches by username or group name; Phase 2 resolves group memberships or group members using the id returned in Phase 1. See Identity API for the full endpoint specification and JSON contract.

2. (Optional) Add HBAC-based access control

Create a FreeIPA HBAC service and rules to restrict which machines may obtain tokens:

ipa hbacsvc-add sssd-idp --desc "SSSD IdP token endpoint access"
ipa hbacrule-add sssd-idp-enrolled \
    --desc "Allow enrolled workstations to get IdP tokens"
ipa hbacrule-add-service sssd-idp-enrolled --hbacsvcs=sssd-idp
# Add individual hosts (hostgroup matching is not yet supported — see known limitation):
ipa hbacrule-add-host sssd-idp-enrolled \
    --hosts=node1.example.com --hosts=node2.example.com

Then update the template client to reference the HBAC service:

curl -s -b session.jar \
  -X PUT -H 'Content-Type: application/json' \
  -d '{
    "kerberos_hbac_service": "sssd-idp"
  }' \
  https://idp.example.com/api/admin/clients/<client_id>

Known limitation: HBAC rules that grant access by hostgroup membership currently evaluate as deny for Kerberos machine principals. Use individual host entries in the HBAC rule until this limitation is resolved. When kerberos_hbac_service is set and the HBAC rule set contains no applicable rules, the server denies all token requests (fail-closed).

3. Deploy sssd.conf — same file on every machine

[domain/example.com]
id_provider = idp
idp_client_id = <template_client_id>
# No idp_client_secret — the machine keytab is used automatically

Token sub for template clients

Access tokens issued to template clients carry the actual machine principal as the sub claim (e.g. host/node1.example.com@EXAMPLE.COM), not the template client_id. This makes individual machines distinguishable in audit logs and token introspection responses even though they share a single client registration.

Discovery advertisement

kerberos_client_auth appears in token_endpoint_auth_methods_supported only when [ipa] gssapi = true. When GSSAPI is disabled, the method is absent from discovery.

Dynamic registration exclusion

POST /register rejects token_endpoint_auth_method = "kerberos_client_auth" with invalid_client_metadata. Kerberos clients must be registered by an administrator via the admin API.

See Kerberos client authentication for full field reference and validation rules.

FreeIPA HBAC rule mirroring

FreeIPA HBAC rules (stored in cn=hbac,<suffix>) can be imported into ahdapa as Identity HBAC policies via the admin API. The conceptual mapping is direct: FreeIPA’s “Who”, “Host”, and “Service” axes correspond to ahdapa’s Identity Subject (users/user groups), Identity Handler (OAuth2 client), and Scoped Access (allowed scopes) respectively.

The LDAP search bases for host and service objects used by the HBAC typeahead lookup endpoints (/api/admin/hbac/lookup/hosts and /api/admin/hbac/lookup/services) are derived automatically from the discovered domain suffix — no configuration keys are needed:

  • Hosts: cn=computers,cn=accounts,<suffix>
  • Services: cn=services,cn=accounts,<suffix>

See Identity HBAC Policy for a full description of the policy engine, axis semantics, and the admin API.

Remote IPA (ahdapa on a separate host)

When ahdapa runs on a separate host rather than on the IPA server itself, use an LDAPS URI instead of ldapi://:

[ipa]
uri = "ldaps://ipa.example.com"

With a non-ldapi:// URI and a GSSAPI initiator configured, ahdapa automatically switches to the FreeIPA JSON-RPC API (https://ipa.example.com/ipa/session/json) for attribute lookups, OTP token management, and passkey writes. The API session cookie is cached per user (default TTL 1200 s) so the GSSAPI round-trip happens at most once per session rather than on every request.

This mode requires only HTTP/ahdapa-host → HTTP/ipa-host Kerberos constrained delegation; ldap/ipa-host delegation is not needed.

OTP bind verification always uses direct LDAP regardless of the uri scheme, because the OTP_REQUIRED_OID client control is a bind-time mechanism with no JSON-RPC equivalent.

For the reverse proxy, replace the ldapi:// line and use a standalone Apache or nginx vhost as described in Reverse Proxy Setup; the issuer and listen configuration principles are the same.