Demo: FreeIPA/Kerberos deployment
Location: contrib/demo/ipa/
Ansible playbooks that install a FreeIPA cluster and deploy ahdapa on every node behind IPA’s Apache httpd. This demo describes a production-representative deployment rather than a local script: it requires real infrastructure (Fedora 44 hosts with DNS) and takes 15–30 minutes to complete.
Architecture
Browser / OAuth2 client
|
| HTTPS :443
v
Apache httpd (IPA-managed)
/ipa/* → mod_wsgi (IPA API)
/ca/* → AJP → Dogtag PKI
/idp/* → mod_proxy → ahdapa (Unix socket)
|
v
ahdapa process (plain HTTP over Unix socket)
GSSAPI SASL → IPA Directory Server (slapd, ldapi://)
SPNEGO → KDC
ahdapa listens on a Unix domain socket at /run/ahdapa/ahdapa.sock. Apache
proxies all /idp/* traffic there. TLS is terminated by Apache; ahdapa needs
no [tls] section.
After installation, ahdapa is available at https://<node-fqdn>/idp/.
What the demo shows
- IPA-integrated authentication — SPNEGO single sign-on for domain members; password, OTP, and passkey login for others.
- Automatic peer discovery via IPA topology — ahdapa reads the IPA replication topology from LDAP and gossips with all directly connected replicas automatically. No static peer list is required.
- Kerberos key bootstrapping — each node seeds its ML-KEM-768 key on
peers via
POST /api/gossip/register-kemauthenticated with the node’s Kerberos machine credential. No manual key seeding is needed. - FreeIPA IdP auto-discovery — IdPs registered with
ipa idp-add …are discovered at startup and refreshed every 300 s. No[[federation.upstream_idps]]entries are needed inahdapa.toml. ipauserauthtypeenforcement — users withipauserauthtype=idpset in FreeIPA are automatically redirected to the correct upstream IdP; password, OTP, and passkey flows are blocked for those users.- SSSD
id_provider = idpsecretless deployment — IPA-enrolled machines usekerberos_client_auth(no per-machine secret) to obtain tokens and call the/api/identity/directory API for user and group lookups. - HBAC policies — Identity HBAC rules restrict which users may obtain tokens
for which OAuth2 clients, enforced at the token endpoint. Manageable via
the WebUI, admin API, or
ahdapactl hbaccommands. - FreeIPA
allow_allHBAC integration — ahdapa queries the FreeIPA built-inallow_allrule at startup and every 300 s; when it is enabled (the default), ahdapa HBAC enforcement is transparently skipped.
Prerequisites
-
Fedora 44 on all target hosts.
-
Working forward and reverse DNS for every FQDN (IPA requires this).
-
SSH key-based access with passwordless sudo on all hosts.
-
On the Ansible control node:
pip install ansible ansible-galaxy collection install freeipa.ansible_freeipa ansible.posixOr install from the distribution package:
dnf install ansible-freeipa ansible-galaxy collection install ansible.posix -
ahdapa packages are installed from the abbra/synta COPR.
Running
# 1. Copy and edit the inventory.
cp contrib/demo/ipa/ansible/inventory.ini.example \
contrib/demo/ipa/ansible/inventory.ini
$EDITOR contrib/demo/ipa/ansible/inventory.ini
# 2. (Recommended) encrypt passwords with ansible-vault.
ansible-vault encrypt_string 'SomeAdminPass1!' --name ipa_admin_password
ansible-vault encrypt_string 'SomeDSPass1!' --name ipa_ds_password
# Paste the output into inventory.ini.
# 3. Run the full site playbook.
ansible-playbook -i contrib/demo/ipa/ansible/inventory.ini \
contrib/demo/ipa/ansible/site.yml
Individual phases
# Install and configure the IPA primary server.
ansible-playbook -i inventory.ini playbooks/ipa_server.yml
# Enroll IPA replicas (serial).
ansible-playbook -i inventory.ini playbooks/ipa_replica.yml
# Deploy ahdapa on all IPA nodes.
ansible-playbook -i inventory.ini playbooks/ahdapa.yml
# Grant IPA permissions to all ahdapa service principals.
ansible-playbook -i inventory.ini playbooks/ipa_permissions.yml
What gets installed
| Phase | Playbook | Target group | Action |
|---|---|---|---|
| 1 | ipa_server.yml | ipa_server | Runs ipa-server-install |
| 2 | ipa_replica.yml | ipa_replicas | Runs ipa-replica-install, serial |
| 3 | ahdapa.yml | ipa_nodes (all) | Installs COPR packages, drops config and service files |
| 4 | ipa_permissions.yml | ipa_server (once) | Grants required IPA privileges to all node service principals |
IPA privilege grants
ahdapa authenticates to the FreeIPA LDAP directory as its HTTP service
principal (HTTP/<fqdn>@<REALM>). Three privileges are required:
| Privilege | Permissions | 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, ipaidpsub on user objects |
Ahdapa IdP Read | System: Read External IdP server | Read all ipaIdP entries for automatic IdP discovery |
ipa_permissions.yml creates and assigns these privileges idempotently for
all node HTTP principals.
Inventory variables
| Variable | Example | Description |
|---|---|---|
ipa_domain | ipa.example.com | IPA DNS domain |
ipa_realm | IPA.EXAMPLE.COM | Kerberos realm (usually the domain uppercased) |
ipa_admin_password | — | IPA admin account password |
ipa_ds_password | — | LDAP Directory Manager password |
ahdapa_issuer_path | /idp | URL path prefix for ahdapa behind Apache |
Additional defaults are in contrib/demo/ipa/ansible/group_vars/all.yml.
Post-installation verification
After site.yml completes:
# Verify OIDC discovery endpoint.
curl -s https://ipa1.example.com/idp/.well-known/openid-configuration \
| python3 -m json.tool | head -20
# Obtain a Kerberos ticket and test SPNEGO login.
kinit alice@IPA.EXAMPLE.COM
curl -s --negotiate -u: -c /tmp/alice.jar \
https://ipa1.example.com/idp/api/auth/info | python3 -m json.tool
# Register an OAuth2 client via Kerberos-authenticated dynamic registration.
kinit -k -t /etc/http.keytab HTTP/client.ipa.example.com@IPA.EXAMPLE.COM
curl -s -o /dev/null -w "%{http_code}\n" \
--negotiate -u: -c /tmp/session.jar \
https://ipa1.example.com/idp/authorize
# → 400 (expected; no OAuth2 params given but session cookie is set)
curl -s -b /tmp/session.jar \
-X POST -H 'Content-Type: application/json' \
-d '{
"redirect_uris": ["https://client.ipa.example.com/callback"],
"client_name": "My App",
"scope": "openid profile email"
}' \
https://ipa1.example.com/idp/register | python3 -m json.tool
HBAC demo
Note: Default FreeIPA deployments have the built-in
allow_allHBAC rule enabled. Whenallow_allis enabled, ahdapa skips its own HBAC evaluation entirely. To test HBAC enforcement, first disableallow_all:ipa hbacrule-disable allow_allAhdapa picks up the change within 300 seconds (or immediately on restart).
Restrict which users can obtain tokens for a specific OAuth2 client:
Using curl
# 1. Create the demo OAuth2 client.
curl -s -b /tmp/admin.jar \
-X POST -H 'Content-Type: application/json' \
-d '{
"client_id": "demo-app",
"client_name": "Demo Application",
"redirect_uris": ["https://demo.ipa.example.com/callback"],
"scope": "openid profile email"
}' \
https://ipa1.example.com/idp/api/admin/clients | python3 -m json.tool
# 2. Create an HBAC policy: only alice may use demo-app.
curl -s -b /tmp/admin.jar \
-X POST -H 'Content-Type: application/json' \
-d '{
"name": "alice can use demo-app",
"enabled": true,
"users": ["alice"],
"clients": ["demo-app"],
"allowed_scopes": ["openid", "profile"]
}' \
https://ipa1.example.com/idp/api/admin/hbac | python3 -m json.tool
# 3. List active HBAC policies.
curl -s -b /tmp/admin.jar \
https://ipa1.example.com/idp/api/admin/hbac | python3 -m json.tool
Using ahdapactl
# 1. Log in (uses Kerberos if a valid TGT is present).
ahdapactl login https://ipa1.example.com/idp
# 2. Create the demo OAuth2 client.
ahdapactl clients create \
--name "Demo Application" \
--redirect-uris https://demo.ipa.example.com/callback \
--scopes openid,profile,email
# 3. Create the HBAC rule with initial members (one step).
ahdapactl hbac create --name "alice can use demo-app" \
--users alice \
--clients demo-app \
--scopes openid,profile \
--enabled true
# 4. List active HBAC policies.
ahdapactl hbac list
After the HBAC rule is created and enabled, any user other than alice who
attempts to obtain a token for demo-app receives an access_denied error
at the token endpoint. The gossip protocol replicates the rule to all cluster
nodes immediately (the CRDT write wakes the gossip loop via Notify).
Configuration notes
The static reference configuration is at contrib/demo/ipa/ahdapa.toml.
Key points:
[server]
issuer = "https://ipa.example.com/idp"
listen = "unix:/run/ahdapa/ahdapa.sock"
[gssapi]
gssproxy = true
initiator_principal = "HTTP/ipa.example.com@EXAMPLE.COM"
[ipa]
uri = "ldapi://%2Fvar%2Frun%2Fdirsrv%2Fslapd-EXAMPLE-COM.socket"
cache_ttl_secs = 60
[gossip]
ipa_topology = true
interval_secs = 5
listen = "unix:/run/ahdapa/ahdapa.sock"— Apache proxies to this socket.gssproxy = true— uses gssproxy to obtain the HTTP service credential; no separate keytab extraction is needed.ipa_topology = true— peers are discovered from the IPA replication topology; no staticpeerslist is required.
Troubleshooting
| Symptom | Check |
|---|---|
| ahdapa not starting | journalctl -u ahdapa — often a config parse error or missing DB dir |
| 502 Bad Gateway from Apache | ls -la /run/ahdapa/ — socket must be group-accessible by apache |
| gssproxy errors | journalctl -u gssproxy — verify /etc/gssproxy/20-ahdapa.conf |
| Gossip not discovering peers | Verify the HTTP principal has “Ahdapa Topology Read” (ipa role-show "Ahdapa Services") |
| Kerberos self-registration failing | Check journalctl -u ahdapa on the peer for register-kem 403/503 errors |
| IPA IdPs not discovered at startup | Run ipa_permissions.yml — the service principal needs “Ahdapa IdP Read” |
upstream_id="ipa-unknown" in logs | Same as above — ipaidpconfiglink unreadable. Re-run ipa_permissions.yml and restart ahdapa |
| Federated user hits passkey/OTP flow | Confirm ipauserauthtype: idp on the user: ipa user-show <uid> --all |
| SELinux AVC denial for outbound HTTPS | Load the ahdapa SELinux module: semodule -i ahdapa.pp |
Federated login slow (notes=U in 389-ds) | LDAP indexes on ipaIdpConfigLink and ipaIdpSub are missing — re-run ipa_permissions.yml |
Files
| File | Description |
|---|---|
ahdapa.toml | Reference ahdapa config for IPA co-deployment |
ahdapa-gssproxy.conf | gssproxy config fragment for the HTTP service credential |
ipa-idp-proxy.conf | Apache conf.d fragment that proxies /idp/* to the Unix socket |
ansible/site.yml | Full site playbook (runs all four phases in order) |
ansible/inventory.ini.example | Inventory template |
ansible/group_vars/all.yml | Default variable values |
ansible/playbooks/ipa_server.yml | Phase 1: IPA primary server |
ansible/playbooks/ipa_replica.yml | Phase 2: IPA replicas |
ansible/playbooks/ahdapa.yml | Phase 3: ahdapa install |
ansible/playbooks/ipa_permissions.yml | Phase 4: IPA privilege grants |
ansible/templates/ahdapa.toml.j2 | Jinja2 template for the deployed ahdapa config |
ansible/templates/ahdapa-gssproxy.conf.j2 | Jinja2 template for the gssproxy config |
ansible/templates/ipa-idp-proxy.conf.j2 | Jinja2 template for the Apache proxy config |
See also
- FreeIPA Co-deployment — full IPA co-deployment guide.
- Multi-node Cluster — cluster configuration reference.
- Identity HBAC Policy — HBAC policy reference.