52fc007123
Erstlauf 2026-06-03 hat einen by-design-Konflikt offengelegt: pg_restore des
produktiven postgresql17-authelia.dump in eine Test-Instanz mit Wegwerf
AUTHELIA_STORAGE_ENCRYPTION_KEY scheitert im Authelia-Startup-Check mit
"the configured encryption key does not appear to be valid for this database".
Productive Storage-Werte werden mit dem produktiven Key verschluesselt; ein
Wegwerf-Key kann sie nicht entschluesseln. Smoke ist deshalb explizit auf
Config-Restore + Boot reduziert, nicht Daten-Decrypt.
Zwei Nebenbefunde aus demselben Lauf:
- AUTHELIA__SERVER__ADDRESS (Doppel-Underscore) wurde von Authelia 4.39
abgelehnt ("configuration environment variable not expected"). ENV
entfernt; server.address kommt eh aus der generierten configuration.yml.
- ntp-Startup-Check schlug fehl ("Could not determine the clock offset
... lookup time.cloudflare.com on 127.0.0.1:53: server misbehaving"),
weil das isolierte Test-Compose-Netz keinen DNS-Resolver fuer NTP hat.
Neuer Test-Config-Block setzt ntp.disable_startup_check: true.
Doku nachgezogen (Plan + Runbook): Encryption-Key-Konflikt ist explizit
als "nicht Teil dieses Smokes" dokumentiert; Fehler-Matrix hat Eintraege
fuer Doppel-Underscore-ENV und NTP-Lookup.
Frische des produktiven authelia-Dumps wird unveraendert ueber
check-restore-freshness.sh ueberwacht; Daten-Decrypt-Drill ist eine eigene
DR-Aufgabe mit kontrollierter Schluessel-Verwendung.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
312 lines
11 KiB
Bash
312 lines
11 KiB
Bash
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
# Authelia Restore Smoke Test
|
|
#
|
|
# Nicht-destruktiver Restore-Smoke-Test fuer Authelia.
|
|
#
|
|
# 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.
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
. "$SCRIPT_DIR/common.sh"
|
|
|
|
WHATIF=0
|
|
KEEP_DATA=0
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--what-if) WHATIF=1 ;;
|
|
--keep-data) KEEP_DATA=1 ;;
|
|
*) echo "Unknown argument: $arg" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
RESTORE_ROOT="/mnt/user/backups/restore-lab/authelia"
|
|
RESTORED_CONFIG_DIR="$RESTORE_ROOT/config"
|
|
TEST_CONFIG_DIR="$RESTORE_ROOT/test-config"
|
|
REPORT_ROOT="/mnt/user/backups/restore-reports"
|
|
EXTRACT_DIR="$BORG_RESTORE_HOST_ROOT/authelia-extract"
|
|
COMPOSE_FILE="$SCRIPT_DIR/authelia-compose.test.yml"
|
|
REPORT_FILE="$REPORT_ROOT/authelia-$(date +%F).md"
|
|
|
|
if [ "$WHATIF" -eq 1 ]; then
|
|
cat <<EOF
|
|
Authelia restore test
|
|
Mode: WhatIf
|
|
RestoreRoot: $RESTORE_ROOT
|
|
ReportRoot: $REPORT_ROOT
|
|
Expected Borg source paths:
|
|
- local/appdata/authelia/config
|
|
Planned isolation:
|
|
- 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
|
|
EOF
|
|
exit 0
|
|
fi
|
|
|
|
require_cmd docker
|
|
require_cmd curl
|
|
require_path "$BORG_PASSPHRASE_FILE_DEFAULT"
|
|
require_path "$COMPOSE_FILE"
|
|
|
|
RESTORE_SUCCESS=0
|
|
cleanup() {
|
|
cleanup_compose "$COMPOSE_FILE"
|
|
if [ "$RESTORE_SUCCESS" -ne 1 ]; then
|
|
preserve_on_failure "authelia" "$RESTORE_ROOT"
|
|
rm -rf "$EXTRACT_DIR"
|
|
return
|
|
fi
|
|
if [ "$KEEP_DATA" -ne 1 ]; then
|
|
rm -rf "$RESTORE_ROOT"
|
|
fi
|
|
rm -rf "$EXTRACT_DIR"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
rm -rf "$EXTRACT_DIR" "$RESTORE_ROOT"
|
|
mkdir -p "$RESTORED_CONFIG_DIR" "$TEST_CONFIG_DIR" "$RESTORE_ROOT/postgres"
|
|
|
|
archive="$(latest_archive_name)"
|
|
repo="$(borg_repo_url)"
|
|
|
|
if [ -z "$archive" ] || [ -z "$repo" ]; then
|
|
echo "Could not resolve Borg repo/archive from borg-ui database" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Stufe 1: Config aus Borg extrahieren
|
|
borg_extract "/restore/authelia-extract" "local/appdata/authelia/config"
|
|
if [ ! -d "$EXTRACT_DIR/local/appdata/authelia/config" ]; then
|
|
echo "Authelia config path missing in Borg archive" >&2
|
|
exit 1
|
|
fi
|
|
cp -a "$EXTRACT_DIR/local/appdata/authelia/config/." "$RESTORED_CONFIG_DIR/"
|
|
|
|
# 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.
|
|
ORIGINAL_CONFIG_FILE="$RESTORED_CONFIG_DIR/configuration.yml"
|
|
TEST_CONFIG_FILE="$TEST_CONFIG_DIR/configuration.yml"
|
|
if [ ! -f "$ORIGINAL_CONFIG_FILE" ]; then
|
|
echo "configuration.yml missing in restored config dir" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Kopiere alle Begleitdateien (z. B. users_database.yml) in einen separaten
|
|
# Runtime-Mount. configuration.yml wird danach vollstaendig neu geschrieben.
|
|
cp -a "$RESTORED_CONFIG_DIR/." "$TEST_CONFIG_DIR/"
|
|
cp "$ORIGINAL_CONFIG_FILE" "$RESTORED_CONFIG_DIR/configuration.yml.original"
|
|
|
|
cat > "$TEST_CONFIG_FILE" <<'YAML'
|
|
---
|
|
# Minimal-Konfiguration nur fuer den Restore-Smoke.
|
|
|
|
theme: dark
|
|
|
|
server:
|
|
address: tcp://0.0.0.0:9091
|
|
|
|
log:
|
|
level: info
|
|
|
|
authentication_backend:
|
|
file:
|
|
path: /config/users_database.yml
|
|
password:
|
|
algorithm: argon2id
|
|
iterations: 3
|
|
key_length: 32
|
|
salt_length: 16
|
|
memory: 65536
|
|
parallelism: 4
|
|
|
|
access_control:
|
|
# Authelia 4.39 verlangt: wenn KEINE Regeln gesetzt sind, muss default_policy
|
|
# 'two_factor' oder 'one_factor' sein. 'bypass' ist als Default-Policy ohne
|
|
# explizite Regeln nicht erlaubt. Fuer den Smoke ist das egal: /api/health
|
|
# ist ein public Endpunkt und laeuft nicht durch access_control.
|
|
default_policy: two_factor
|
|
|
|
regulation:
|
|
max_retries: 3
|
|
find_time: 2m
|
|
ban_time: 5m
|
|
|
|
totp:
|
|
issuer: kaleschke.info
|
|
period: 30
|
|
skew: 1
|
|
|
|
storage:
|
|
postgres:
|
|
address: tcp://restoretest-authelia-postgres:5432
|
|
database: authelia
|
|
username: authelia
|
|
# Passwort kommt ueber AUTHELIA_STORAGE_POSTGRES_PASSWORD ENV.
|
|
|
|
notifier:
|
|
disable_startup_check: true
|
|
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
|
|
domain: kaleschke.info
|
|
authelia_url: https://auth.kaleschke.info
|
|
default_redirection_url: https://glance.kaleschke.info
|
|
expiration: 1h
|
|
inactivity: 5m
|
|
|
|
identity_validation:
|
|
reset_password:
|
|
jwt_secret: restoretest-authelia-reset-password-jwt-secret-placeholder-64bytes
|
|
jwt_lifespan: 5m
|
|
jwt_algorithm: HS256
|
|
YAML
|
|
|
|
mkdir -p "$TEST_CONFIG_DIR/notifier"
|
|
chmod -R a+rwX "$TEST_CONFIG_DIR/notifier"
|
|
|
|
# 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 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 \
|
|
-e AUTHELIA_STORAGE_ENCRYPTION_KEY=restoretest-authelia-storage-enc-key-placeholder-32 \
|
|
-e AUTHELIA_STORAGE_POSTGRES_PASSWORD=restoretest-authelia-db \
|
|
-v "$TEST_CONFIG_DIR:/config" \
|
|
authelia/authelia:4.39.20@sha256:1b363e9279e742397966333f364e0876ae02bf5c876de73e83af6d48c57ff51b \
|
|
authelia config validate --config /config/configuration.yml \
|
|
>/tmp/authelia-validate.log 2>&1; then
|
|
validate_status="failed"
|
|
cat /tmp/authelia-validate.log >&2
|
|
exit 1
|
|
fi
|
|
|
|
# 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
|
|
|
|
http_status=""
|
|
for _ in $(seq 1 60); do
|
|
http_status="$(curl -s -o /tmp/authelia-body.html -w '%{http_code}' \
|
|
http://127.0.0.1:19091/api/health || true)"
|
|
if [ "$http_status" = "200" ]; then
|
|
break
|
|
fi
|
|
sleep 2
|
|
done
|
|
|
|
if [ "$http_status" != "200" ]; then
|
|
echo "Authelia HTTP health failed: status=$http_status" >&2
|
|
docker logs --tail 120 restoretest-authelia >&2 || true
|
|
exit 1
|
|
fi
|
|
|
|
write_report "$REPORT_FILE" <<EOF
|
|
# Authelia Restore Test Report - $(date +%F)
|
|
|
|
- Service: \`authelia\`
|
|
- Source repo: \`$repo\`
|
|
- Archive: \`$archive\`
|
|
- Restore root: \`$RESTORE_ROOT\`
|
|
- Test containers:
|
|
- \`restoretest-authelia\`
|
|
- \`restoretest-authelia-postgres\` (fresh schema, no productive dump)
|
|
- Test endpoint: \`http://127.0.0.1:19091/api/health\`
|
|
- Result: \`SUCCESS\`
|
|
|
|
## Checks
|
|
|
|
- Borg extract of config: \`ok\`
|
|
- configuration.yml present in archive: \`ok\`
|
|
- test runtime configuration.yml written: \`ok\`
|
|
- \`authelia config validate\`: \`$validate_status\`
|
|
- HTTP /api/health status: \`$http_status\`
|
|
|
|
## Scope
|
|
|
|
Dieser Smoke prueft: Borg-Restore der Config, Validate gegen Produktions-Image,
|
|
Authelia-Boot gegen frische Test-Postgres + Wegwerf-Encryption-Key,
|
|
HTTP-Health-Endpoint antwortet.
|
|
|
|
Bewusst NICHT Teil des Smokes: pg_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-Key
|
|
schlaegt im Startup-Check by design fehl. Frische des produktiven Dumps
|
|
wird in \`check-restore-freshness.sh\` ueberwacht; Daten-Decrypt-Drill ist
|
|
eine separate DR-Aufgabe.
|
|
|
|
## Notes
|
|
|
|
- Test ran without Traefik and without the productive domain \`auth.kaleschke.info\`.
|
|
- Productive Authelia secrets under \`/mnt/user/appdata/secrets/authelia_*.txt\` were NOT mounted.
|
|
- Notifier was forced to filesystem (\`/config/notifier/notifications.txt\`); no SMTP call to GMX.
|
|
- Storage forced to isolated test postgres; productive shared PostgreSQL 18 was NOT touched.
|
|
- NTP startup-check disabled in test config (kein DNS-Resolver im isolierten Compose-Netz).
|
|
- Test data was cleaned after success: \`$([ "$KEEP_DATA" -eq 1 ] && echo no || echo yes)\`
|
|
EOF
|
|
|
|
RESTORE_SUCCESS=1
|
|
echo "Authelia restore test ok -> $REPORT_FILE"
|