Commit Graph

7 Commits (268feaa1f2f5b5f4e2789e97bd0ffbcd9ba9b0b7)

Author SHA1 Message Date
Amir Alexander Abdelbaki 268feaa1f2 feat(ansipa): add Nextcloud, and make FreeIPA the sole identity source
Adds a built-in Nextcloud to the ansipa stack and wires every service's
logins back to FreeIPA, either through Keycloak or directly.

Nextcloud:
  - nextcloud + redis services, sharing the existing postgres server via a
    second database. The initdb hook only fires on an empty data directory,
    so run.sh also creates the role/DB idempotently for stacks that predate
    Nextcloud.
  - served at the /nextcloud subpath of the gateway (OVERWRITEWEBROOT), with
    the /.well-known/* DAV redirects at the server root that sync clients and
    Nextcloud's own setup checks expect.

Identity:
  - keycloak-configure.sh now provisions confidential OIDC clients per relying
    party (nextcloud, proxmox-ve, proxmox-bs, opnsense) plus a realm-wide
    "groups" claim, writing secrets to .oidc-secrets (0600, gitignored).
  - nextcloud-configure.sh binds the IPA LDAP backend as the account source
    and user_oidc for login. The internal username is pinned to the IPA uid
    and unique-uid is disabled, otherwise every SSO login creates a second,
    empty account beside the LDAP one.
  - checkmk-ldap-configure.sh binds CheckMK straight to FreeIPA. CheckMK
    cannot go through Keycloak: SAML is commercial-edition only and no
    edition supports OIDC. The LDAP REST endpoints landed in 2.4 (werk
    #16527) but the stack pins 2.3, so it writes user_connections.mk —
    generated via Python repr() so quotes in a password cannot produce a
    SyntaxError that takes the GUI down, backed up and rolled back if the
    site fails to load it. Directory type is 389directoryserver, which is
    what gives FreeIPA the correct uid/member attribute defaults.

Monitoring:
  - OPNsense local check (gateways, pf state table vs its hard limit,
    firmware, CARP) plus an installer. POSIX sh, since OPNsense is FreeBSD
    with no bash or GNU grep. Transport is agent-over-SSH rather than inetd
    on 6556, because OPNsense regenerates inetd.conf/services/hosts.allow
    from config.xml and silently reverts those edits on reboot.

Fixes found along the way:
  - keycloak-configure.sh used KC_URL without the /auth relative path and
    probed /health/ready, which lives on management port 9000 and is not
    under that path — so it 404'd and timed out on this deployment.
  - .env.example had an unquoted `cn=Directory Manager`, which the shell
    scripts sourcing it parsed as a command named `Manager`.
  - summary heredocs printed raw escape codes; colours now use ANSI-C quoting.

docs/md/ansipa-sso.md covers the identity chain, the per-service support
matrix, Proxmox VE/PBS OpenID realm setup, and why packet captures should
not be tunnelled through the CheckMK API.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BWLuyyrydC1u2FjM7naPcz
2026-08-10 14:51:27 +02:00
Amir Alexander Abdelbaki e5d58622fe Revert "feat(freeipa-ansible): gateway-wide Basic Auth in front of the ansipa portal"
This reverts commit 4730f89e82.
2026-07-22 16:24:31 +02:00
Amir Alexander Abdelbaki 4730f89e82 feat(freeipa-ansible): gateway-wide Basic Auth in front of the ansipa portal
Adds a deliberate extra sign-in layer covering the entire nginx gateway
(portal page + every proxied web UI) via a shared htpasswd credential
generated on the FreeIPA container and published to nginx through a new
portal-auth volume. FreeIPA, CheckMK and Keycloak still each enforce their
own login behind it — two prompts beats one attacker who only had to beat
one lock. Healthcheck updated to treat 401 as healthy accordingly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 16:08:10 +02:00
Amir Alexander Abdelbaki 4200557757 feat(freeipa-ansible): git-based signed policy distribution for ANSIPA
Replaces the unsigned SMB-policystore auto-sync (enforcer blindly sourced
whatever .sh files sat on the share, no integrity check) with a dedicated
git-over-SSH server plus GPG commit signing: nodes pull on a timer, verify
every commit's signature and fast-forward history before an unprivileged
puller account hands off to root via one exact scoped sudo command, and a
fetch failure is a safe no-op that keeps the last-known-good policy running.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-22 15:56:19 +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 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 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