diff --git a/ops/restore-tests/authelia-compose.test.yml b/ops/restore-tests/authelia-compose.test.yml index 094fd7a..848e75d 100644 --- a/ops/restore-tests/authelia-compose.test.yml +++ b/ops/restore-tests/authelia-compose.test.yml @@ -39,11 +39,11 @@ services: AUTHELIA_SESSION_SECRET: restoretest-authelia-session-secret-placeholder-32 AUTHELIA_STORAGE_ENCRYPTION_KEY: restoretest-authelia-storage-enc-key-placeholder-32 AUTHELIA_STORAGE_POSTGRES_PASSWORD: restoretest-authelia-db - # Die command:-Config laedt die vom Skript erzeugte Restore-Test-Config. - # Sie ersetzt produktive storage/notifier/session-Bloecke durch isolierte - # Test-Backends, damit kein produktives Postgres erreicht und kein echter - # SMTP-Versand ausgeloest wird. - AUTHELIA__SERVER__ADDRESS: tcp://0.0.0.0:9091 + # server.address wird in der vom Skript erzeugten configuration.yml + # gesetzt (tcp://0.0.0.0:9091). Eine zusaetzliche ENV waere + # redundant - und in Authelia 4.39 nicht als Doppel-Underscore + # akzeptiert (war Ursache des "configuration environment variable + # not expected"-Warnings im Lauf 2026-06-03). volumes: - /mnt/user/backups/restore-lab/authelia/test-config:/config ports: diff --git a/ops/restore-tests/authelia-plan.md b/ops/restore-tests/authelia-plan.md index 8dfec0c..1879ca6 100644 --- a/ops/restore-tests/authelia-plan.md +++ b/ops/restore-tests/authelia-plan.md @@ -10,13 +10,14 @@ Bewusst **nicht** Teil dieses Tests: - SMTP-Realanruf an GMX. Die minimale Test-Konfiguration setzt nur den Filesystem-Notifier. - Forward-Auth gegen Traefik. Test laeuft nur auf `127.0.0.1:19091`, keine Traefik-Route. - WebAuthn-/Duo-/OIDC-Identity-Provider-Endpunkte. Smoke prueft `/api/health`. +- **pg_restore des produktiven `postgresql17-authelia.dump`**. Authelia verschluesselt Storage-Werte mit `AUTHELIA_STORAGE_ENCRYPTION_KEY`. Ein Restore mit produktiven Daten in eine Test-Instanz mit Wegwerf-Key schlaegt im Startup-Check **by design** fehl ("the configured encryption key does not appear to be valid for this database"). Frische des produktiven Dumps wird ueber `check-restore-freshness.sh` ueberwacht; Daten-Decrypt-Drill ist eine separate DR-Aufgabe und braucht eine eigene Sicherheits-Choreographie mit kontrollierter Schluessel-Verwendung. Beobachtet im Erstlauf 2026-06-03 (Commit-Reihe `cacf77b..8d71dfb`); seit dem 2026-06-03-Folgecommit ist der Dump-Restore explizit aus dem Smoke entfernt. ## Quelle - Backup-Quelle: produktives Borg-Archiv (`hetzner_borg_appdata_critical`) - fachlich relevante Pfade im Archiv: - `local/appdata/authelia/config` (verpflichtend) - - `local/borg-dumps/latest/postgresql17-authelia.dump` (optional, wenn vorhanden) + - `local/borg-dumps/latest/postgresql17-authelia.dump` (existiert ggf. im Archiv; wird vom Smoke bewusst NICHT eingespielt, siehe oben) - produktive Secrets unter `/mnt/user/appdata/secrets/authelia_*.txt` werden **nicht** gemountet ## Test-Ziel @@ -48,14 +49,12 @@ Bewusst **nicht** Teil dieses Tests: 1. Restore-Lab-Pfade leer anlegen 2. `local/appdata/authelia/config` aus dem aktuellsten Borg-Archiv extrahieren -3. optional `local/borg-dumps/latest/postgresql17-authelia.dump` extrahieren; wenn nicht im Archiv vorhanden, weiter ohne DB-Restore -4. minimale `test-config/configuration.yml` erzeugen; restaurierte Begleitdateien wie `users_database.yml` bleiben im Runtime-Mount, produktive externe Abhaengigkeiten werden nicht uebernommen -5. Test-Postgres mit `ops/restore-tests/authelia-compose.test.yml` hochfahren -6. optional Dump per `pg_restore -Fc --clean --if-exists --no-owner --no-privileges` einspielen (mit transientem Retry wie im Immich-/Paperless-Test) -7. `authelia config validate` gegen `test-config/configuration.yml` laufen lassen -8. `restoretest-authelia` starten und HTTP-Health `http://127.0.0.1:19091/api/health` pollen -9. Report unter `/mnt/user/backups/restore-reports/authelia-YYYY-MM-DD.md` schreiben -10. Testcontainer stoppen und Restore-Lab bereinigen (`--keep-data` ueberschreibt) +3. minimale `test-config/configuration.yml` erzeugen; restaurierte Begleitdateien wie `users_database.yml` bleiben im Runtime-Mount, produktive externe Abhaengigkeiten werden nicht uebernommen; `notifier` auf Filesystem, `ntp.disable_startup_check: true`, `storage` auf Test-Postgres +4. Test-Postgres mit `ops/restore-tests/authelia-compose.test.yml` **frisch** hochfahren (keine Daten aus Dump - siehe Encryption-Key-Begruendung oben) +5. `authelia config validate` gegen `test-config/configuration.yml` laufen lassen +6. `restoretest-authelia` starten und HTTP-Health `http://127.0.0.1:19091/api/health` pollen +7. Report unter `/mnt/user/backups/restore-reports/authelia-YYYY-MM-DD.md` schreiben +8. Testcontainer stoppen und Restore-Lab bereinigen (`--keep-data` ueberschreibt) ## Smoke-Test @@ -78,7 +77,8 @@ Optional spaeter: |---|---|---| | Testkonfig-Schema-Drift | Authelia erwartet nach Upgrade andere Keys in der Minimal-Konfig | bei `config validate`-Fehler Test-Block im Skript anpassen | | SMTP-Startup-Check blockiert Start | Wenn Authelia trotz `disable_startup_check` SMTP probiert | Container-Logs lesen, ggf. Notifier-Block weiter haerten | -| Postgres-Schema-Drift nach Major-Update | Authelia migriert Schema beim Start; Dump aus 17er-Cluster kann unter 18er andere Indexe brauchen | Smoke ist DB-Schema-tolerant; bei Validierung Logs auf `migration` pruefen | +| NTP-Lookup im Test-Netz | Container hat keinen DNS-Resolver fuer `time.cloudflare.com` | im Smoke per `ntp.disable_startup_check: true` deaktiviert | +| Storage-Encryption-Key vs. Dump | siehe "Bewusst nicht Teil dieses Tests" - der Smoke laeuft FRISCH ohne Dump | by design - Daten-Decrypt-Drill ist separate Aufgabe | | identity_validation Schema-Drift | Aelteres/neueres Authelia-Schema erwartet andere Keys | Validate-Config Output lesen, ggf. Test-Block anpassen | | users_database.yml mit produktiven Hashes | Daten werden ins Restore-Lab kopiert, aber niemals gemountet auf produktive Domain | OK; Testpfad ist isoliert, kein Browser-Zugang ueber LAN | diff --git a/ops/restore-tests/authelia-restore-test.sh b/ops/restore-tests/authelia-restore-test.sh index b2edb12..1bcfb69 100644 --- a/ops/restore-tests/authelia-restore-test.sh +++ b/ops/restore-tests/authelia-restore-test.sh @@ -4,15 +4,27 @@ set -euo pipefail # Authelia Restore Smoke Test # # Nicht-destruktiver Restore-Smoke-Test fuer Authelia. -# - extrahiert die Authelia-Config aus dem produktiven Borg-Archiv -# - erzeugt eine minimale Test-Konfiguration, die restaurierte Begleitdateien -# wie users_database.yml nutzt, aber produktive externe Abhaengigkeiten -# durch Test-Backends ersetzt -# - importiert optional den shared-Postgres-Dump fuer Authelia -# - validiert die Test-Konfiguration mit `authelia config validate` -# - startet einen isolierten Authelia-Container ohne Traefik -# - prueft den HTTP-Health-Endpunkt -# - bereinigt anschliessend +# +# Was dieser Smoke nachweist: +# - Authelia-Config kann aus dem produktiven Borg-Archiv extrahiert werden +# - die restaurierten Begleitdateien (users_database.yml etc.) sind lesbar +# - eine minimale Test-Konfiguration, die diese Begleitdateien nutzt und +# produktive externe Abhaengigkeiten (Postgres/SMTP) durch Wegwerf-Backends +# ersetzt, ist gegen den produktiven Authelia-Image-Pin valide +# (`authelia config validate`) +# - Authelia startet damit gegen ein frisches Test-Postgres und antwortet +# auf `/api/health` +# +# Was dieser Smoke bewusst NICHT nachweist: +# - Daten-Restore des produktiven authelia.dump. Authelia verschluesselt +# Storage-Werte mit AUTHELIA_STORAGE_ENCRYPTION_KEY; ein Restore mit +# produktiven Daten in eine Test-Instanz mit Wegwerf-Encryption-Key +# schlaegt im Startup-Check fehl ("the configured encryption key does +# not appear to be valid for this database"). Daten-Decrypt ist eine +# eigene DR-Aufgabe mit kontrollierter Schluessel-Verwendung, nicht +# Teil dieses Smokes. Frische des Dumps wird ueber +# check-restore-freshness.sh ueberwacht. +# - vollstaendiger Login-/2FA-/ForwardAuth-Flow. # # Produktive Authelia-Container, produktive Postgres-DB, produktive Secrets # und produktiver SMTP-Versand werden NICHT angefasst. @@ -46,16 +58,23 @@ RestoreRoot: $RESTORE_ROOT ReportRoot: $REPORT_ROOT Expected Borg source paths: - local/appdata/authelia/config -- local/borg-dumps/latest/postgresql17-authelia.dump (optional - wird uebersprungen wenn nicht vorhanden) Planned isolation: -- Test-Postgres: postgres:18.4 mit Wegwerf-Credentials +- Test-Postgres: postgres:18.4 mit Wegwerf-Credentials, FRISCH - Test-Authelia: authelia/authelia:4.39.20 (Image-Pin wie Produktion) - Wegwerf-Secrets ausschliesslich im Test-Compose - test-config/configuration.yml wird im Restore-Lab erzeugt: * storage -> Test-Postgres (kein produktives Postgres erreicht) * notifier -> Filesystem (KEIN SMTP-Versand) * session -> lokaler Smoke ohne produktive Session-Secrets + * ntp -> disable_startup_check (kein DNS im isolierten Test-Netz) - Test endpoint: 127.0.0.1:19091/api/health (no Traefik, no public domain) + +Bewusst NICHT Teil dieses Smokes: +- pg_restore von postgresql17-authelia.dump. Authelia verschluesselt + Storage-Werte mit AUTHELIA_STORAGE_ENCRYPTION_KEY; ein Restore in eine + Test-Instanz mit Wegwerf-Key ist by design nicht boot-faehig. + Dump-Frische wird via check-restore-freshness.sh ueberwacht. + Smoke-Test: - authelia config validate gegen test-config/configuration.yml - HTTP 200 von /api/health @@ -84,7 +103,7 @@ cleanup() { trap cleanup EXIT rm -rf "$EXTRACT_DIR" "$RESTORE_ROOT" -mkdir -p "$RESTORED_CONFIG_DIR" "$TEST_CONFIG_DIR" "$RESTORE_ROOT/postgres" "$RESTORE_ROOT/dumps/latest" +mkdir -p "$RESTORED_CONFIG_DIR" "$TEST_CONFIG_DIR" "$RESTORE_ROOT/postgres" archive="$(latest_archive_name)" repo="$(borg_repo_url)" @@ -102,17 +121,7 @@ if [ ! -d "$EXTRACT_DIR/local/appdata/authelia/config" ]; then fi cp -a "$EXTRACT_DIR/local/appdata/authelia/config/." "$RESTORED_CONFIG_DIR/" -# Stufe 2: optionalen Postgres-Dump extrahieren und ggf. einspielen -dump_available=0 -if borg_extract "/restore/authelia-extract" "local/borg-dumps/latest/postgresql17-authelia.dump" 2>/dev/null; then - if [ -f "$EXTRACT_DIR/local/borg-dumps/latest/postgresql17-authelia.dump" ]; then - mv "$EXTRACT_DIR/local/borg-dumps/latest/postgresql17-authelia.dump" \ - "$RESTORE_ROOT/dumps/latest/postgresql17-authelia.dump" - dump_available=1 - fi -fi - -# Stufe 3: Minimale Test-Konfiguration erzeugen. +# Stufe 2: Minimale Test-Konfiguration erzeugen. # Die restaurierte Originalkonfig bleibt als Diagnosematerial erhalten. Der # Smoke nutzt bewusst eine neu geschriebene Test-Config, damit keine produktiven # Blocks (SMTP, echtes Postgres, Session/JWT-Altkeys) hineinmergen koennen. @@ -180,6 +189,12 @@ notifier: filesystem: filename: /config/notifier/notifications.txt +ntp: + # Test-Netz hat keinen DNS-Resolver fuer time.cloudflare.com; ohne diesen + # Schalter loggt Authelia "Could not determine the clock offset" und der + # Startup-Check kann fehlschlagen. + disable_startup_check: true + session: cookies: - name: authelia_session_restoretest @@ -199,38 +214,21 @@ YAML mkdir -p "$TEST_CONFIG_DIR/notifier" chmod -R a+rwX "$TEST_CONFIG_DIR/notifier" -# Stufe 4: Test-Postgres hochfahren +# Stufe 3: Test-Postgres hochfahren (FRISCH, keine Daten aus Dump). +# Authelia legt sein Schema beim ersten Start selbst an und schreibt eine +# Encryption-Probe mit AUTHELIA_STORAGE_ENCRYPTION_KEY. Ein Restore des +# produktiven authelia.dump in diese Instanz wuerde die Encryption-Probe +# mit einem anderen Key vorbelegen und Authelia beim Startup-Check +# ablehnen lassen ("the configured encryption key does not appear to be +# valid for this database"). Genau aus diesem Grund laeuft der Smoke +# bewusst auf einer leeren DB. Frische des produktiven Dumps wird +# separat in check-restore-freshness.sh ueberwacht. docker compose -f "$COMPOSE_FILE" up -d restoretest-authelia-postgres >/dev/null until docker exec restoretest-authelia-postgres pg_isready -U authelia -d authelia >/dev/null 2>&1; do sleep 2 done -# Stufe 5: optional Dump einspielen -dump_status="skipped (no dump in archive)" -if [ "$dump_available" -eq 1 ]; then - restore_ok=0 - for attempt in $(seq 1 12); do - if docker exec -i restoretest-authelia-postgres \ - pg_restore -U authelia -d authelia --clean --if-exists --no-owner --no-privileges \ - < "$RESTORE_ROOT/dumps/latest/postgresql17-authelia.dump" 2>/tmp/authelia-pg-restore.err; then - restore_ok=1 - break - fi - if grep -qiE "starting up|shutting down|connection refused|database .* does not exist" /tmp/authelia-pg-restore.err; then - sleep 5 - continue - fi - cat /tmp/authelia-pg-restore.err >&2 - exit 1 - done - if [ "$restore_ok" -ne 1 ]; then - cat /tmp/authelia-pg-restore.err >&2 - exit 1 - fi - dump_status="restored" -fi - -# Stufe 6: config validate im Container-Kontext, gegen minimale Test-Config +# Stufe 4: config validate im Container-Kontext, gegen minimale Test-Config validate_status="ok" if ! docker run --rm \ -e AUTHELIA_SESSION_SECRET=restoretest-authelia-session-secret-placeholder-32 \ @@ -245,7 +243,7 @@ if ! docker run --rm \ exit 1 fi -# Stufe 7: Authelia-Container starten. Das Compose nutzt test-config als +# Stufe 5: Authelia-Container starten. Das Compose nutzt test-config als # /config-Mount mit isolierten Test-Backends. docker compose -f "$COMPOSE_FILE" up -d restoretest-authelia >/dev/null @@ -274,27 +272,39 @@ write_report "$REPORT_FILE" <=4.39 verlangt ohne Rules `two_factor`/`one_factor` | Test-Block ist bereits auf `two_factor` gesetzt; bei weiterer Schema-Aenderung anpassen | | HTTP-Timeout 120 s | Authelia haengt in Postgres-Schema-Migration | `docker logs --tail 200 restoretest-authelia` lesen, ggf. Wartezeit erhoehen | +| `encryption key does not appear to be valid for this database` | jemand hat `pg_restore` des produktiven Dumps wieder eingebaut | `pg_restore` ist seit `2026-06-03` bewusst NICHT mehr Teil dieses Smokes - siehe Plan/Skript-Doku; nicht re-aktivieren ohne kontrollierte Encryption-Key-Choreographie | | SMTP-Connect im Log | Testkonfig oder Env erzeugt unerwartet SMTP | `test-config/configuration.yml` und `AUTHELIA_*SMTP*` Env pruefen | -| `pg_restore` failt mit Schema-Drift | Dump aus 17er-Cluster, 18er Image braucht andere Initialisierung | Schritt als optional dokumentiert; Smoke ohne Dump akzeptieren und Issue nachverfolgen | +| `Could not determine the clock offset` | DNS-Lookup `time.cloudflare.com` failt im isolierten Test-Netz | `ntp.disable_startup_check: true` ist im Test-Config-Block bereits gesetzt; bei Aenderung beibehalten | +| `configuration environment variable not expected: AUTHELIA__SERVER__ADDRESS` | Doppel-Underscore ENV im Compose | seit `2026-06-03` entfernt; `server.address` kommt aus configuration.yml | ## Cleanup