Found while bringing up the FreeIPA+CheckMK+Keycloak stack in a VM:
- Pin volume and network names (name:) so compose and the cgroup-v2 run.sh
wrapper share the same objects. Compose otherwise prefixes the project name
(image_cmk-creds, image_ipa-net), so the run.sh-started freeipa container
mounted a different cmk-creds volume than the compose-started checkmk wrote
to — the automation secret never reached IPA and the dev_mon_* integration
retried forever. run.sh also now labels the network it creates so compose
adopts it instead of erroring "exists but not created by compose".
- run.sh: mount cmk-creds into freeipa and include checkmk in the "all" target;
both were missing, so the CheckMK path was never wired up via run.sh.
- Fix both healthchecks: Keycloak has no curl/wget and serves /health on the
management port 9000 only when KC_HEALTH_ENABLED=true, so the old curl probe
left it permanently unhealthy — use a bash /dev/tcp probe against 9000.
CheckMK's /api/1.0/version needs auth (401), so probe the login page instead.
- Advertise a client-routable CheckMK URL (CMK_ADVERTISED_URL) in the
dev_mon_base description instead of the container-internal 172.30.0.12, which
enrolled clients cannot reach; falls back to the internal URL when unset.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUhrcFU8J1Hnf7vNqNxZNi
The 1550-line ansipa-enforce-policies.sh is replaced by a slim orchestrator
that sources policy files from policies.d/ — one file per policy type — via
a shared lib/ansipa-policy.sh that consolidates all IPA group discovery into
two queries (one host-show, one group-find) instead of five or more.
New files:
- ansible/lib/ansipa-policy.sh shared constants, log/warn, _ansipa_discover(),
_smb_parse_cred(), _ansipa_sync_policystore()
- ansible/policies.d/dev_daemon.sh
- ansible/policies.d/dev_timeshift-backup.sh
- ansible/policies.d/dev_security-scan.sh
- ansible/policies.d/dev_no-local-users.sh
- ansible/policies.d/dev_local-sudo.sh
- ansible/policies.d/dev_ssh.sh
- ansible/policies.d/dev_mon.sh includes usr_mon_logins (same CMK dep)
- ansible/policies.d/usr_admin.sh
- ansible/policies.d/usr_block-binary.sh
- ansible/policies.d/usr_prt.sh
- ansible/policies.d/usr_scan-notify.sh
- ansible/policies.d/usr_smb.sh
- ansible/policies.d/usr_policystore.sh NEW: auto-mount ~/policystore for
usr_smb_adm_policystore members
The policies.d/ folder on the FreeIPA container is exposed as an SMB share
(ansipa-policystore) so admins can live-edit policy files without Ansible.
The enforcer syncs from the share before each run; cached local files are
used as fallback when SMB is unreachable.
- ansipa-smb-setup.sh: adds ansipa-policystore share + smb-policystore system
user + usr_smb_adm_policystore IPA group with auto-generated credential
- deploy-ansipa-policies.yml: deploys lib/ and policies.d/ to enrolled clients;
adds optional seed_policystore task for bootstrap
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ansipa-enforce-policies.sh header: add missing dev_ssh_*, usr_smb_r_*,
usr_smb_rw_*, dev_mon_* (base/malware/timeshift/power), usr_mon_logins
with concise descriptions matching actual behaviour. Notes section
updated with CheckMK setup prerequisite.
- ansipa_packages local check: replace broken rpm||dpkg fallback (wc -l
always exits 0 so dpkg/pacman branches were never reached) with an
if/elif/elif that probes for rpm, dpkg, or pacman in order. Includes
pacman.log-based recent-packages list for Arch hosts.
- deploy-ansipa-policies.yml prerequisites: add CheckMK and SMB mount notes.
- .env.example: add CheckMK section (CMK_ADMIN_PASSWORD, CMK_SITE_ID).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds full CheckMK CE 2.3 monitoring integration to the ansipa policy
enforcement system. Monitoring is group-driven — adding a host to an
IPA hostgroup installs the relevant agent checks automatically.
New monitoring groups:
dev_mon_base — install check-mk-agent, register host in CMK;
packages check + built-in CPU/RAM/disk/uptime
dev_mon_malware — ClamAV scan results from dev_security-scan log
dev_mon_timeshift — Timeshift snapshot age (WARN >5d, CRIT >10d)
dev_mon_power — CPU package TDP via Intel RAPL or lm-sensors
usr_mon_logins — SSH login attempts (success/failed/invalid-user)
Key implementation details:
- ansipa-checkmk-setup.sh: one-time folder/group creation on FreeIPA
container; reads automation secret from shared cmk-creds volume;
stores credentials in IPA dev_mon_base description for clients
- ansipa-checkmk.service: starts after IPA first-boot, retries until
both IPA and CheckMK are ready
- docker-compose.yml: CheckMK container adds port 8000 (agent receiver
for push mode); command override writes automation.secret to shared
volume, patches cookie_auth.conf for API access, and polls
/cmk-creds/push-mode-*.req to configure push-agent hosts in hosts.mk
- ansipa-enforce-policies.sh: local check scripts for all 5 groups;
cmk-agent-ctl registration + auto push timer when push mode is active
- CheckMK REST API path corrected to /cmk/check_mk/api/1.0/ (not
/cmk/api/1.0/ which returns 404 in 2.3)
- .gitignore added to prevent accidental commit of .env test creds
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHops6PU4c2Mv5UyhUj8Ms
usr_smb_r_<name> / usr_smb_rw_<name> (user groups):
- Server creates /data/smb-shares/<name>/ with smb-<name> system user,
setgid directory, and Samba share stanzas [usr-<name>-r/rw] on startup
- Auto-generates credentials and stores them in IPA group descriptions
(cifs://<host>:<samba-user>:<password>) so no out-of-band secret distribution
- Client mounts //ipa/<share> at ~/‹name› for logged-in members;
rw membership wins over r when user is in both groups
- Revert: unmounts and rmdir the mount point on group leave
dev_ssh_<userid> (host group):
- Reads IPA user's SSH public keys (ipasshpubkey / --all output)
- Writes them into /home/<userid>/.ssh/authorized_keys in an
ansipa-managed section (preserves manually-added keys)
- Home dir is created if it doesn't exist (user may not have logged in yet)
- Revert: removes only the ansipa-managed section on group leave
- Enables same-key SSH access from any registered device to enrolled hosts
Also: add cifs-utils + openssh-server to Ansible package list
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHops6PU4c2Mv5UyhUj8Ms
Fixes found during a complete archiso → FreeIPA DC → client enrollment →
ansipa policy deployment test cycle (Rocky 9 client VM, FreeIPA in Docker):
arch-autoinstall.sh:
- Add openssh to pacstrap — was missing, breaking headless/automated installs
- Enable sshd at boot and set PermitRootLogin yes for post-install access
ansipa-install-packages.sh:
- Fix broken uninstall logic: three always-true conditions caused every package
removal to be silently skipped; replace with a state-file-based approach that
tracks packages installed by ansipa and removes only those when the IPA group
disappears
ansipa-enforce-policies.sh:
- Add usr_admin policy: creates /etc/sudoers.d/ansipa-usr-admin granting full
sudo to the FreeIPA usr_admin user group on every enrolled host; reverted
when the IPA group is deleted
- Add usr_prt_<printer> policy: auto-adds CUPS printers for FreeIPA user group
members at login; printer URI stored in IPA group description; per-user ACL;
reverted when group is deleted or membership ends
deploy-ansipa-policies.yml:
- Document usr_admin and usr_prt_* policies in header comment
- Install cups package on clients (required for printer policy)
FreeIPA container (image/):
- Add docker-env.service: extracts IPA_*/SMB_*/LUKS_*/KEYCLOAK_* env vars from
/proc/1/environ into /etc/container.env on container start; services read from
there rather than relying on PassEnvironment (which is lost on container restart)
- Add run.sh: wrapper that starts FreeIPA with --cgroupns host, required on cgroup
v2 hosts (WSL2, recent Linux) because Docker Compose schema does not expose
cgroupns_mode; also wires LUKS_KEY_UPLOAD_PASSWORD which the SMB service needs
- Dockerfile: copy + enable docker-env.service; add glibc-langpack-en for locale
- ansipa-smb.service: use EnvironmentFile=/etc/container.env (via docker-env.service)
instead of PassEnvironment; add docker-env.service dependency
- ipa-first-boot.service: add docker-env.service dependency + EnvironmentFile
- ipa-first-boot.sh: add --skip-mem-check (container has limited cgroup memory
visibility); remove --no-reverse from non-DNS path (was invalid without DNS)
- docker-compose.yml: add prominent cgroupns note explaining when to use run.sh
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHops6PU4c2Mv5UyhUj8Ms
ansipa-smb-setup.sh:
- Adds KeyAdmin Linux group and luks-upload service account (member of
KeyAdmin) on the IPA container, both persisted across restarts.
- LUKS base dir /data/luks-keys owned root:KeyAdmin, mode 2750 (setgid
so new files inherit the group).
- New [ansipa-luks-keys] SMB share: valid users = @KeyAdmin, read only,
write list = luks-upload. Human admins gain read access by being added
to KeyAdmin: useradd -r -G KeyAdmin <user> && smbpasswd -a <user>.
- LUKS_KEY_UPLOAD_PASSWORD sourced from env / /data/samba/ansipa-smb.env
alongside the existing SMB_SCAN_PASSWORD.
collect-luks-keys.yml:
- After fetching /_LUKS_BACKUP_KEY from each client, uploads it to the
ansipa-luks-keys share via smbclient using a temp credentials file
(no_log, deleted in post_tasks).
- Local staging copy is removed after a successful upload.
- SMB credentials file uses an epoch-stamped path to avoid collisions.
.env.example: documents LUKS_KEY_UPLOAD_PASSWORD.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ansipa-smb.service: WantedBy=multi-user.target (was smb.service) so the
setup service always runs at boot, not only when smb.service pulls it in
docker-compose.yml: add NetBIOS UDP ports 137/138 to match Dockerfile EXPOSE
and nmb.service being enabled
ansipa-smb-setup.sh:
- use printf '%q' when writing SMB_SCAN_PASSWORD to ansipa-smb.env so
passwords with spaces or shell-special chars are correctly quoted
- always write /etc/cron.d/ansipa-check-scans (remove the [[ ! -f ]] guard)
since /etc/cron.d is on the ephemeral container layer and is lost on
container recreation; the service runs on every start anyway
Dockerfile: add -e SMB_SCAN_PASSWORD and -p 445:445 to the quick-test comment
ansipa-fetch-alerts.sh: replace $NEW && log with [[ "$NEW" == true ]] && log
to avoid set -e ambiguity with the 'false' builtin
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Container (ansipa image):
- Add samba + cronie to Dockerfile; expose ports 445/139
- ansipa-smb-setup.sh: idempotent setup of smbd + scanupload user +
/data/scan-results/{archive,alerts}/ on every container start
- ansipa-smb.service: runs setup before smb.service on each boot
- ansipa-check-scans.sh: hourly cron on server; analyses archive logs for
ClamAV/rkhunter/chkrootkit findings and writes <host>/<date>.alert files
- docker-compose.yml: add SMB_SCAN_PASSWORD env var + port mappings
- .env.example: document SMB_SCAN_PASSWORD
Client (policy-security-scan):
- Scan script now uploads log to //ipa-server/ansipa-scans/archive/<host>/
via smbclient after each run
Client (policy-scan-notify — new policy group):
- ansipa-fetch-alerts.sh: root timer (10 min) downloads alerts from SMB into
~/administration/<hostname>/ for each active login session; deletes server
alert when user removes local file (acknowledgment)
- ansipa-scan-notify.sh: user daemon started via /etc/profile.d/ansipa-notify.sh;
sends notify-send every 10 min while *.alert files remain in ~/administration/
- deploy-ansipa-policies.yml: installs samba-client, deploys SMB creds file
(/etc/ansipa-smb.creds, 0600), and deploys both notification scripts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
freeipa-image-builder.sh: TUI chooser that builds a FreeIPA server image
and exports it to four target formats:
docker — builds via podman/docker, optional registry push
lxc — exports container rootfs as .tar.zst Proxmox CT template,
generates pct import instructions
proxmox-vm — downloads Rocky/Fedora cloud image, customizes with
virt-customize, outputs QCOW2 + cloud-init user-data.yml
oci-archive — skopeo OCI tarball for air-gapped import
Keycloak TUI option generates the full constellation:
docker-compose.yml FreeIPA + Keycloak + PostgreSQL stack
.env pre-filled env template (passwords placeholder)
keycloak-configure.sh post-start Keycloak REST API config script
image/Dockerfile: Fedora 41 + freeipa-server-dns + ansible-core,
systemd-enabled container (CMD /sbin/init).
image/ipa-first-boot.{sh,service}: systemd oneshot that runs
ipa-server-install on first container/VM boot from env vars
(IPA_DOMAIN, IPA_ADMIN_PASSWORD, IPA_DM_PASSWORD, and optionals).
ConditionPathExists=!/etc/ipa/default.conf makes it idempotent.
image/keycloak-configure.sh: Keycloak REST API automation that:
- waits for Keycloak readiness
- creates a realm
- wires FreeIPA LDAP user federation (READ_ONLY, vendor=rhds)
- adds attribute mappers: email, firstName, lastName, uidNumber
- adds group mapper (IPA groups → Keycloak groups, cn=groups,cn=accounts)
- triggers an initial full user sync
image/docker-compose.yml: freeipa + postgres + keycloak services on
a private 172.30.0.0/24 bridge; FreeIPA has a fixed IP so Keycloak
can resolve it via extra_hosts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>