Commit Graph

41 Commits (c1e54f4f7513d5168d17876e3647471962bba37b)

Author SHA1 Message Date
Amir Alexander Abdelbaki c1e54f4f75 feat(freeipa-ansible): add CheckMK monitoring for Proxmox VE + PBS
Proxmox VE hypervisors and Proxmox Backup Server aren't FreeIPA clients,
so they sit outside the dev_mon_base host-group flow. Add standalone
CheckMK local checks (cluster/storage/guests/backup for PVE; datastore
usage/last-backup/task-failures for PBS) plus a small installer that
detects the role, installs the agent, and registers the host under
/ansipa/infra — mirroring the existing dev_mon.sh pattern without
requiring IPA enrollment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 11:31:03 +02:00
Amir Alexander Abdelbaki 4f22a3b03f refactor(ansipa): dev_security-scan → dev_mon_clamscan, report to CheckMK; drop SMB scan pipeline
Rename the security-scan policy to dev_mon_clamscan (fits the XXX_mon_* naming
for monitoring groups) and make it hand results to CheckMK natively instead of
uploading logs to an SMB share.

- dev_mon_clamscan.sh: runs the daily ClamAV/rkhunter/chkrootkit scan and writes
  its own CheckMK local check (ansipa_clamscan → service "Ansipa_ClamScan"); the
  agent from dev_mon_base collects it. Removes the SMB upload entirely. Discovery
  matches dev_mon_clamscan BEFORE the generic dev_mon_* catch-all so it sets
  WANT_CLAMSCAN rather than being treated as a dev_mon.sh check. mkdir -p the
  cron dir (minimal hosts lack /etc/cron.d until cron is installed).
- Remove the now-redundant dev_mon_malware check from dev_mon.sh (subsumed).
- Remove usr_scan-notify and the whole SMB scan-alert pipeline it fed:
  ansipa-fetch-alerts.sh, ansipa-scan-notify.sh, server ansipa-check-scans.sh,
  the ansipa-scans Samba share + scanupload account, /etc/ansipa-smb.creds, and
  the SMB_SCAN_PASSWORD/smb_scan_password variables (playbook, compose, run.sh,
  .env, Dockerfile). The ansipa-luks-keys and ansipa-policystore shares stay.

Verified live on the Arch client: enrolling in dev_mon_clamscan writes the scan
script, cron, and local check (which reports "Ansipa_ClamScan"); leaving the
group removes all three. Docs updated (policy tables, architecture, SMB shares,
scan-flow → CheckMK).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUhrcFU8J1Hnf7vNqNxZNi
2026-07-02 13:49:18 +02:00
Amir Alexander Abdelbaki adea0f457d fix(ansipa): reliable nginx + keycloak healthchecks
Both containers were reporting "unhealthy" while serving fine — the
healthchecks I added probed the wrong address/endpoint:

- nginx: `wget localhost` resolves to IPv6 ::1 inside the container, but nginx
  listens on IPv4 0.0.0.0:80 only, so the probe was refused. Use 127.0.0.1.
- keycloak: with KC_HTTP_RELATIVE_PATH=/auth, /health/ready on the 9000
  management port returns 404. Probe /auth/realms/master on the serving port
  8080 instead (bash /dev/tcp — the image has no curl/wget).

Verified all five containers report healthy and the gateway serves /, /ipa/ui/,
/cmk/ and /auth/ correctly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUhrcFU8J1Hnf7vNqNxZNi
2026-07-02 13:22:24 +02:00
Amir Alexander Abdelbaki be757fda5a feat(ansipa): nginx gateway — reverse proxy + portal start-page
Add an nginx service that fronts all three web UIs on a single hostname/port
and serves a portal landing page, designed to sit behind the operator's own
TLS-terminating reverse proxy.

- Each backend is proxied at the path it already serves natively — FreeIPA
  /ipa, CheckMK /cmk, Keycloak under a configured /auth relative path — so no
  fragile path rewriting is needed and one upstream proxy line exposes
  everything. FreeIPA's strict Host/Referer anti-CSRF checks are satisfied by
  pinning Host to the IPA hostname and rewriting Referer.
