diff --git a/docs/AUDIT_2026-05-25_TODO.md b/docs/AUDIT_2026-05-25_TODO.md index 932cb02..fe655c2 100644 --- a/docs/AUDIT_2026-05-25_TODO.md +++ b/docs/AUDIT_2026-05-25_TODO.md @@ -107,6 +107,7 @@ In diesem Audit-Zyklus werden diese Punkte **nicht** umgesetzt. Sie sind dokumen | erledigt 2026-05-29 | Monitoring-Stack Digest-Pinning (F-07) | 9 Container in `monitoring/docker-compose.yml` per Tag@sha256 gepinnt: prometheus, alertmanager, alertmanager-ntfy-bridge (python:3.13-alpine), blackbox-exporter, loki, promtail, grafana, node-exporter, cadvisor. Digests aus dem aktuell laufenden Container ausgelesen, damit der Pin den Live-Stand reflektiert. influxdb3-core war bereits gepinnt. | | erledigt 2026-05-29 | Komodo-Bootstrap-Trockenlauf-Skript (F-09 Rest) | `ops/restore-tests/komodo-bootstrap-{compose.test.yml,test.sh,plan.md,runbook.md}` analog zum Immich-Restore-Test angelegt. Test-Compose nutzt dieselben Image-Digests wie Produktion, isoliert unter Project `restoretest-komodo`, Test-Periphery ohne docker.sock-Mount, Test-Port nur `127.0.0.1:19120`. Wegwerf-Secrets im Compose. Erster Lauf manuell durch Operator. | | erledigt 2026-05-29 | Renovate-Bot gegen Gitea (F-12) | Live: Service-Account `renovate` (uid 2, kein Admin) angelegt, Collaborator Write auf `Micha/homelab-infra`, PAT in `/mnt/user/appdata/secrets/renovate_token.txt` (chmod 600). Cron `renovate-six-hourly` (`20 */6 * * *`) live in `/etc/cron.d/root`. Erstlauf 2026-05-29 erfolgreich: 5 PRs (mongo digest+minor, postgres digest+minor, minor-and-patch-updates gruppiert), 1 Dependency-Dashboard-Issue, 8 Branches. Komodo-Major durch packageRule deaktiviert wie erwartet. Architektur-Detail: Repo-Config in `renovate.json`, Bot-Config in `ops/renovate/bot-config.js` (Renovate liest die im Repo nur als Repo-Config, Bot-Settings dort triggern "forbidden/disabled"). | +| erledigt 2026-05-30 | Authelia Repo<->Host Drift-Check (F-10) | `services/authelia-diff.sh` vergleicht die `access_control:`-Sektion zwischen Repo-Baseline und Host-Datei (Default; per env `AUTHELIA_DIFF_SECTIONS` erweiterbar). OIDC-Clients/Identity-Provider und Secret-Werte bleiben bewusst aussen vor. Exit-Codes: 0 = ok, 1 = Drift, 2 = Datei fehlt, 3 = Sektion fehlt, 4 = Werkzeug fehlt. Posture-Check ruft das Skript als Check `authelia_config_drift` auf (`SKIP_AUTHELIA_DRIFT=1` skippt, `AUTHELIA_DIFF_SCRIPT` ueberschreibt den Pfad); Drift wird als Warning gemeldet, nicht Critical. Smoke-Test lokal: identische Files -> rc=0, ACL-Drift im Domain-Eintrag -> rc=1 mit unified diff. WORKFLOW.md hat jetzt eine eigene Pflicht-Sektion "Ausnahme: Authelia configuration.yml" analog zur Traefik-Dynamic-Sektion. Pflicht-Setup auf dem Host: Repo-Spiegel unter `/mnt/user/services/homelab-infra/`. | ## Sprint 7 - Off-site und 3-2-1 (offen) diff --git a/docs/MIGRATION_LOG.md b/docs/MIGRATION_LOG.md index 4e2a3cf..a33fd5f 100644 --- a/docs/MIGRATION_LOG.md +++ b/docs/MIGRATION_LOG.md @@ -17,6 +17,20 @@ Dieses Dokument ist nur noch ein historischer Verlauf. Der aktuelle operative Ab ## Historische Meilensteine +### 2026-05-30 - F-10: Authelia Repo<->Host Drift-Check + +Der dokumentierte "by-design"-Drift zwischen `security/authelia/configuration.yml` (Repo-Baseline) und `/mnt/user/appdata/authelia/config/configuration.yml` (Host) wird jetzt automatisch ueberwacht. Vorher: Manueller Merge auf den Host war Pflicht, aber keine Pruefung. Eine vergessene ACL-Synchronisation waere erst bei einem Login-Fehler aufgefallen. + +- Neues Skript `services/authelia-diff.sh`: extrahiert die `access_control:`-Sektion aus beiden YAMLs per awk-Block-Extractor (Top-Level-Key bis zum naechsten Top-Level-Key), normalisiert Kommentar- und Leerzeilen, vergleicht via `diff -u`. Default-Sektion ist `access_control`, weil das laut F-10 der primaere Drift-Vektor ist; per env `AUTHELIA_DIFF_SECTIONS` koennen weitere Top-Level-Sektionen (`session`, `regulation`, `totp`, ...) ergaenzt werden. OIDC-Clients, Identity-Provider und Secret-Werte bleiben bewusst aussen vor. +- Exit-Code-Schema: 0 = ok, 1 = Drift (Diff auf stdout), 2 = Datei fehlt, 3 = Sektion fehlt, 4 = Werkzeug fehlt. Macht das Skript auch standalone nutzbar (`ssh kallilab "bash /mnt/user/services/homelab-infra/services/authelia-diff.sh"`). +- `services/posture-check/posture-check.sh` ruft das Skript am Ende des Checks-Blocks auf (`check_authelia_config_drift`). Drift wird als **Warning** gemeldet, nicht Critical, weil die produktive Authelia trotz Drift weiter laeuft und die ACL fuer schon angemeldete Sessions weiter wirkt. Skip-Mechanismus: `SKIP_AUTHELIA_DRIFT=1`. Pfad-Override: `AUTHELIA_DIFF_SCRIPT`. +- Pflicht-Setup auf dem Host: Repo-Spiegel unter `/mnt/user/services/homelab-infra/` als read-only-Clone von Gitea `Micha/homelab-infra` mit regelmaessigem `git pull --ff-only`. Default-Pfade des Skripts setzen das voraus. Ohne Repo-Spiegel meldet der Check Warning, weil die Baseline-Datei fehlt - keine stille Inaktivierung. +- Lokaler Smoke-Test 2026-05-30 erfolgreich: identische Files -> rc=0; ACL-Drift im Domain-Eintrag `scrutiny.kaleschke.info -> scrutiny-renamed.kaleschke.info` -> rc=1 mit unified diff, ACL-Block korrekt extrahiert, Kommentar- und Leerzeilen rausgefiltert. False-Positive auf `session.default_redirection_url`-Aenderung korrekt vermieden (gehoert nicht zu `access_control`). +- `docs/WORKFLOW.md` hat jetzt eine eigene Sektion "Ausnahme: Authelia configuration.yml" analog zur Traefik-Dynamic-Sektion. Pflicht-Workflow: 1. Repo-Aenderung + Commit + Push, 2. manueller Merge in die Host-Datei mit Erhalt der OIDC-Sektionen, 3. `docker restart authelia` + Login-Smoke-Test, 4. `services/authelia-diff.sh` muss `exit 0` liefern. +- `docs/REPO_MAP.md` und `docs/SERVICE_CATALOG.md` zeigen das Skript und den neuen Posture-Check-Eintrag. + +Operator-Folgeschritt (klein, nicht heute): Repo-Spiegel `/mnt/user/services/homelab-infra/` auf dem Host einrichten und in den vorhandenen `gitea-bundle-mirror-6h`-Plan oder einen eigenen 6h-Cron einbinden, damit das Skript einen aktuellen Vergleichsstand findet. + ### 2026-05-29 - Stack-Hygiene Sprint: Healthchecks, Monitoring-Digests, Komodo-Bootstrap-Skript, Renovate-Vorbereitung Vier Audit-Punkte am Stueck abgearbeitet. Pro Block: Live-Verifikation am Host, Doku im Repo. diff --git a/docs/REPO_MAP.md b/docs/REPO_MAP.md index c3b0ce8..14d64b0 100644 --- a/docs/REPO_MAP.md +++ b/docs/REPO_MAP.md @@ -66,9 +66,10 @@ Secret-Werte werden hier nicht dokumentiert. Aufgefuehrt werden nur Variablennam | `monitoring/grafana/provisioning/*` | Grafana Datasource-/Dashboard-Provisioning fuer Prometheus und Loki | | `ops/glance/config/glance.yml` | Glance Dashboard-Konfiguration fuer Homelab-Monitore, Internet-/DNS-/VPN-Widgets, Community-Widgets, Docker-Containergruppen, Zeitfortschritt, Host-Snapshot, Bookmarks und zweite Infrastruktur-Seite | | `ops/borg-ui/scripts/pre-backup-dumps.sh` | Host-seitiges Dump-Skript fuer PostgreSQL, SQLite-Container-Dumps und Komodo Mongo | -| `services/posture-check/posture-check.sh` | Host-seitiger Posture-Check fuer Filesystem, Mover-Drift, NVMe-SMART, Fuellstand und ntfy-Alarmierung | +| `services/posture-check/posture-check.sh` | Host-seitiger Posture-Check fuer Filesystem, Mover-Drift, NVMe-SMART, Fuellstand, Authelia-Repo<->Host-Drift und ntfy-Alarmierung | | `services/posture-check/docker-critical-events.sh` | Host-seitiger Docker-Event-Watcher fuer kritische ntfy-Alarme | | `services/posture-check/posture_check.sh` | Kompatibilitaets-Wrapper fuer die historische Schreibweise aus `STORAGE_LAYOUT.draft.md` | +| `services/authelia-diff.sh` | Vergleicht `access_control:`-Sektion zwischen Repo-Baseline und Host-Datei; wird vom Posture-Check als Check `authelia_config_drift` aufgerufen | | `ops/hermes-agent/config/hermes/config.yaml` | Hermes Agent Konfiguration mit Env-Platzhaltern | | `ops/hermes-agent/hermes.env.example` | Beispiel fuer Hermes `.env`; echte Datei liegt auf Host-Appdata | | `ops/hermes-agent/stack.env.example` | Beispiel fuer Hermes Stack-ENV; echte `stack.env` bleibt host-/komodoseitig und ist per `.gitignore` ausgeschlossen | diff --git a/docs/SERVICE_CATALOG.md b/docs/SERVICE_CATALOG.md index 47b24bc..a42322b 100644 --- a/docs/SERVICE_CATALOG.md +++ b/docs/SERVICE_CATALOG.md @@ -82,7 +82,7 @@ Secret-Werte sind nicht enthalten. Es werden nur Secret-Namen, Env-Key-Namen und | Service | Zweck | Autoritativer Pfad | URL / Zugang | Abhaengigkeiten | Datenpfade | Backup / Restore | Traefik | Besonderheiten / TODOs | |---|---|---|---|---|---|---|---|---| -| `posture-check` | Host-Posture-Audit fuer Filesystem, Mover-Drift, NVMe-SMART und Fuellstand | `services/posture-check/posture-check.sh` | Unraid User-Script / Cron / Borg Pre-Hook | `findmnt`, `df`, `nvme`, optional `curl` fuer ntfy | `/mnt/user/services/posture-check/last.json` | Repo-Skript + letzter JSON-Status | nein | Muss auf dem Unraid-Host bei Boot, stuendlich und vor Borg laufen; Disk1-NTFS ist nach Disk1 Phase 2 nicht mehr erlaubt (`ALLOW_DISK1_NTFS=0` Standard); Warning/Critical alarmieren via ntfy nur bei neuer Ursache oder nach `ALERT_REPEAT_SECONDS` | +| `posture-check` | Host-Posture-Audit fuer Filesystem, Mover-Drift, NVMe-SMART, Fuellstand und Authelia-Repo<->Host-Drift | `services/posture-check/posture-check.sh` | Unraid User-Script / Cron / Borg Pre-Hook | `findmnt`, `df`, `nvme`, optional `curl` fuer ntfy; ruft `services/authelia-diff.sh` fuer `authelia_config_drift` auf | `/mnt/user/services/posture-check/last.json` | Repo-Skript + letzter JSON-Status | nein | Muss auf dem Unraid-Host bei Boot, stuendlich und vor Borg laufen; Disk1-NTFS ist nach Disk1 Phase 2 nicht mehr erlaubt (`ALLOW_DISK1_NTFS=0` Standard); Warning/Critical alarmieren via ntfy nur bei neuer Ursache oder nach `ALERT_REPEAT_SECONDS`. Authelia-Drift-Check braucht einen Repo-Spiegel unter `/mnt/user/services/homelab-infra/` (siehe `docs/WORKFLOW.md` Sektion "Ausnahme: Authelia configuration.yml") | | `docker-critical-events` | Live-Alarmierung fuer Docker `die`/`oom`/`kill` Events | `services/posture-check/docker-critical-events.sh` | Unraid User-Script / Hintergrundprozess | Docker CLI, ntfy | `/mnt/user/services/posture-check/docker-critical-events-last.log` | Repo-Skript + letzter Event-Log | nein | Optional als Unraid User-Script `at array start` starten; sendet nach `homelab-alerts` | ## Backup- und Restore-Hinweise diff --git a/docs/WORKFLOW.md b/docs/WORKFLOW.md index 8e4ce35..aa3b806 100644 --- a/docs/WORKFLOW.md +++ b/docs/WORKFLOW.md @@ -269,6 +269,42 @@ Diese Ausnahme bleibt bewusst bestehen. Der File-Provider wird weiterhin nur fue --- +## Ausnahme: Authelia configuration.yml + +> **Diese Datei wird von Komodo nicht automatisch deployed.** + +`security/authelia/configuration.yml` ist die Repo-Baseline fuer nicht geheime Einstellungen (Access-Control, Session, Storage-Struktur, Notifier, TOTP). Die produktive Host-Datei darf zusaetzlich OIDC-Clients und hostseitige Identity-Provider-Konfiguration enthalten. Secret-Werte und die User-Datenbank bleiben grundsaetzlich ausserhalb von Git. + +| Git-Pfad | Host-Pfad (NAS) | +|---|---| +| `security/authelia/configuration.yml` | `/mnt/user/appdata/authelia/config/configuration.yml` | + +### Pflicht-Workflow bei Aenderungen an `configuration.yml` + +1. Datei im Git-Repo (`security/authelia/`) aendern. +2. Commit + Push. +3. Aenderung manuell in die Host-Datei mergen, OIDC-/Identity-Provider-Sektionen erhalten. +4. `docker restart authelia` und Login-Smoke-Test auf einer ACL-betroffenen Domain. +5. `services/authelia-diff.sh` (Default-Aufruf) muss `exit 0` liefern. + +### Automatische Drift-Erkennung + +`services/authelia-diff.sh` vergleicht die `access_control:`-Sektion zwischen Repo-Baseline und Host-Datei. Der Posture-Check (`services/posture-check/posture-check.sh`) ruft das Skript als Check `authelia_config_drift` auf und meldet Drift als Warning via ntfy. + +Konfigurierbare Variablen (Defaults sind das produktive Zielbild): + +- `AUTHELIA_REPO_BASELINE` — Pfad zur Repo-Datei auf dem Host, Default `/mnt/user/services/homelab-infra/security/authelia/configuration.yml` +- `AUTHELIA_HOST_CONFIG` — Pfad zur produktiven Host-Datei, Default `/mnt/user/appdata/authelia/config/configuration.yml` +- `AUTHELIA_DIFF_SECTIONS` — Komma-Liste der zu vergleichenden Top-Level-Sektionen, Default `access_control` +- `AUTHELIA_DIFF_SCRIPT` — Pfad zum Diff-Skript fuer den Posture-Check, Default `/mnt/user/services/homelab-infra/services/authelia-diff.sh` +- `SKIP_AUTHELIA_DRIFT=1` — Check im Posture-Check ueberspringen + +Pflicht-Setup auf dem Host: Repo-Spiegel unter `/mnt/user/services/homelab-infra/` (Read-only-Clone von Gitea `Micha/homelab-infra`, regelmaessig `git pull --ff-only`). Ohne Repo-Spiegel meldet der Check Warning, weil die Baseline-Datei fehlt — Critical wird der Check bewusst nicht. + +> **Merksatz:** Push allein reicht hier nicht. Ohne den manuellen Merge ins Host-Configfile wirkt die Aenderung nicht, und der Drift-Check wuerde Warning melden. + +--- + ## Secrets-Regeln - Secrets liegen niemals im Repository diff --git a/services/authelia-diff.sh b/services/authelia-diff.sh new file mode 100644 index 0000000..8f71b2c --- /dev/null +++ b/services/authelia-diff.sh @@ -0,0 +1,121 @@ +#!/usr/bin/env bash +# Vergleicht die Repo-Baseline der Authelia-Konfiguration gegen die produktive +# Host-Datei. Bewusst nur fuer Sektionen, die laut Repo-Konvention auf Host +# und Repo identisch sein muessen (Default: access_control). OIDC-Clients, +# identity_providers und Secret-Werte bleiben hostseitig und werden nicht +# verglichen. +# +# Aufruf-Defaults siehe Variablen unten. Aufruf typischerweise: +# bash services/authelia-diff.sh +# +# Exit-Codes: +# 0 alle verglichenen Sektionen identisch +# 1 Drift festgestellt (Diff wird auf stdout ausgegeben) +# 2 Repo-Baseline oder Host-Datei fehlt +# 3 Sektion in mindestens einer Datei nicht gefunden +# 4 internes Werkzeug fehlt (awk/diff) + +set -uo pipefail + +AUTHELIA_REPO_BASELINE="${AUTHELIA_REPO_BASELINE:-/mnt/user/services/homelab-infra/security/authelia/configuration.yml}" +AUTHELIA_HOST_CONFIG="${AUTHELIA_HOST_CONFIG:-/mnt/user/appdata/authelia/config/configuration.yml}" +AUTHELIA_DIFF_SECTIONS="${AUTHELIA_DIFF_SECTIONS:-access_control}" + +for cmd in awk diff; do + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "authelia-diff: missing required command '$cmd'" >&2 + exit 4 + fi +done + +if [ ! -f "$AUTHELIA_REPO_BASELINE" ]; then + echo "authelia-diff: repo baseline not found: $AUTHELIA_REPO_BASELINE" >&2 + exit 2 +fi +if [ ! -f "$AUTHELIA_HOST_CONFIG" ]; then + echo "authelia-diff: host config not found: $AUTHELIA_HOST_CONFIG" >&2 + exit 2 +fi + +# Extrahiert einen Top-Level-Block aus einer YAML-Datei. +# Block-Anfang: Zeile, die mit "
:" beginnt (kein Whitespace davor). +# Block-Ende: naechste Top-Level-Key-Zeile (`^[A-Za-z_][A-Za-z0-9_]*:`). +# Eingaberauschen wird entfernt: reine Kommentarzeilen, trailing whitespace, +# Leerzeilen. +extract_section() { + local file="$1" + local section="$2" + awk -v section="$section" ' + BEGIN { inside = 0; found = 0 } + { + line = $0 + sub(/[[:space:]]+$/, "", line) + } + # Top-Level-Key entdeckt + /^[A-Za-z_][A-Za-z0-9_]*:/ { + key = line + sub(/:.*$/, "", key) + if (key == section) { + inside = 1 + found = 1 + print line + next + } else if (inside == 1) { + inside = 0 + } + } + inside == 1 { + # Kommentar- und Leerzeilen ignorieren + if (line ~ /^[[:space:]]*#/) next + if (line ~ /^[[:space:]]*$/) next + print line + } + END { + if (!found) exit 10 + } + ' "$file" +} + +tmpdir="$(mktemp -d -t authelia-diff.XXXXXX)" +trap 'rm -rf "$tmpdir"' EXIT + +overall_status=0 +diff_output="" +missing_sections="" + +IFS=',' read -r -a sections <<< "$AUTHELIA_DIFF_SECTIONS" +for section in "${sections[@]}"; do + section="${section// /}" + [ -z "$section" ] && continue + + repo_file="$tmpdir/repo.$section" + host_file="$tmpdir/host.$section" + + if ! extract_section "$AUTHELIA_REPO_BASELINE" "$section" > "$repo_file" 2>/dev/null; then + missing_sections="${missing_sections}${missing_sections:+, }$section (repo)" + continue + fi + if ! extract_section "$AUTHELIA_HOST_CONFIG" "$section" > "$host_file" 2>/dev/null; then + missing_sections="${missing_sections}${missing_sections:+, }$section (host)" + continue + fi + + if ! diff_chunk="$(diff -u \ + --label "repo:$section" "$repo_file" \ + --label "host:$section" "$host_file")"; then + overall_status=1 + diff_output="${diff_output}${diff_chunk}"$'\n' + fi +done + +if [ -n "$missing_sections" ] && [ "$overall_status" -eq 0 ]; then + echo "authelia-diff: sections missing: $missing_sections" >&2 + exit 3 +fi + +if [ "$overall_status" -ne 0 ]; then + printf '%s' "$diff_output" + exit 1 +fi + +exit 0 diff --git a/services/posture-check/posture-check.sh b/services/posture-check/posture-check.sh index 43bbf2e..5a43d95 100755 --- a/services/posture-check/posture-check.sh +++ b/services/posture-check/posture-check.sh @@ -10,6 +10,8 @@ TMP_DIR="${TMP_DIR:-/tmp/kallilab-posture-check}" ALLOW_DISK1_NTFS="${ALLOW_DISK1_NTFS:-0}" ALERT_STATE_PATH="${ALERT_STATE_PATH:-/mnt/user/services/posture-check/last-alert.state}" ALERT_REPEAT_SECONDS="${ALERT_REPEAT_SECONDS:-86400}" +SKIP_AUTHELIA_DRIFT="${SKIP_AUTHELIA_DRIFT:-0}" +AUTHELIA_DIFF_SCRIPT="${AUTHELIA_DIFF_SCRIPT:-/mnt/user/services/homelab-infra/services/authelia-diff.sh}" mkdir -p "$TMP_DIR" RESULTS_FILE="$TMP_DIR/results.$$" @@ -219,6 +221,41 @@ check_nvme_smart() { fi } +check_authelia_config_drift() { + if [ "$SKIP_AUTHELIA_DRIFT" = "1" ]; then + add_result "ok" "authelia_config_drift" "Authelia drift check skipped via SKIP_AUTHELIA_DRIFT=1" + return + fi + + if [ ! -x "$AUTHELIA_DIFF_SCRIPT" ] && [ ! -f "$AUTHELIA_DIFF_SCRIPT" ]; then + add_result "warning" "authelia_config_drift" "Authelia diff script missing: $AUTHELIA_DIFF_SCRIPT" + return + fi + + local output + local rc + output="$(bash "$AUTHELIA_DIFF_SCRIPT" 2>&1)" + rc=$? + + case "$rc" in + 0) + add_result "ok" "authelia_config_drift" "Authelia repo baseline matches host config (access_control)" + ;; + 1) + add_result "warning" "authelia_config_drift" "Authelia repo<->host drift in access_control; run authelia-diff.sh for details" + ;; + 2) + add_result "warning" "authelia_config_drift" "Authelia diff aborted: $output" + ;; + 3) + add_result "warning" "authelia_config_drift" "Authelia diff: section missing in repo or host: $output" + ;; + *) + add_result "warning" "authelia_config_drift" "Authelia diff returned unexpected rc=$rc: $output" + ;; + esac +} + send_ntfy() { local severity="$1" local topic="$2" @@ -388,6 +425,7 @@ main() { done check_nvme_smart + check_authelia_config_drift write_json }