Skip to content

Runbook 10 — FreeIPA server build (identity: LDAP/Kerberos/DNS/CA)

Goal: stand up FreeIPA in SERVERS as the identity backbone — consistent UID/GID, Kerberos, an internal CA, and (optionally) integrated DNS. This unlocks SSO and sec=krb5 NFS in runbooks 11–12.

Prereqs: runbooks 03–06 (SERVERS zone + DNS + rules). A fresh VM/host for FreeIPA (do not put it on the fileserver or the firewall).


Decisions (set before installing)

  • Realm / domain: use a dedicated subdomain to avoid clashing with your public ${domain} DNS — domain ${ipa_domain}, realm ${realm} (realm = uppercase of the domain, per Kerberos convention). Cleaner separation from the public zone (origin Caddy + hidden Bind primary, fronted by the VPS edges).
  • DNS: let FreeIPA run its integrated DNS for the IPA domain only; Unbound forwards ${ipa_domain} to it (runbook 04 step 1). FreeIPA is not your main resolver — Unbound stays that.
  • Host: AlmaLinux 9 — same template as the rest of the fleet (the whole lab standardized on EL, partly because FreeIPA is a Red Hat project and runs best here). 2 vCPU / 4 GB / 20 GB. Clone per runbook 01 (NIC tag=20 SERVERS).
  • IP: static 10.20.20.2 in SERVERS (gw 10.20.20.1, DNS during install = itself once up; bootstrap with 10.20.20.1).

1. Prepare the host

# static hostname must be the FQDN
hostnamectl set-hostname ipa.${ipa_domain}
# /etc/hosts: pin the FQDN
echo "10.20.20.2  ipa.${ipa_domain} ipa" >> /etc/hosts
dnf -y update
dnf -y install ipa-server ipa-server-dns
# open the firewall on the host (firewalld)
firewall-cmd --permanent --add-service={freeipa-ldap,freeipa-ldaps,dns,ntp}
firewall-cmd --reload

Ensure forward/reverse name resolution of the FQDN works before installing.


2. Install FreeIPA

ipa-server-install \
  --realm=${realm} \
  --domain=${ipa_domain} \
  --ds-password='<DM-password>' \
  --admin-password='<admin-password>' \
  --setup-dns \
  --forwarder=10.20.20.1 \
  --no-reverse \
  --ssh-trust-dns
- --forwarder=10.20.20.1 sends non-IPA lookups back to Unbound on the firewall. - Accept the chrony/NTP setup (time sync is mandatory for Kerberos). - Note the CA cert location: /etc/ipa/ca.crt (you'll trust this on clients and optionally in OPNsense).

Verify:

kinit admin            # get a Kerberos ticket
ipa user-find          # talks to the directory


3. Wire DNS to Unbound

On OPNsense (Services → Unbound DNS → Query Forwarding → +): - Domain: ${ipa_domain} → Server IP 10.20.20.2 - (and the reverse zone if you later enable --reverse)

So clients resolving *.${ipa_domain} (IPA-enrolled hosts/services) hit FreeIPA, everything else stays with Unbound.


4. Create users / groups / homedir policy

# group for lab users
ipa group-add labusers --desc "Lab users"
# ADMIN group — the whole SSO/authz plane keys off this one group (create it now)
ipa group-add fleet-admins --desc "Lab administrators"
# a user (UID/GID auto-allocated consistently across all enrolled hosts)
ipa user-add operator --first Lab --last Operator --email admin@example.com
ipa group-add-member labusers --users=operator
ipa group-add-member fleet-admins --users=operator   # grant the operator admin
ipa passwd operator
Home directory path policy is set on the client side (autofs/oddjob-mkhomedir, runbook 12) and the NFS export (runbook 11). FreeIPA just guarantees the UID/GID is identical everywhere.

fleet-admins is the lab's single admin group — create it here, before anything downstream. Membership is surfaced via the LDAP groups claim and mapped to admin across the plane: Authelia's admin group (runbook 13), Grafana admin, the control-plane Forgejo site admin, the Jellyfin admin filter, and ipa_access HBAC/sudo (which assumes the usergroup already exists). Add/remove an admin = one ipa group-add-member fleet-admins here; there is no per-app admin list to keep in sync.


5. Firewall alias update

Set the FREEIPA host alias to 10.20.20.2 and confirm IPA_PORTS (88,464,389,636,53,443) are allowed WS→SERVERS (runbook 06).

5b. Trust the IPA CA across the fleet

Now that FreeIPA's CA exists, have the Ansible common role trust it everywhere:

# copy the CA off the FreeIPA server, then on the control node:
cp ipa-ca.crt ansible/roles/common/files/ipa-ca.crt      # CA = /etc/ipa/ca.crt
# set lab_ipa_ca_file: ipa-ca.crt (defaults or host_vars), then re-run:
cd ansible && ansible-playbook playbooks/site.yml
(Idempotent — this is the "re-run the baseline after FreeIPA" step from runbook 09.)

6. Nightly logical backup

Image backups (Proxmox/ZFS daily) are crash/fs-consistent; add an app-consistent dump. The db_backups Ansible role (runbook 16) installs a systemd timer running ipa-backup --data --online nightly to /var/lib/ipa/backup (captured by the daily image backup), with retention.

cd ansible && ansible-playbook playbooks/db-backups.yml --limit freeipa
Also take a periodic full/offline ipa-backup on a maintenance window for DR.


Validation

  • [ ] kinit admin succeeds; ipa user-find lists users.
  • [ ] systemctl list-timers db-backup.timer shows it scheduled; a manual systemctl start db-backup.service produces an ipa-data-* backup.
  • [ ] From a WORKSTATIONS test client: dig ipa.${ipa_domain} resolves (via Unbound→IPA forward) and dig google.com still resolves (via Unbound).
  • [ ] FreeIPA web UI reachable at https://ipa.${ipa_domain} from an allowed zone; not reachable from GUEST/IOT/DMZ.
  • [ ] Chrony/time healthy (Kerberos is time-sensitive).

Then → runbook 11 (AlmaLinux ZFS + NFS storage).