22 KiB
FreeIPA & Ansible
The FreeIPA/Ansible system ("ansipa") provides centralised identity management for a mixed Linux fleet (Arch, Fedora, RHEL/Rocky/AlmaLinux, Debian/Ubuntu): single sign-on, host-group-driven package and module deployment, policy enforcement, CheckMK monitoring, LUKS backup key collection, scan-result aggregation, and automatic Keycloak configuration. For a full deployment walkthrough — ports, the nginx portal/reverse proxy, firewall rules, NAT behaviour, and Proxmox LXC — see the ansipa Setup Guide.
All relevant files live under setup/modules/FreeipaAnsible/.
Architecture
┌──────────────────────────────────────────────────────────────────────┐
│ FreeIPA + Keycloak + Samba container (docker-compose) │
│ │
│ • FreeIPA — user/host directory, Kerberos KDC, DNS (optional) │
│ • Keycloak — OIDC provider federating FreeIPA via LDAP │
│ • CheckMK — fleet monitoring (agent + local checks) │
│ • Samba — SMB shares for the policy store and LUKS backup keys │
└───────────┬─────────────────────────┬────────────────────────────────┘
│ SSSD / Kerberos │ SMB (445)
▼ ▼
┌──────────────────────┐ ┌─────────────────────────────────────────┐
│ Enrolled client │ │ Ansible controller │
│ │ │ │
│ • sssd │ │ • deploy-ansipa-*.yml playbooks │
│ • ipa CLI │ │ • collect-luks-keys.yml │
│ • CheckMK agent │ │ (uploads keys to ansipa-luks-keys │
│ • Ansible-deployed │ │ share as luks-upload service acct) │
│ timers: │ └─────────────────────────────────────────┘
│ ├── pkg installer │
│ ├── module install│
│ ├── Flatpak install
│ ├── baseuser sync │
│ └── policy enforcer (every 30 min)
│ ├── binary blocking
│ ├── daemon enable/disable
│ ├── Timeshift backups
│ └── dev_mon_clamscan → CheckMK
└──────────────────────┘
FreeIPA Server Container
Quick Start
cd setup/modules/FreeipaAnsible/image
cp .env.example .env
$EDITOR .env # set all required variables
docker compose up -d
docker compose logs -f freeipa # watch first-boot (~10 min)
# Once freeipa is healthy:
./keycloak-configure.sh # wire Keycloak → FreeIPA LDAP
To run FreeIPA without Keycloak:
docker compose up -d freeipa
Environment Variables (.env)
| Variable | Required | Description |
|---|---|---|
IPA_HOSTNAME |
yes | FQDN of the IPA server (e.g. ipa.corp.example.com) |
IPA_DOMAIN |
yes | DNS domain (e.g. corp.example.com) |
IPA_REALM |
— | Kerberos realm; defaults to IPA_DOMAIN uppercased |
IPA_ADMIN_PASSWORD |
yes | admin account password |
IPA_DM_PASSWORD |
yes | Directory Manager password |
IPA_SETUP_DNS |
— | true to enable integrated DNS (default false) |
IPA_DNS_FORWARDER |
— | Upstream DNS when IPA_SETUP_DNS=true |
IPA_SETUP_KRA |
— | true to enable the Key Recovery Authority |
LUKS_KEY_UPLOAD_PASSWORD |
yes | Password for the luks-upload Samba service account |
KC_HOSTNAME |
yes | Public hostname of Keycloak |
KC_REALM |
— | Keycloak realm name (default corp) |
KC_ADMIN |
— | Keycloak admin username (default admin) |
KC_ADMIN_PASSWORD |
yes | Keycloak admin password |
KC_DB_PASSWORD |
yes | PostgreSQL password for Keycloak |
IPA_BIND_DN |
— | LDAP bind DN for Keycloak federation (default: Directory Manager) |
IPA_BIND_PASSWORD |
— | LDAP bind password; leave blank to reuse IPA_DM_PASSWORD |
IPA_USE_LDAPS |
— | true to use LDAPS for Keycloak federation |
Exposed Ports
| Port | Protocol | Service |
|---|---|---|
| 389 | TCP | LDAP |
| 636 | TCP | LDAPS |
| 88 | TCP + UDP | Kerberos |
| 464 | TCP + UDP | kpasswd |
| 443 | TCP | HTTPS (IPA web UI) |
| 445 | TCP | SMB (Samba shares) |
| 139 | TCP | NetBIOS session (Samba) |
| 137 | UDP | NetBIOS name service |
| 138 | UDP | NetBIOS datagram |
| 8080 | TCP | Keycloak HTTP |
| 8443 | TCP | Keycloak HTTPS |
| 8090 | TCP | CheckMK web UI (container :5000) |
| 8000 | TCP | CheckMK agent receiver (push mode) |
| 6557 | TCP | CheckMK livestatus (optional) |
| 8088 | TCP | nginx gateway — portal + reverse proxy for all UIs |
For a full deployment walkthrough — the nginx portal, exposing ansipa behind your own reverse proxy, and NAT/push-mode behaviour — see the ansipa Setup Guide.
Container Internals
On first start, ipa-first-boot.service runs ipa-first-boot.sh to initialise the FreeIPA instance. On every start, ansipa-smb.service runs ansipa-smb-setup.sh to configure Samba (the container rootfs is ephemeral — Samba config and users must be re-applied after restarts).
Data persisted to the /data volume:
/data/
├── samba/
│ ├── passdb.tdb # Samba password database (survives restarts)
│ └── ansipa-smb.env # Persisted service passwords (auto-written on first start)
├── policy-store/ # Policy files, edited live via the ansipa-policystore share
└── luks-keys/ # LUKS backup keys (written by Ansible controller via SMB)
SMB Shares
The container exposes two Samba shares (ansipa-luks-keys and
ansipa-policystore), configured by ansipa-smb-setup.sh.
ClamAV scan results are no longer delivered over SMB. The
dev_mon_clamscanpolicy now reports them to CheckMK via a local check (Ansipa_ClamScan), so there is noansipa-scansshare orscanuploadaccount any more.
ansipa-luks-keys — LUKS Backup Key Store
- Path:
/data/luks-keys - Permissions: write-only for
luks-upload; read-only for members of theKeyAdminLinux group - Purpose: The Ansible controller writes each host's LUKS backup key here after collecting it via
collect-luks-keys.yml. - Access control: Add an admin Samba user to the
KeyAdmingroup on the container:
The# On the freeipa container useradd -r -G KeyAdmin <username> smbpasswd -a <username>KeyAdmingroup and theluks-uploadservice account are created byansipa-smb-setup.shon every container start.
Client Enrollment
freeipa-enroll.sh supports Arch, Fedora, RHEL/Rocky/AlmaLinux, and
Debian/Ubuntu — it auto-detects the distro and uses the right package manager
and package names. On Arch, ipa-client-install comes from the AUR
freeipa-client package; the enroll script first builds the bundled
autofs-pkgbuild/ (the stock AUR autofs is currently broken — its pinned
tarball 404s), so expect the first Arch enrollment to take a while as the AUR
FreeIPA suite compiles. See the ansipa Setup Guide
for details.
Via Installer Module
Select freeipa-client during tui-install.sh or install-modules.sh.
Manual Enrollment
Three modes:
# Answerfile mode (unattended)
bash setup/modules/FreeipaAnsible/freeipa-client.sh \
--answerfile setup/modules/FreeipaAnsible/freeipa-client-answerfile.json
# Interactive prompts
bash setup/modules/FreeipaAnsible/freeipa-client.sh --interactive
# Direct flag passthrough to freeipa-enroll.sh
bash setup/modules/FreeipaAnsible/freeipa-client.sh \
--domain freeipa.example.com \
--server ipa.example.com \
--principal admin
Client Answerfile Schema
{
"domain": "freeipa.abdelbaki.eu",
"realm": "FREEIPA.ABDELBAKI.EU",
"server": "freeipa.abdelbaki.eu",
"hostname": "",
"principal": "admin",
"password": "",
"mkhomedir": true,
"sudo": true,
"dns_update": true,
"ntp_server": "",
"fido2": false,
"fido2_users": []
}
Leave hostname blank to use the current machine hostname. Leave password blank to be prompted at enrollment time.
Ansible Playbooks
All playbooks live in setup/modules/FreeipaAnsible/ansible/ and require an inventory of enrolled IPA clients.
Deploy Package Auto-Installer
ansible-playbook -i inventory deploy-ansipa-install.yml
Deploys ansipa-install-packages.sh + a systemd timer (every 30 min). The script queries IPA for host groups named ansipa-install-<package> and installs or removes packages to match.
Group naming convention: ansipa-install-firefox → installs firefox.
Deploy Module Auto-Installer
ansible-playbook -i inventory deploy-ansipa-modules.yml \
[-e ansipa_user=amir]
Deploys ansipa-install-modules.sh + timer. Queries for groups named ansipa-module-<name> and runs the matching script from /usr/local/lib/ansipa-modules/<name>.sh. Module scripts are copied from setup/modules/optional-Modules/apps/*.sh.
Each module is applied once and stamped in /var/lib/ansipa-modules/<name>.done.
Deploy BaseUser Sync
ansible-playbook -i inventory deploy-baseuser-sync.yml
Deploys a systemd.path unit that triggers on login. Users who are members of the IPA BaseUser group are automatically added to the local baseusers group.
Deploy Policy Enforcer
ansible-playbook -i inventory deploy-ansipa-policies.yml
Deploys the shared policy library, all policies.d/ modules, ansipa-enforce-policies.sh, and a systemd timer that runs the enforcer every 30 min.
Collect LUKS Backup Keys
ansible-playbook -i inventory collect-luks-keys.yml \
-e luks_smb_server=ipa.corp.example.com \
-e luks_upload_password=<LUKS_KEY_UPLOAD_PASSWORD>
For each enrolled host:
- Fetches
/_LUKS_BACKUP_KEYfrom the client to a local staging directory. - Uploads the staged key to
//ipa-server/ansipa-luks-keys/<hostname>_LUKS_BACKUP_KEYviasmbclientusing a temporary credentials file (no_log, deleted inpost_tasks). - Removes the local staging copy after a successful upload.
Keys on the SMB share are accessible only to KeyAdmin group members (see SMB Shares).
Schedule automatic collection:
# Add to crontab on the Ansible controller
0 3 * * * cd /path/to/FreeipaAnsible/ansible && \
ansible-playbook -i inventory collect-luks-keys.yml \
-e luks_smb_server=ipa.corp.example.com \
-e luks_upload_password=<LUKS_KEY_UPLOAD_PASSWORD>
Host Group Reference
Device policies (host groups — applied machine-wide)
| Group prefix | Handled by | Effect |
|---|---|---|
ansipa-install-<pkg> |
ansipa-install-packages.sh |
Install/remove native package |
ansipa-module-<name> |
ansipa-install-modules.sh |
Run module script once |
fp_install-<app> |
ansipa-install-flatpaks.sh |
Install Flatpak app |
BaseUser |
auto-add-baseuser.sh |
Add user to local baseusers group |
policy-daemon-enable-<unit> |
ansipa-enforce-policies.sh |
systemctl enable --now <unit>; reverted on leave |
policy-daemon-disable-<unit> |
ansipa-enforce-policies.sh |
systemctl disable --now <unit>; reverted on leave |
dev_timeshift-backup |
ansipa-enforce-policies.sh |
Daily Timeshift snapshot at 03:00 |
dev_mon_clamscan |
ansipa-enforce-policies.sh |
Daily ClamAV + rkhunter + chkrootkit scan at 02:00; result reported to CheckMK (Ansipa_ClamScan local check) |
dev_no-local-users |
ansipa-enforce-policies.sh |
Lock passwords for root and all local users (UID ≥ 1000); reverted on leave |
local_sudo_<username> |
ansipa-enforce-policies.sh |
Grant <username> full sudo on this specific device; reverted on leave |
User policies (user groups — follow the user across all enrolled devices)
| Group prefix | Handled by | Effect |
|---|---|---|
usr_block-binary-<name> |
ansipa-enforce-policies.sh |
Prevent group members from running <name> on any enrolled host; use __ for . in Flatpak app IDs |
Policy Enforcement
ansipa-enforce-policies.sh runs every 30 minutes on each enrolled client (deployed by deploy-ansipa-policies.yml). All policies are idempotent and reversible — leaving a host/user group undoes the policy on the next run.
Binary Blocking (per user)
policy-block-binary-<name> is a FreeIPA user group, not a host group. Membership follows the user to every enrolled machine: a blocked user cannot run <name> regardless of which device they log into.
The enforcer queries all policy-block-binary-* user groups from FreeIPA on every run and installs a PATH-priority wrapper in /usr/local/bin/<name> for each one. The wrapper checks the caller's group membership at runtime via id(1) / SSSD and:
- blocks the command if the caller is a group member (exits 1 with a policy message);
- passes through to the real binary for all other users (searches native PATH dirs, then falls back to
flatpak run <name>).
Flatpak support: use __ in place of . in the group name. For example, policy-block-binary-org__gimp__Gimp blocks the Flatpak org.gimp.Gimp for group members while transparently invoking flatpak run org.gimp.Gimp for everyone else.
Deleting the IPA user group causes the wrapper to be removed on the next enforcer run. State is tracked in /var/lib/ansipa-policies/blocked-binaries.
Daemon Enable / Disable
| Group | Effect on join | Effect on leave |
|---|---|---|
policy-daemon-enable-<unit> |
systemctl enable --now <unit> |
systemctl disable --now <unit> |
policy-daemon-disable-<unit> |
systemctl disable --now <unit> |
systemctl enable --now <unit> |
The .service suffix is optional — it is appended automatically when the unit name contains no dot, so any systemd unit type works. If a unit appears in both enable and disable groups simultaneously, it is skipped with a warning.
State is tracked in /var/lib/ansipa-policies/daemon-enabled and daemon-disabled so revert actions are applied correctly when a host leaves a group.
ClamAV Scan → CheckMK (dev_mon_clamscan)
The scan results go straight to CheckMK — there is no SMB share or desktop-notification pipeline.
Client (dev_mon_clamscan)
└── cron 02:00: /usr/local/bin/ansipa-clamscan.sh
├── clamscan + rkhunter + chkrootkit → /var/log/ansipa-clamscan.log
└── writes CheckMK local check /usr/lib/check_mk_agent/local/ansipa_clamscan
CheckMK agent (installed by dev_mon_base)
└── collects the ansipa_clamscan local check on each poll
└── surfaces the "Ansipa_ClamScan" service:
OK = clean · WARN = stale DB / rkhunter warnings · CRIT = infected
Prerequisites:
- Add the host to
dev_mod_anti-malware(installs ClamAV, rkhunter, chkrootkit). - Add the host to
dev_mon_base(installs the CheckMK agent that collects the check). - Add the host to
dev_mon_clamscan(runs the scan + emits the check).
Leaving dev_mon_clamscan removes the cron, scan script, log, and local check on the next enforcer run.
Proxmox Infra Monitoring (PVE / PBS) → CheckMK
The Proxmox VE hypervisor and Proxmox Backup Server are infrastructure
appliances, not FreeIPA clients, so they sit outside the dev_mon_base
host-group flow above. Instead, image/ansipa-infra-checkmk-install.sh
wires them into the same CheckMK instance directly:
ansipa-infra-checkmk-install.sh <CMK_URL> <CMK_SITE> <CMK_SECRET> [HOST_FQDN] [CMK_USER]
Run as root on the PVE or PBS host itself (copy the image/ directory over
first, or scp just the three files). The script:
- auto-detects the role (
pveversion→ PVE,proxmox-backup-manager→ PBS), - installs the CheckMK agent (
.deb, falling back to the shell agent), - drops the matching local check into
/usr/lib/check_mk_agent/local/, - registers
cmk-agent-ctlpush mode if available (useful behind NAT), - registers the host in CheckMK under folder
/ansipa/infraand triggers service discovery + activate-changes.
It is idempotent — safe to re-run after a secret rotation or hostname change.
ansipa_pve (image/ansipa-pve-monitor.sh) reports, via pvesh:
Ansipa_PVE_Cluster (quorum/standalone), Ansipa_PVE_Storage (worst
utilisation across node storages), Ansipa_PVE_Guests (VM/CT running vs
stopped counts), Ansipa_PVE_Backup (freshness + outcome of the last
vzdump job).
ansipa_pbs (image/ansipa-pbs-monitor.sh) reports, via
proxmox-backup-manager: Ansipa_PBS_Datastores (worst datastore disk
usage via df), Ansipa_PBS_LastBackup (freshness + outcome of the last
backup task), Ansipa_PBS_TaskFailures (any failed GC/verify/sync/prune/
backup task in the last 24h).
Both scripts have no dependency beyond the Proxmox CLI tooling itself (no
jq/python3 required) and degrade a given service to CheckMK state 3
(UNKNOWN) rather than failing outright when a command is unavailable. CPU/
RAM/root-disk are already covered by CheckMK's stock Linux checks, so they
aren't duplicated here.
Local User Lockdown
Adding a host to the no_local_users group locks the password of every local account — root (UID 0) and all regular users (UID ≥ 1000) — using passwd -l. Accounts whose passwords are already locked (! or * prefix in shadow) are left untouched and are not tracked.
State is persisted in /var/lib/ansipa-policies/no-local-users (one username per line). Only accounts that were actively unlocked at apply time are written to this file, so the revert step only unlocks what was changed.
| Action | Effect |
|---|---|
Join no_local_users |
passwd -l on root + all UID ≥ 1000 local accounts |
Leave no_local_users |
passwd -u on every account listed in the state file |
Interaction with FreeIPA sudo: Domain accounts in the sudoers or sudo-nopasswd FreeIPA groups retain full sudo access via SSSD — local password lockdown does not affect them. Ensure at least one domain admin has sudo before adding a host to this group.
Per-device sudo grants
local_sudo_<username> is a host group that grants a specific user full sudo on that particular machine, independently of FreeIPA-wide sudo rules. This is useful for giving a user admin rights on their own workstation while keeping them unprivileged on shared servers.
| Action | Effect |
|---|---|
Join local_sudo_alice |
Creates /etc/sudoers.d/ansipa-local-sudo-alice with alice ALL=(ALL) ALL |
Leave local_sudo_alice |
Removes the drop-in on the next enforcer run |
State is tracked in /var/lib/ansipa-policies/local-sudo-users. Drop-ins are mode 0440 and validated by visudo syntax rules automatically.
LUKS Key Flow
Install time (arch-autoinstall.sh or archbaseos-guided-install.sh)
─────────────────────────────────────────────────────────────────
1. User sets primary LUKS passphrase interactively
2. 64-byte random key generated from /dev/urandom
3. Key enrolled in second LUKS slot
4. Key written to /_LUKS_BACKUP_KEY (mode 0400, root-only)
inside the encrypted Btrfs volume
Post-install (Ansible controller)
──────────────────────────────────
5. collect-luks-keys.yml runs
6. Fetches /_LUKS_BACKUP_KEY from each client (become: yes)
7. Uploads to //ipa-server/ansipa-luks-keys/<host>_LUKS_BACKUP_KEY
as 'luks-upload' service account (write-only, no_log)
8. Local staging copy deleted after successful upload
Recovery (KeyAdmin member)
───────────────────────────
9. Connect to //ipa-server/ansipa-luks-keys with a KeyAdmin Samba account
10. Retrieve <host>_LUKS_BACKUP_KEY and use with cryptsetup
The backup key lives inside the encrypted partition and is only accessible when the disk is already unlocked. Its purpose is to allow an admin to unlock the disk for recovery without knowing the user's passphrase.
Keycloak
keycloak-configure.sh performs the initial wiring after both FreeIPA and Keycloak containers are healthy:
- Creates the configured realm in Keycloak.
- Sets up an LDAP federation pointing at the FreeIPA LDAP/LDAPS endpoint.
- Configures user and group mappers.
Run it once after the first docker compose up:
cd setup/modules/FreeipaAnsible/image
./keycloak-configure.sh
The Keycloak admin console is available at http://<KC_HOSTNAME>:8080 (dev mode) or https://<KC_HOSTNAME>:8443 (requires TLS cert for production start command).
Auto Enrollment + Ansible
bash setup/modules/FreeipaAnsible/auto-enroll-ansible.sh
Combines FreeIPA client enrollment and Ansible deployment in one shot. Useful for provisioning scripts that run on first boot.