Host groups named policy-daemon-enable-<unit> and
policy-daemon-disable-<unit> are now matched by a wildcard case arm in
the group parser — no per-service configuration required.
Enforcement (every 30 min via existing timer):
enable: systemctl enable --now <unit>; state written to
/var/lib/ansipa-policies/daemon-enabled
disable: systemctl disable --now <unit>; state written to
/var/lib/ansipa-policies/daemon-disabled
revert: when a host leaves a group the opposite action is applied
on the next run (enable→disable, disable→enable)
conflict: unit in both lists is skipped with a warning
The .service suffix is optional — _svc_unit() appends it when the name
contains no dot, so all systemd unit types work as-is.
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>
Binary blocking now applies two layers:
1. PATH-priority wrapper in /usr/local/bin/ (existing)
2. Empty AppArmor profile in /etc/apparmor.d/ loaded in enforce mode
An empty AppArmor profile denies all access — the blocked binary cannot
load shared libraries and exits immediately with a permission error,
covering callers that use absolute paths and bypassed the wrapper.
AppArmor layer is skipped silently when apparmor_parser is not present,
and deferred with a warning if the real binary is not yet installed.
Profiles are unloaded and deleted when the host leaves the policy group.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New playbook collect-luks-keys.yml connects to all enrolled FreeIPA
clients, checks for /_LUKS_BACKUP_KEY (placed there by the installer
when encryption is enabled), and fetches each key to the Ansible
controller as luks-keys/<HOSTNAME>_LUKS_BACKUP_KEY (mode 0400).
Hosts without the file are reported but not treated as errors.
The luks-keys/ store directory is created with mode 0700.
Usage:
ansible-playbook -i inventory collect-luks-keys.yml
Can be scheduled via cron on the controller for automatic collection.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
IPA group naming: fp_install_org__mozilla__firefox (dots encoded as __)
Decoding: sed strips prefix, then s/__/./g restores the Flatpak app ID.
Single underscores in app IDs are preserved unambiguously.
ansipa-install-flatpaks.sh:
- kinit with host keytab, queries ipa group-find --pkey-only with awk $NF
- Validates decoded ID against reverse-domain regex before installing
- Ensures flathub system remote exists
- System-scope install (flatpak install --system) since service runs as root
- Timer offset to 4 min (after packages at 2 min) to avoid contention
deploy-ansipa-install.yml updated to deploy the Flatpak script, service,
and timer alongside the existing package installer.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add freeipa-client module (sssd, cyrus-sasl-gssapi, freeipa-client AUR)
with post-install enrollment hints; wired into tui-install.sh and
install-modules.sh
- Add ansipa-install-modules.sh: reads IPA host groups named
ansipa-module-<name>, applies matching module scripts via a yay wrapper
that drops to ANSIPA_USER so AUR builds work from the root service
- Add ansipa-install-modules.service + .timer (boot + 30 min)
- Add deploy-ansipa-modules.yml Ansible playbook that deploys scripts,
writes /etc/ansipa-modules.conf, and enables the timer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>