# 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](ansipa-setup.md). 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 ```bash 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: ```bash 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](ansipa-setup.md). ### 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_clamscan` > policy now reports them to CheckMK via a local check (`Ansipa_ClamScan`), so > there is no `ansipa-scans` share or `scanupload` account any more. ### `ansipa-luks-keys` — LUKS Backup Key Store - **Path:** `/data/luks-keys` - **Permissions:** write-only for `luks-upload`; read-only for members of the `KeyAdmin` Linux 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 `KeyAdmin` group on the container: ```bash # On the freeipa container useradd -r -G KeyAdmin smbpasswd -a ``` The `KeyAdmin` group and the `luks-upload` service account are created by `ansipa-smb-setup.sh` on 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](ansipa-setup.md#arch-caveat--autofs--freeipa-client-from-the-aur) for details. ### Via Installer Module Select `freeipa-client` during `tui-install.sh` or `install-modules.sh`. ### Manual Enrollment Three modes: ```bash # 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 ```json { "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 ```bash 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-` and installs or removes packages to match. **Group naming convention:** `ansipa-install-firefox` → installs `firefox`. ### Deploy Module Auto-Installer ```bash ansible-playbook -i inventory deploy-ansipa-modules.yml \ [-e ansipa_user=amir] ``` Deploys `ansipa-install-modules.sh` + timer. Queries for groups named `ansipa-module-` and runs the matching script from `/usr/local/lib/ansipa-modules/.sh`. Module scripts are copied from `setup/modules/optional-Modules/apps/*.sh`. Each module is applied once and stamped in `/var/lib/ansipa-modules/.done`. ### Deploy BaseUser Sync ```bash 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 ```bash 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. Installs the Ansible-deployed fallback copy of `policies.d/` — see [Git-Based Policy Distribution](#git-based-policy-distribution) for the live, signed update path that supersedes it. ### Deploy Signed Git-Based Policy Puller ```bash ansible-playbook -i inventory deploy-ansipa-git-pull.yml \ -e ansipa_git_host=ipa.corp.example.com -e ansipa_git_port=2222 \ -e @vault-ansipa-git-pull.yml # ansipa_git_host_key, ansipa_git_deploy_key, ansipa_gpg_pubkey ``` Run this *after* `deploy-ansipa-policies.yml`. Bootstraps the unprivileged `_ansipa` puller account, pins the git server's SSH host key, installs this node's read-only deploy key and the operator's GPG public key, clones the bare mirror, and installs `ansipa-pull-apply.sh` + a systemd timer (10 min, ±90s jitter) plus a sudoers drop-in scoped to one exact command. See [Git-Based Policy Distribution](#git-based-policy-distribution) below. ### Collect LUKS Backup Keys ```bash ansible-playbook -i inventory collect-luks-keys.yml \ -e luks_smb_server=ipa.corp.example.com \ -e luks_upload_password= ``` For each enrolled host: 1. Fetches `/_LUKS_BACKUP_KEY` from the client to a local staging directory. 2. Uploads the staged key to `//ipa-server/ansipa-luks-keys/_LUKS_BACKUP_KEY` via `smbclient` using a temporary credentials file (`no_log`, deleted in `post_tasks`). 3. Removes the local staging copy after a successful upload. Keys on the SMB share are accessible only to `KeyAdmin` group members (see [SMB Shares](#smb-shares)). **Schedule automatic collection:** ```bash # 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= ``` --- ## Git-Based Policy Distribution The old distribution path — an enforcer running as root, blind-syncing whatever `.sh` files sat on the `ansipa-policystore` SMB share and `source`-ing them with no integrity check — is gone. Policy content now reaches a node only via a GPG-signed git commit, verified before anything derived from it runs as root. **Goal:** nodes pull on a timer; a pull failure (git server/network unreachable) is a no-op — the node keeps running its last-known-good, already-verified policy indefinitely. Rollback/replay of an old-but-validly- signed commit is detected and refused. No persistent, executable copy of policy scripts sits on disk between runs. ### Components | Piece | Where | Runs as | |---|---|---| | `ansipa-git-server-setup.sh` / `ansipa-git-sshd.service` | FreeIPA container (`image/`) | root (container) | | `ansipa-policy.git` (bare repo) | `/data/git-server/repo/` on the container, persisted | owned by `git` | | `ansipa-pull-apply.sh` / `ansipa-pull.timer` | every enrolled client (`ansible/`) | `_ansipa` (unprivileged) | | `ansipa-enforce-policies.sh` | every enrolled client | root, via one scoped `sudo` command | **Git server** — rolled into the same ANSIPA container as everything else ("own service, own attack surface" means *logically* separate, not a separate LXC): a dedicated system user `git` with shell `git-shell` (`git-upload-pack`/`git-receive-pack` only, no interactive shell even if a key leaks), and its own `sshd` instance on a distinct port (`ANSIPA_GIT_SSH_PORT`, default 2222) with `PasswordAuthentication no`. The admin/authoring key gets full push access (restricted only by `git-shell`); per-node read-only deploy keys are additionally forced to `git-upload-pack ` via `command=` in `authorized_keys` (defense in depth — GPG signing is the real trust boundary regardless). Register a node's deploy key with: ```bash docker exec freeipa ansipa-git-add-deploy-key.sh "ssh-ed25519 AAAA...== node.example.com" ``` **Commit signing** — every commit must be GPG-signed on the authoring machine (`git config commit.gpgsign true`); a `pre-receive` hook on the server (`git verify-commit` on every incoming commit) rejects unsigned or badly-signed pushes as defense in depth. The signing *private* key never leaves the authoring machine — only the public key (`ANSIPA_GIT_SIGNING_PUBKEY`) is on the container. **Node-side puller (`ansipa-pull-apply.sh`, run by `_ansipa` via `ansipa-pull.timer`)** — each run: 1. `git fetch` the mirror. Failure here (server/network down) is the designed no-op path — exit, last-applied policy keeps running. 2. `git verify-commit` the new `HEAD`. Failure → hard refusal + alert, nothing executed. 3. `git merge-base --is-ancestor ` — refuses anything that isn't a fast-forward (blocks rollback/replay of an old-but-validly-signed commit). 4. Materializes the verified tree into a fresh tmpfs workdir (`/run/ansipa/current`), normalizing permissions (no setuid, no unexpected modes) since git doesn't track real ownership/setuid bits. Wiped before use and unconditionally wiped after via `trap ... EXIT` — executable policy content exists on disk only for the duration of one run. 5. Hands off to root via **one exact, argument-free** `sudo` command (`/etc/sudoers.d/ansipa-git-pull`): `sudo -n /usr/local/bin/ansipa-enforce-policies.sh`. A compromised `_ansipa` account cannot pivot to arbitrary root execution — it can only trigger the enforcer, which only does what the already-verified commit told it to. 6. Only on a successful apply does `/var/lib/ansipa/last-applied-commit` advance, so a failed apply doesn't get silently skipped next time. `lib/ansipa-policy.sh` resolves `POLICY_DIR` to `/run/ansipa/current/policies.d` when the puller has populated it (the live, verified source), falling back to the Ansible-deployed `/usr/local/lib/ansipa/policies.d` baseline otherwise — there is no third, unverified source. `usr_smb_adm_policystore` (the `~/policystore` SMB mount) still exists as an **editing convenience only**: changes made there take effect only once committed, signed, and pushed. ### Bootstrap order 1. `image/ansipa-git-server-setup.sh` runs automatically on the FreeIPA container (`ansipa-git-server-setup.service` + `ansipa-git-sshd.service`); set `ANSIPA_GIT_ADMIN_PUBKEY` / `ANSIPA_GIT_SIGNING_PUBKEY` in `.env` first. 2. Fetch and **verify out of band** the container's git-sshd host key (`ssh-keyscan -p 2222 ` alone is not verification — confirm the fingerprint through a separate channel). 3. `ansible-playbook deploy-ansipa-policies.yml` (baseline enforcer + fallback policies). 4. `ansible-playbook deploy-ansipa-git-pull.yml` with the pinned host key, this node's deploy private key, and the operator's GPG public key (vault these). 5. Register the node's deploy key on the server: `ansipa-git-add-deploy-key.sh`. 6. Author policy changes on a git-signing-capable machine, commit with `commit.gpgsign=true`, push to `git@:ansipa-policy.git` — it propagates fleet-wide within one `ansipa-pull.timer` tick. --- ## Host Group Reference ### Device policies (host groups — applied machine-wide) | Group prefix | Handled by | Effect | |--------------|-----------|--------| | `ansipa-install-` | `ansipa-install-packages.sh` | Install/remove native package | | `ansipa-module-` | `ansipa-install-modules.sh` | Run module script once | | `fp_install-` | `ansipa-install-flatpaks.sh` | Install Flatpak app | | `BaseUser` | `auto-add-baseuser.sh` | Add user to local `baseusers` group | | `policy-daemon-enable-` | `ansipa-enforce-policies.sh` | `systemctl enable --now `; reverted on leave | | `policy-daemon-disable-` | `ansipa-enforce-policies.sh` | `systemctl disable --now `; 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_` | `ansipa-enforce-policies.sh` | Grant `` 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-` | `ansipa-enforce-policies.sh` | Prevent group members from running `` 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-` is a FreeIPA **user group**, not a host group. Membership follows the user to every enrolled machine: a blocked user cannot run `` 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/` 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 `). **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-` | `systemctl enable --now ` | `systemctl disable --now ` | | `policy-daemon-disable-` | `systemctl disable --now ` | `systemctl enable --now ` | 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 [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-ctl` push mode if available (useful behind NAT), - registers the host in CheckMK under folder `/ansipa/infra` and 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_` 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/_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 _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: 1. Creates the configured realm in Keycloak. 2. Sets up an LDAP federation pointing at the FreeIPA LDAP/LDAPS endpoint. 3. Configures user and group mappers. Run it once after the first `docker compose up`: ```bash cd setup/modules/FreeipaAnsible/image ./keycloak-configure.sh ``` The Keycloak admin console is available at `http://:8080` (dev mode) or `https://:8443` (requires TLS cert for production `start` command). --- ## Auto Enrollment + Ansible ```bash 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.