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, theipauserauthtypegate 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 ownipaidpconfiglinkand 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_proxyandmod_proxy_httpare loaded (they are standard on Fedora / RHEL).- The
ahdapapackage 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 bysystemd-tmpfilesfromahdapa-tmpfiles.conf(mode2750, ownerahdapa, groupapache). The setgid bit causes the Unix socket created inside to inherit groupapache; combined withUMask=0117in the service unit, the socket ends upahdapa:apache 0660so 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:
| Phase | Playbook | What it does |
|---|---|---|
| 1 | playbooks/ipa_server.yml | Installs the IPA server (via freeipa.ansible_freeipa.ipaserver) |
| 2 | playbooks/ipa_replica.yml | Installs replicas one at a time (via freeipa.ansible_freeipa.ipareplica) |
| 3 | playbooks/ahdapa.yml | Enables the abbra/synta COPR, installs ahdapa, deploys Jinja2-rendered configs, enables the service |
| 4 | playbooks/ipa_permissions.yml | Creates 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 file | Install 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:
| Placeholder | Where | Replace 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) | Required | The IPA server’s FQDN (output of hostname -f) |
EXAMPLE.COM | Required | The Kerberos realm (output of ipa env realm) |
slapd-EXAMPLE-COM (in [ipa] uri) | Required | The 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
/idpprefix before forwarding to ahdapa’s Unix socket. - Rewrites the
Pathattribute on ahdapa’s session cookie from/to/idp/so it does not collide with theipa_sessioncookie at/ipa. - Injects
X-Forwarded-Proto: httpsso ahdapa generates correcthttps://URIs in OAuth2 responses. - Redirects HTTP requests for
/idp/to HTTPS using theRewriteEnginethatipa-rewrite.confalready 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_sessionhasPath: /ipa(IPA’s cookie, unchanged).sessionhasPath: /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:
| Process | gssproxy 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.TEST → ipa.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:
| Privilege | IPA permission | Purpose |
|---|---|---|
Ahdapa Topology Read | System: Read Topology Segments | Peer discovery via IPA replication topology |
Ahdapa IdP Read | Ahdapa - Read user IdP attributes | Read ipauserauthtype, ipaidpconfiglink, and ipaidpsub on user objects — needed to enforce ipauserauthtype=idp and resolve federated users via their IPA-stored external identity |
Ahdapa IdP Read | System: Read External IdP server | Built-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:
- Node FQDN — extracted from the principal (e.g.
HTTP/ipa1.ipa.test@IPA.TEST→https://ipa1.ipa.test/idp). ipa-ca.<domain>— derived from the Kerberos realm lowercased (e.g.IPA.TEST→ipa-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.testsendsorigin = https://ipa1.ipa.testin the WebAuthn assertion — accepted without error. - Backchannel logout
aud: upstream IdPs that have the alias registered as the RP client URL sendaud = https://ipa1.ipa.test/idpin logout notifications — accepted. client_assertionJWTaud(RFC 7521): local services configured against the per-node FQDN may puthttps://ipa1.ipa.test/idp(or/token) inaud— 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
- An administrator registers one template OAuth2 client with
kerberos_principal_pattern = "host/*@REALM". - Each enrolled machine’s SSSD is configured with that single
idp_client_idand noidp_client_secret. - 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 aclient_credentialsaccess 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_serviceis 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.