F-10: automated Authelia repo<->host drift check
New services/authelia-diff.sh compares the access_control: section of the repo baseline against the live host configuration.yml. OIDC clients, identity providers, and secret values stay out of scope by design. Exit codes: 0 ok, 1 drift, 2 file missing, 3 section missing, 4 tool missing. posture-check.sh gains check_authelia_config_drift, which calls the diff script and reports drift as warning (not critical). SKIP_AUTHELIA_DRIFT=1 opts out; AUTHELIA_DIFF_SCRIPT overrides the path. WORKFLOW.md gets a dedicated "Ausnahme: Authelia configuration.yml" section analogous to the Traefik dynamic-config exception, with the mandatory repo->host merge workflow and the env-variable contract. Smoke-tested locally: identical files rc=0, ACL change rc=1 with proper unified diff, non-ACL change (session.default_redirection_url) correctly ignored. Operator follow-up: set up a read-only repo mirror at /mnt/user/services/homelab-infra/ so the check finds a current baseline. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -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 "<section>:" 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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user