feat(ansipa): rework scan-notify as per-user policy

policy-scan-notify is now a FreeIPA *user* group instead of a host group,
so alert notifications follow the user to every enrolled machine. The
fetch-alerts timer is installed fleet-wide on any host where the group exists;
the profile.d snippet gates notification daemon start on runtime group
membership (id(1) / SSSD) so non-members log in unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main
Amir Alexander Abdelbaki 2026-05-20 16:41:35 +02:00
parent 87b62f368b
commit 547c997614
2 changed files with 36 additions and 9 deletions

View File

@ -277,7 +277,6 @@ Keys on the SMB share are accessible only to `KeyAdmin` group members (see [SMB
| `policy-daemon-disable-<unit>` | `ansipa-enforce-policies.sh` | `systemctl disable --now <unit>`; reverted on leave |
| `policy-timeshift-backup` | `ansipa-enforce-policies.sh` | Daily Timeshift snapshot at 03:00 |
| `policy-security-scan` | `ansipa-enforce-policies.sh` | Daily ClamAV + rkhunter + chkrootkit scan + SMB upload |
| `policy-scan-notify` | `ansipa-enforce-policies.sh` | Fetch server alerts, notify user every 10 min until acknowledged |
| `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 |
@ -286,12 +285,13 @@ Keys on the SMB share are accessible only to `KeyAdmin` group members (see [SMB
| Group prefix | Handled by | Effect |
|--------------|-----------|--------|
| `policy-block-binary-<name>` | `ansipa-enforce-policies.sh` | Prevent group members from running `<name>` on any enrolled host; use `__` for `.` in Flatpak app IDs |
| `policy-scan-notify` | `ansipa-enforce-policies.sh` | Fetch server alerts and notify group members every 10 min until acknowledged; follows the user across all enrolled devices |
---
## 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 group undoes the policy on the next run.
`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)
@ -305,6 +305,14 @@ The enforcer queries all `policy-block-binary-*` user groups from FreeIPA on eve
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`.
### Scan Notifications (per user)
`policy-scan-notify` is a FreeIPA **user group**, not a host group. Membership follows the user to every enrolled machine: group members receive scan alert notifications regardless of which device they log into.
The enforcer checks whether the `policy-scan-notify` group exists in FreeIPA on every run. If it does, it installs the `ansipa-fetch-alerts` systemd timer fleet-wide (root service, fires every 10 min) and a `/etc/profile.d/ansipa-notify.sh` snippet. The profile.d snippet checks group membership at login via `id(1)` / SSSD and starts `ansipa-scan-notify.sh` as a background user daemon only for members. Non-members log in unaffected.
Deleting the `policy-scan-notify` IPA user group causes the timer and profile.d snippet to be removed on the next enforcer run.
### Daemon Enable / Disable
| Group | Effect on join | Effect on leave |

View File

@ -13,6 +13,7 @@
# If a unit appears in both enable and disable groups it is skipped.
# policy-timeshift-backup Enforce a daily Timeshift snapshot (requires timeshift installed)
# policy-security-scan Enforce daily ClamAV + rkhunter + chkrootkit scans
# policy-scan-notify (see User-group section below — treated as a user group)
# no_local_users Lock passwords for root and all local users (UID >= 1000) so
# that only FreeIPA domain accounts with centrally-managed sudo
# rules can authenticate and gain elevated privileges.
@ -29,6 +30,12 @@
# Flatpak org.gimp.Gimp). Enforced via a PATH-priority wrapper
# that checks group membership at runtime via SSSD/id(1).
# Removing the user group from FreeIPA reverts the wrapper.
# policy-scan-notify Members receive scan alert notifications on every enrolled device
# they log into. The fetch-alerts timer is installed fleet-wide
# when the group exists; the notification daemon starts on login
# only for group members (checked via id(1) / SSSD).
# Deleting the IPA user group removes the timer and profile.d
# snippet on the next enforcer run.
#
# Notes:
# - Install scan tools first: add the host to ansipa-module-anti-malware.
@ -76,7 +83,6 @@ if [[ -n "$RAW_GROUPS" ]]; then
policy-daemon-disable-*) ACTIVE_DAEMON_DISABLE+=("${g#policy-daemon-disable-}") ;;
policy-timeshift-backup) WANT_TIMESHIFT_BACKUP=true ;;
policy-security-scan) WANT_SECURITY_SCAN=true ;;
policy-scan-notify) WANT_SCAN_NOTIFY=true ;;
no_local_users) WANT_NO_LOCAL_USERS=true ;;
local_sudo_*) ACTIVE_LOCAL_SUDO_USERS+=("${g#local_sudo_}") ;;
esac
@ -102,12 +108,20 @@ while IFS= read -r _grp; do
done <<< "$_BLOCK_LIST"
unset _BLOCK_LIST _grp _raw
# ── Fetch user-group-based scan-notify policy from FreeIPA ────────────────────
# policy-scan-notify is a FreeIPA *user* group — membership follows the user to
# every enrolled host. Install the fetch-alerts timer on any device where the
# group exists; the profile.d snippet gates daemon start on runtime membership.
if ipa group-show policy-scan-notify >/dev/null 2>&1; then
WANT_SCAN_NOTIFY=true
fi
log "Device policies — daemon-enable: ${ACTIVE_DAEMON_ENABLE[*]:-none}" \
"| daemon-disable: ${ACTIVE_DAEMON_DISABLE[*]:-none}" \
"| timeshift-backup: $WANT_TIMESHIFT_BACKUP | security-scan: $WANT_SECURITY_SCAN" \
"| scan-notify: $WANT_SCAN_NOTIFY | no-local-users: $WANT_NO_LOCAL_USERS" \
"| local-sudo: ${ACTIVE_LOCAL_SUDO_USERS[*]:-none}"
log "User policies — block-binary: ${ACTIVE_BLOCK_BINARIES[*]:-none}"
"| no-local-users: $WANT_NO_LOCAL_USERS | local-sudo: ${ACTIVE_LOCAL_SUDO_USERS[*]:-none}"
log "User policies — block-binary: ${ACTIVE_BLOCK_BINARIES[*]:-none}" \
"| scan-notify: $WANT_SCAN_NOTIFY"
# ── Helpers ───────────────────────────────────────────────────────────────────
_in_block_list() {
@ -264,7 +278,10 @@ else
fi
# ── Scan notification daemon ──────────────────────────────────────────────────
# policy-scan-notify:
# policy-scan-notify is a FreeIPA *user* group (not a host group). The fetch-
# alerts timer runs fleet-wide on any host where the group exists; the profile.d
# snippet starts the notification daemon on login only for group members
# (checked via id(1) / SSSD so no IPA query is needed at login time).
# - Root timer (every 10 min): ansipa-fetch-alerts.sh downloads alerts from the
# server SMB share and places them in ~/administration/<hostname>/ per active user.
# - profile.d snippet: starts ansipa-scan-notify.sh as a user daemon on login;
@ -317,10 +334,12 @@ UNIT
if [[ ! -f "$NOTIFY_PROFILED" ]]; then
log "Installing /etc/profile.d/ansipa-notify.sh"
cat > "$NOTIFY_PROFILED" <<'PROFILED'
# ansipa-notify: launch the scan alert notification daemon on login.
# ansipa-notify: launch the scan alert notification daemon on login for
# members of the policy-scan-notify FreeIPA user group.
# Managed by ansipa-enforce-policies — do not edit manually.
_NOTIFY_DAEMON=/usr/local/bin/ansipa-scan-notify.sh
if [[ -x "$_NOTIFY_DAEMON" ]] && \
id -nG 2>/dev/null | grep -qw "policy-scan-notify" && \
! pgrep -u "$(id -u)" -f "ansipa-scan-notify" >/dev/null 2>&1; then
"$_NOTIFY_DAEMON" &
disown
@ -334,7 +353,7 @@ else
systemctl disable --now ansipa-fetch-alerts.timer 2>/dev/null || true
rm -f "$FETCH_SVC" "$FETCH_TIMER"
systemctl daemon-reload
log "Removed ansipa-fetch-alerts timer (host left policy-scan-notify group)"
log "Removed ansipa-fetch-alerts timer (policy-scan-notify user group no longer exists)"
fi
if [[ -f "$NOTIFY_PROFILED" ]]; then
rm -f "$NOTIFY_PROFILED"