- Honors incoming X-Forwarded-Proto/For so it works behind a second reverse
  proxy (double-proxy verified: portal, FreeIPA, CheckMK and Keycloak all
  reachable through two layers, and a CheckMK login POST completes to an
  authenticated session through the gateway).
- Keycloak: KC_HTTP_RELATIVE_PATH=/auth + KC_PROXY_HEADERS=xforwarded so it
  builds correct URLs behind the gateway; deliberately not forwarding
  X-Forwarded-Port (this gateway is :80 internally; the public port is the
  upstream proxy's) to avoid https://host:80 redirects.
- .env.example documents ANSIPA_HTTP_PORT and gives the exact upstream
  reverse-proxy snippet (nginx location block + Caddy one-liner).

Portal is a static cyberqueer-themed page linking to /ipa/ui/, /cmk/, /auth/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUhrcFU8J1Hnf7vNqNxZNi
2026-07-02 12:36:28 +02:00
Amir Alexander Abdelbaki b21f415cde fix(ansipa): hostname-binary-free FQDN + push-mode orchestration (NAT path)
Found while running the policy matrix and NAT/WAN scenario in VMs:

- ansipa-enforce-policies.sh and the dev_security-scan generated script called
  `hostname -f`, but the hostname binary (inetutils) is not installed on a
  minimal Arch system, so the enforcer aborted with "hostname: command not
  found" on every run. Add an ansipa_fqdn() helper (hostnamectl / getent /
  /etc/hostname / kernel-hostname fallback chain) and use it; inline the same
  fallback in the standalone scan script.

- Fix two stacked bugs that meant CheckMK push mode — the only monitoring
  transport that works when a client is behind NAT — never functioned:
  * ansipa-checkmk-setup.sh referenced IPA_ADMIN_PASS in the DONE_FLAG
    early-exit path, but that variable was assigned only afterwards, so every
    rerun kinit'd with an empty password, failed silently, and skipped the
    push-mode request writer. Resolve config before the early exit.
  * _write_push_requests parsed `ipa hostgroup-show dev_mon_base` members with
    awk that did `next` on the "Member hosts:" line — but ipa prints members on
    that same line, so the only line with hostnames was discarded and no push
    request was ever written. Rewrite the awk to capture the label line's value
    plus wrapped continuation lines. Verified end-to-end: requests are written
    and CheckMK flips the host to cmk_agent_connection=push-agent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUhrcFU8J1Hnf7vNqNxZNi
2026-07-02 12:25:03 +02:00
Amir Alexander Abdelbaki 0f767fb051 fix(ansipa): docker stack volume/network naming, healthchecks, client-routable CMK URL
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
2026-07-02 11:28:00 +02:00
Amir Alexander Abdelbaki 3ee375b1c1 fix(ansipa): FreeIPA enrollment for Arch/Fedora/RHEL + autofs AUR workaround
Found while testing client enrollment across distros in VMs:

- Restore the executable bit on freeipa-enroll.sh (lost on a prior edit), which
  made freeipa-client.sh fail with a misleading "not found" for every path.
- Arch: freeipa-client is AUR-only, not the repo package "freeipa" — pacman -S
  freeipa died with "target not found". Install sssd/krb5 from repos and
  freeipa-client via an aur_install helper that drops to the sudo user.
- Bundle autofs-pkgbuild/: freeipa-client (AUR) hard-depends on autofs (AUR),
  whose PKGBUILD pins autofs-5.1.9.tar.xz — pruned from kernel.org at the 5.2.0
  release, so it 404s and blocks all Arch enrollment. Build autofs ourselves
  from upstream git pinned to a 5.1.9-series commit that already carries the
  cyrus-sasl function-pointer fix GCC 14+ needs, and install it before the
  freeipa-client AUR build so the broken AUR autofs is never touched. Verified
  the bundled PKGBUILD builds and installs cleanly on Arch.
