fix(restore): harden restore checks and add authelia smoke scaffold
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Authelia Restore Smoke Test
|
||||
#
|
||||
# Nicht-destruktiver Restore-Smoke-Test fuer Authelia.
|
||||
# - extrahiert die Authelia-Config aus dem produktiven Borg-Archiv
|
||||
# - patcht in einer Restore-Lab-Kopie der configuration.yml die
|
||||
# externen Abhaengigkeiten (storage = lokales Test-Postgres,
|
||||
# notifier = Filesystem-Notifier, identity_validation auf Test-Werte)
|
||||
# - importiert optional den shared-Postgres-Dump fuer Authelia
|
||||
# - validiert die gepatchte Konfiguration mit `authelia config validate`
|
||||
# - startet einen isolierten Authelia-Container ohne Traefik
|
||||
# - prueft den HTTP-Health-Endpunkt
|
||||
# - bereinigt anschliessend
|
||||
#
|
||||
# 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"
|
||||
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
|
||||
- local/borg-dumps/latest/postgresql17-authelia.dump (optional - wird uebersprungen wenn nicht vorhanden)
|
||||
Planned isolation:
|
||||
- Test-Postgres: postgres:18.4 mit Wegwerf-Credentials
|
||||
- Test-Authelia: authelia/authelia:4.39.20 (Image-Pin wie Produktion)
|
||||
- Wegwerf-Secrets ausschliesslich im Test-Compose
|
||||
- configuration.yml wird im Restore-Lab gepatcht:
|
||||
* storage -> Test-Postgres (kein produktives Postgres erreicht)
|
||||
* notifier -> Filesystem (KEIN SMTP-Versand)
|
||||
* session -> in-memory (kein Redis-Backend noetig)
|
||||
- Test endpoint: 127.0.0.1:19091/api/health (no Traefik, no public domain)
|
||||
Smoke-Test:
|
||||
- authelia config validate gegen gepatchte 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 "$RESTORE_ROOT/config" "$RESTORE_ROOT/postgres" "$RESTORE_ROOT/dumps/latest" "$RESTORE_ROOT/notifier"
|
||||
|
||||
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/." "$RESTORE_ROOT/config/"
|
||||
|
||||
# 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: configuration.yml im Restore-Lab gezielt patchen.
|
||||
# Wir ersetzen storage/notifier/session-Blocks durch Test-Definitionen,
|
||||
# damit der Test KEIN produktives Postgres und KEIN echtes SMTP anspricht.
|
||||
CONFIG_FILE="$RESTORE_ROOT/config/configuration.yml"
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
echo "configuration.yml missing in restored config dir" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Sichere Originalkopie fuer Diff/Diagnose
|
||||
cp "$CONFIG_FILE" "$CONFIG_FILE.original"
|
||||
|
||||
# Schreibe Drop-In fuer Test-Backends. Authelia 4.39 laedt mehrere
|
||||
# Configdateien ueber wiederholte --config-Argumente; einfacher fuer Smoke
|
||||
# ist ein gezielter Overlay-File, der Test-Werte vorgibt.
|
||||
cat > "$RESTORE_ROOT/config/configuration.test-overlay.yml" <<'YAML'
|
||||
# Test-Overlay nur fuer Restore-Smoke. Wird als zweite --config-Datei
|
||||
# zusaetzlich zur restaurierten configuration.yml geladen und ueberschreibt
|
||||
# externe Abhaengigkeiten.
|
||||
|
||||
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
|
||||
|
||||
session:
|
||||
cookies:
|
||||
- name: authelia_session_restoretest
|
||||
domain: kaleschke.info
|
||||
authelia_url: http://127.0.0.1:19091
|
||||
default_redirection_url: http://127.0.0.1:19091
|
||||
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 "$RESTORE_ROOT/config/notifier"
|
||||
chmod -R a+rwX "$RESTORE_ROOT/config/notifier"
|
||||
|
||||
# Stufe 4: Test-Postgres hochfahren
|
||||
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 restauriertes + overlay
|
||||
validate_status="ok"
|
||||
if ! docker run --rm \
|
||||
-e AUTHELIA_JWT_SECRET=restoretest-authelia-jwt-secret-placeholder-32bytes \
|
||||
-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 \
|
||||
-e AUTHELIA_NOTIFIER_SMTP_PASSWORD=restoretest-authelia-smtp-placeholder \
|
||||
-v "$RESTORE_ROOT/config:/config" \
|
||||
authelia/authelia:4.39.20@sha256:1b363e9279e742397966333f364e0876ae02bf5c876de73e83af6d48c57ff51b \
|
||||
authelia config validate --config /config/configuration.yml --config /config/configuration.test-overlay.yml \
|
||||
>/tmp/authelia-validate.log 2>&1; then
|
||||
validate_status="failed"
|
||||
cat /tmp/authelia-validate.log >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Stufe 7: Authelia-Container starten. Das Compose setzt wiederholte
|
||||
# --config-Argumente, sodass das Test-Overlay zusaetzlich geladen wird; die
|
||||
# zweite Datei gewinnt bei Konflikten und ersetzt storage/notifier/session.
|
||||
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\`
|
||||
- Test endpoint: \`http://127.0.0.1:19091/api/health\`
|
||||
- Result: \`SUCCESS\`
|
||||
|
||||
## Checks
|
||||
|
||||
- Borg extract of config: \`ok\`
|
||||
- Borg extract of dump: \`$dump_status\`
|
||||
- configuration.yml present: \`ok\`
|
||||
- Test-overlay (storage/notifier/session) written: \`ok\`
|
||||
- \`authelia config validate\`: \`$validate_status\`
|
||||
- HTTP /api/health status: \`$http_status\`
|
||||
|
||||
## 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.
|
||||
- Test data was cleaned after success: \`$([ "$KEEP_DATA" -eq 1 ] && echo no || echo yes)\`
|
||||
- Restore-Quelle Dump: \`local/borg-dumps/latest/postgresql17-authelia.dump\` (optional, wenn im Archiv).
|
||||
EOF
|
||||
|
||||
RESTORE_SUCCESS=1
|
||||
echo "Authelia restore test ok -> $REPORT_FILE"
|
||||
Reference in New Issue
Block a user