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

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-kem authenticated 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 in ahdapa.toml.
  • ipauserauthtype enforcement — users with ipauserauthtype=idp set in FreeIPA are automatically redirected to the correct upstream IdP; password, OTP, and passkey flows are blocked for those users.
  • SSSD id_provider = idp secretless deployment — IPA-enrolled machines use kerberos_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.

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.posix
    

    Or 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

PhasePlaybookTarget groupAction
1ipa_server.ymlipa_serverRuns ipa-server-install
2ipa_replica.ymlipa_replicasRuns ipa-replica-install, serial
3ahdapa.ymlipa_nodes (all)Installs COPR packages, drops config and service files
4ipa_permissions.ymlipa_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:

PrivilegePermissionsPurpose
Ahdapa Topology ReadSystem: Read Topology SegmentsPeer discovery via IPA replication topology
Ahdapa IdP ReadAhdapa - Read user IdP attributesRead ipauserauthtype, ipaidpconfiglink, ipaidpsub on user objects
Ahdapa IdP ReadSystem: Read External IdP serverRead all ipaIdP entries for automatic IdP discovery

ipa_permissions.yml creates and assigns these privileges idempotently for all node HTTP principals.

Inventory variables

VariableExampleDescription
ipa_domainipa.example.comIPA DNS domain
ipa_realmIPA.EXAMPLE.COMKerberos realm (usually the domain uppercased)
ipa_admin_passwordIPA admin account password
ipa_ds_passwordLDAP Directory Manager password
ahdapa_issuer_path/idpURL 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

Restrict which users can obtain tokens for a specific OAuth2 client:

# 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

After step 2, 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 within one gossip interval.

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 static peers list is required.

Troubleshooting

SymptomCheck
ahdapa not startingjournalctl -u ahdapa — often a config parse error or missing DB dir
502 Bad Gateway from Apachels -la /run/ahdapa/ — socket must be group-accessible by apache
gssproxy errorsjournalctl -u gssproxy — verify /etc/gssproxy/20-ahdapa.conf
Gossip not discovering peersVerify the HTTP principal has “Ahdapa Topology Read” (ipa role-show "Ahdapa Services")
Kerberos self-registration failingCheck journalctl -u ahdapa on the peer for register-kem 403/503 errors
IPA IdPs not discovered at startupRun ipa_permissions.yml — the service principal needs “Ahdapa IdP Read”
upstream_id="ipa-unknown" in logsSame as above — ipaidpconfiglink unreadable. Re-run ipa_permissions.yml and restart ahdapa
Federated user hits passkey/OTP flowConfirm ipauserauthtype: idp on the user: ipa user-show <uid> --all
SELinux AVC denial for outbound HTTPSLoad 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

FileDescription
ahdapa.tomlReference ahdapa config for IPA co-deployment
ahdapa-gssproxy.confgssproxy config fragment for the HTTP service credential
ipa-idp-proxy.confApache conf.d fragment that proxies /idp/* to the Unix socket
ansible/site.ymlFull site playbook (runs all four phases in order)
ansible/inventory.ini.exampleInventory template
ansible/group_vars/all.ymlDefault variable values
ansible/playbooks/ipa_server.ymlPhase 1: IPA primary server
ansible/playbooks/ipa_replica.ymlPhase 2: IPA replicas
ansible/playbooks/ahdapa.ymlPhase 3: ahdapa install
ansible/playbooks/ipa_permissions.ymlPhase 4: IPA privilege grants
ansible/templates/ahdapa.toml.j2Jinja2 template for the deployed ahdapa config
ansible/templates/ahdapa-gssproxy.conf.j2Jinja2 template for the gssproxy config
ansible/templates/ipa-idp-proxy.conf.j2Jinja2 template for the Apache proxy config

See also