- Fedora vs RHEL/Rocky/Alma use different client package names: keep
  freeipa-client on Fedora, use ipa-client on the RHEL family (freeipa-client
  does not exist there). Split the case arms accordingly.
- Guard both freeipa-client.sh entry points against unattended/no-TTY runs: the
  module TUI would hang on a dialog menu, and the wrapper would block on an
  interactive password read when the shipped answerfile has a blank password.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUhrcFU8J1Hnf7vNqNxZNi
2026-07-02 11:27:46 +02:00
Amir Alexander Abdelbaki 03db4f00a7 fix(ansipa): Arch package/hostname fixes, safer seed_policystore delegation
- Use samba/openssh package names on Arch instead of Debian-style samba-client/openssh-server
- Skip AUR-only freeipa install when ipa-client-install already present; skip oddjob if not in official repos
- Guard seed_policystore delegate_to when ipa_server_host is undefined
- Make hostname detection and OS version parsing more robust
- Fix inverted DNS_UPDATE/CONFIGURE_SUDO enrollment flags
2026-07-02 07:01:46 +02:00
Amir Alexander Abdelbaki 815c0e57a8 refactor(ansipa): split enforce-policies monolith into modular policies.d/ system
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>
2026-07-01 19:27:35 +02:00
Amir Alexander Abdelbaki 0dd7ce3b51 docs(ansipa): document all policies; fix package check for pacman/dpkg
- 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>
2026-07-01 18:46:27 +02:00
Amir Alexander Abdelbaki 6bb19cea78 feat(ansipa): integrate CheckMK CE monitoring
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
2026-07-01 18:32:28 +02:00
Amir Alexander Abdelbaki 76c3c2c1c5 feat(ansipa): add usr_smb_* and dev_ssh_* policies
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
2026-07-01 13:52:26 +02:00
Amir Alexander Abdelbaki 00ce4f5260 fix(ansipa): fix ansipa-fetch-alerts.sh alert delivery and acknowledgment
Three bugs fixed, all confirmed end-to-end in a live FreeIPA + client VM:

1. Wrong `who` column: loginctl is SESSION/UID/USER (col 3), but `who` is
   USER/TTY/DATE (col 1) — the fallback used col 3, delivering to the wrong
   user.  Split into _parse_session_user() with per-source column selection.

2. Delivered to all users: alerts were sent to every active non-root session
   instead of only members of usr_scan-notify.  Added `id -nG` group check
   inside _parse_session_user().

3. smbclient ls path breakage: `smbclient -c "ls alerts\host\"` treats the
   argument as a glob pattern, returning NT_STATUS_NO_SUCH_FILE.  Changed to
   `cd alerts\host; ls` which correctly lists directory contents.

Bonus: track ACKNOWLEDGED set so alerts deleted-from-server in the current
run are not re-downloaded (avoiding a spurious WARN on the same run).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DHops6PU4c2Mv5UyhUj8Ms
2026-07-01 11:37:00 +02:00
Amir Alexander Abdelbaki 65e859b8f9 fix(ansipa): full end-to-end test + multiple bug fixes
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
2026-07-01 10:30:06 +02:00
Amir Alexander Abdelbaki 9289f01965 feat(ansipa): unify FreeIPA group naming with dev_* / usr_* prefixes
All ansipa host/user group names now follow a consistent prefix scheme:

  dev_mod_<name>          — dotfiles module install (was ansipa-module-)
  dev_fp_<app-id>         — Flatpak install (was fp_install_)
  dev_pkg_<package>       — native package install (was ansipa-install-)
  dev_daemon-enable-<u>   — service enable policy (was policy-daemon-enable-)
  dev_daemon-disable-<u>  — service disable policy (was policy-daemon-disable-)
  dev_timeshift-backup    — backup policy (was policy-timeshift-backup)
  dev_security-scan       — scan policy (was policy-security-scan)
  dev_no-local-users      — auth lockdown (was no_local_users)
  dev_local-sudo-<user>   — per-device sudo grant (was local_sudo_)
  usr_block-binary-<name> — per-user binary block (was policy-block-binary-)
  usr_scan-notify         — per-user alert notification (was policy-scan-notify)

