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. Manageable via the WebUI, admin API, or ahdapactl hbac commands.
  • FreeIPA allow_all HBAC integration — ahdapa queries the FreeIPA built-in allow_all rule 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.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

Note: Default FreeIPA deployments have the built-in allow_all HBAC rule enabled. When allow_all is enabled, ahdapa skips its own HBAC evaluation entirely. To test HBAC enforcement, first disable allow_all:

ipa hbacrule-disable allow_all

Ahdapa 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 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