Also adds a JSON state manifest (manifest.json) to ansipa-install-modules
and tightens the FreeIPA enrollment guard to check /etc/ipa/default.conf.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LcnnA1whUwQkDv1omsgh9Y
2026-06-26 11:48:24 +02:00
Amir Alexander Abdelbaki 547c997614 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>
2026-05-20 16:41:35 +02:00
Amir Alexander Abdelbaki 87b62f368b feat(ansipa): rework binary blocking as per-user policy; add local_sudo device policy
policy-block-binary-<name> is now a FreeIPA *user* group instead of a host group,
so restrictions follow the user to every enrolled machine. The PATH wrapper is
installed on all hosts and checks group membership at runtime via id(1)/SSSD,
passing non-members through transparently. __ in the group name decodes to .
so Flatpak app IDs are supported (flatpak run fallback included). AppArmor layer
removed since per-user confinement requires a different approach and the wrapper
alone is sufficient. Adds local_sudo_<username> host group policy which writes
a sudoers drop-in granting that user full sudo on the specific device, reverted
on group leave.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 16:31:43 +02:00
Amir Alexander Abdelbaki 6ad8d0d488 feat(ansipa): add no_local_users device policy to lock all local account passwords
Adds a new host group policy `no_local_users` that locks the passwords of root
and all local users (UID >= 1000) via `passwd -l`, ensuring only FreeIPA domain
accounts with centrally-managed sudo rules can authenticate and gain elevated
privileges. Leaving the group reverts by unlocking every account tracked in the
state file. Updates docs with group reference entry and Local User Lockdown section.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 16:18:48 +02:00
Amir Alexander Abdelbaki 5d56984e38 feat(ansipa): store LUKS backup keys on SMB share with KeyAdmin access control
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>
2026-05-20 15:33:17 +02:00
Amir Alexander Abdelbaki aced2c754e feat(ansipa): add daemon enable/disable policy via host-group regex
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>
2026-05-20 15:25:15 +02:00
Amir Alexander Abdelbaki c56c86d57b fix(freeipa): harden container SMB setup and fetch-alerts script
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>
2026-05-20 13:13:53 +02:00
Amir Alexander Abdelbaki 11e66dbddd feat(freeipa): scan result reporting, alert notifications, and SMB share
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>
2026-05-20 12:32:21 +02:00
Amir Alexander Abdelbaki fb8ca498ef feat(freeipa): add AppArmor deny profiles to binary blocking policy
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>
2026-05-20 12:00:55 +02:00
Amir Alexander Abdelbaki 45fd7e5d36 feat(freeipa): add policy enforcement for binary blocking, backups, scans, and sudo
Introduces a FreeIPA host-group-driven policy system alongside a sudo
rules management playbook:

- ansipa-enforce-policies.sh: client-side enforcer (systemd timer, 30 min)
  - policy-block-binary-<name>: PATH-priority wrapper blocks the binary
  - policy-timeshift-backup: daily Timeshift snapshot cron (03:00)
  - policy-security-scan: daily ClamAV/rkhunter/chkrootkit cron (02:00)
  Policies are reversible — leaving a group removes enforcement on next run.

- deploy-ansipa-policies.yml: deploys enforcer + systemd service/timer to clients

- manage-sudo-rules.yml: creates FreeIPA sudo rules (allow_sudoers,
  allow_sudo_nopasswd) that SSSD clients already pick up via --sudo enrollment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 11:34:09 +02:00
Amir Alexander Abdelbaki f1ea6dcb54 ansible: add collect-luks-keys playbook for LUKS backup key archival
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>
2026-05-18 15:25:05 +02:00
Amir Alexander Abdelbaki fb9893504c setup: add FreeIPA Flatpak group installer (fp_install_* groups)
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>
2026-05-18 11:52:27 +02:00
Amir Alexander Abdelbaki c51af40fce setup: add freeipa-client module and FreeIPA group-based module automation
- 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>
2026-05-18 11:40:51 +02:00
Amir Alexander Abdelbaki f66775ce54 setup: add FreeIPA image builder and Keycloak integration
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>
2026-05-18 11:22:48 +02:00
Amir Alexander Abdelbaki 7279a781b0 setup: add FreeIPA server module and generic client script
freeipa-server.sh: interactive installer that collects domain, realm,
IP, admin/DM passwords, DNS, KRA, NTP, and AWX/Ansible settings;
runs conflict pre-flight (checks for existing named/dirsrv/krb5kdc,
ports 389/636/88, and /etc/ipa/default.conf); configures firewalld/ufw;
runs ipa-server-install; and outputs a ready-to-distribute client
package to ~/freeipa-output/ containing:
  - freeipa-enroll.sh (server defaults baked in)
  - freeipa-client.sh (server defaults baked in)
  - freeipa-client-answerfile.json (pre-filled, password intentionally blank)
  - auto-enroll-ansible.sh (AWX defaults embedded, still overridable)
  - README.txt

freeipa-client.sh: thin wrapper around freeipa-enroll.sh with three modes:
  --answerfile FILE   read JSON with jq, build args, exec freeipa-enroll.sh
  --interactive       prompt for every field, then exec freeipa-enroll.sh
  [flags]             passthrough directly to freeipa-enroll.sh

freeipa-client-answerfile.json: template with current server defaults
(freeipa.abdelbaki.eu); freeipa-server.sh sed-replaces these when
generating customized copies.

Supported server OS: RHEL/Rocky/AlmaLinux/Fedora (primary), Arch (warned).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 11:12:31 +02:00
Amir Alexander Abdelbaki 3890d360d6 Update setup/modules/FreeipaAnsible/copilot-explains.txt 2026-04-27 17:00:37 +02:00
Amir Alexander Abdelbaki 16732cc17c Add setup/modules/FreeipaAnsible/copilot-explains.txt 2026-04-27 16:59:36 +02:00
Amir Alexander Abdelbaki 6dbeca2bde Add setup/modules/FreeipaAnsible/ansible/ansipa-install-packages.sh 2026-04-27 16:44:59 +02:00
Amir Alexander Abdelbaki 9daf10888c Add setup/modules/FreeipaAnsible/ansible/deploy-ansipa-install.yml 2026-04-27 16:44:36 +02:00
Amir Alexander Abdelbaki 244d7385eb Add setup/modules/FreeipaAnsible/ansible/ansipa-install.timer 2026-04-27 16:44:18 +02:00
Amir Alexander Abdelbaki a2f3a03547 Add setup/modules/FreeipaAnsible/ansible/ansipa-install.service 2026-04-27 16:44:01 +02:00
Amir Alexander Abdelbaki 64af45d300 Add setup/modules/FreeipaAnsible/ansible/deploy-baseuser-sync.yml 2026-04-27 16:39:34 +02:00
Amir Alexander Abdelbaki 7d96df03ac Add setup/modules/FreeipaAnsible/ansible/baseuser-sync.path 2026-04-27 16:39:11 +02:00
Amir Alexander Abdelbaki f34cbd83b9 Add setup/modules/FreeipaAnsible/ansible/baseuser-sync.service 2026-04-27 16:38:37 +02:00
Amir Alexander Abdelbaki d927eb8904 Update setup/modules/FreeipaAnsible/auto-enroll-ansible.sh 2026-04-27 16:38:02 +02:00
Amir Alexander Abdelbaki 6d1d2c6083 Update setup/modules/FreeipaAnsible/ansible/auto-add-baseuser.sh 2026-04-27 16:37:39 +02:00
Amir Alexander Abdelbaki dea19b2998 Update setup/modules/FreeipaAnsible/freeipa-enroll.sh 2026-04-27 16:37:09 +02:00