Files
nexarch/scripts/e2e-module-keys.sh
patrick 5acb6e5e50
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
Merge-Fix: e2e-module-keys.sh nutzt persistente statt ephemere Sitzungsschluessel (Skript startet den Kontodienst neu und verwendet eine vor dem Neustart erzeugte Sitzung danach weiter; ephemer machte sie beim Neustart ungueltig, PR #121 hatte das uebersehen)
2026-09-22 11:28:08 +02:00

144 lines
8.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# API-16 Ende-zu-Ende auf dem Entwicklungshost (Wegwerf-Cluster 17/coredev): echte Kontodienst-Prozesse
# pruefen persistente Modul-Signaturschluessel: Neustart, zwei Instanzen, Rotation mit Ueberlappung,
# Ausfall des Schluesselspeichers (503) und falscher Verschluesselungsschluessel. Dauer rund 3 Minuten.
# usage (im Repo-Root, innerhalb der gemeinsamen Sperre): scripts/e2e-module-keys.sh
set -euo pipefail
cluster=/etc/nexarch-dev/core-cluster.sh
set -a
source /etc/nexarch-dev/core-dev.env
source /etc/nexarch-dev/tenant-dev.env
set +a
export PATH="$PATH:/usr/local/go/bin"
work="$(mktemp -d /tmp/nexarch-e2e-keys-XXXXXX)"
pids=()
cleanup() {
for p in "${pids[@]:-}"; do kill "$p" 2>/dev/null || true; done
psql "$TEST_TENANT_DSN" -q -c "ALTER TABLE IF EXISTS module_signing_keys_x RENAME TO module_signing_keys" >/dev/null 2>&1 || true
rm -rf "$work"
}
trap cleanup EXIT
fail() { echo "FAIL: $*" >&2; tail -15 "$work"/*.log >&2 2>/dev/null || true; exit 1; }
pass() { echo "PASS: $*"; }
rand() { head -c "$1" /dev/urandom | od -An -tx1 | tr -d ' \n'; }
password="e2e-$(rand 12)"
jwt_secret="e2e-$(rand 16)"
enc_key="$(head -c 32 /dev/urandom | base64)"
other_key="$(head -c 32 /dev/urandom | base64)"
session_enc_key="$(head -c 32 /dev/urandom | base64)"
"$cluster" resettenant
for f in $(ls migrations/tenant/*.up.sql | sort); do
psql "$TEST_TENANT_DSN" -q -v ON_ERROR_STOP=1 -f "$f" >/dev/null
done
go build -o "$work/account-api" ./cmd/account-api
go build -o "$work/verify" ./scripts/e2e-modulekeys-verify
NEXARCH_E2E_TENANT_DSN="$TEST_TENANT_DSN" NEXARCH_E2E_PASSWORD="$password" go run ./scripts/e2e-seed
TTL=30
ACT=60
base_env=(NEXARCH_ACCOUNT_TENANT_DSN="$TEST_TENANT_DSN" NEXARCH_ACCOUNT_TENANT_SLUG=e2e NEXARCH_ACCOUNT_JWT_SECRET="$jwt_secret"
NEXARCH_ACCOUNT_MODULE_TOKEN_TTL_SECONDS=$TTL NEXARCH_ACCOUNT_MODULE_KEY_ACTIVATION_DELAY_SECONDS=$ACT
NEXARCH_ACCOUNT_MODULE_KEY_REFRESH_SECONDS=1 NEXARCH_ACCOUNT_MODULE_KEY_ROTATION_DAYS=0
# QA-15 F-10: dieses Skript startet den Kontodienst mehrfach neu und
# nutzt eine vor dem Neustart erzeugte Sitzung danach weiter (mint auf
# a2 mit dem Cookie von a) — ephemere Sitzungsschluessel wuerden diese
# Sitzung beim Neustart ungueltig machen (siehe sessionkeys.go), daher
# hier persistente Sitzungsschluessel mit festem Verschluesselungsschluessel.
NEXARCH_ACCOUNT_SESSION_KEY_ENCRYPTION_KEY="$session_enc_key")
# env ersetzt sich selbst durch das Programm: $! ist danach die PID des Kontodienstes (kein Zwischenprozess, der beim kill uebrig bleibt)
LAST_PID=""
start() { # port name [key]
local key="${3:-$enc_key}"
env "${base_env[@]}" NEXARCH_ACCOUNT_MODULE_KEY_ENCRYPTION_KEY="$key" NEXARCH_ACCOUNT_API_LISTEN_ADDR="127.0.0.1:$1" \
"$work/account-api" >"$work/$2.log" 2>&1 &
LAST_PID=$!
pids+=("$LAST_PID")
}
wait_up() { for _ in $(seq 1 60); do curl -fsS "http://127.0.0.1:$1/healthz" >/dev/null 2>&1 && return 0; sleep 0.2; done; fail "dienst auf $1 nicht erreichbar"; }
kids() { curl -fsS "http://127.0.0.1:$1/auth/module-jwks" | grep -o '"kid":"[^"]*"' | cut -d'"' -f4 | sort | tr '\n' ' '; }
code() { curl -s -o /dev/null -w '%{http_code}' "$@"; }
login() { curl -fsS -c "$work/jar-$1" -H 'Content-Type: application/json' -d "{\"email\":\"alice@e2e.test\",\"password\":\"$password\"}" "http://127.0.0.1:$1/auth/login" >/dev/null; }
mint() { curl -fsS -b "$work/jar-$1" -X POST "http://127.0.0.1:$1/auth/module-token" | grep -o '"token":"[^"]*"' | cut -d'"' -f4; }
tokkid() { local seg; seg="$(echo "$1" | cut -d. -f1 | tr '_-' '/+')"; while [ $(( ${#seg} % 4 )) -ne 0 ]; do seg="$seg="; done; echo "$seg" | base64 -d 2>/dev/null | grep -o '"kid":"[^"]*"' | cut -d'"' -f4; }
valid() { "$work/verify" "http://127.0.0.1:$1/auth/module-jwks" "$2" >/dev/null; }
start 8099 a; a_pid=$LAST_PID; wait_up 8099
login 8099
kid0="$(kids 8099 | tr -d ' ')"
[ -n "$kid0" ] || fail "keine kid in der jwks"
tok0="$(mint 8099)"
[ "$(tokkid "$tok0")" = "$kid0" ] || fail "token traegt nicht die aktive kid"
valid 8099 "$tok0" || fail "token direkt nach dem start ungueltig"
pass "start: jwks liefert kid $kid0, token gueltig"
kill "$a_pid"; wait "$a_pid" 2>/dev/null || true
for _ in $(seq 1 50); do curl -fsS "http://127.0.0.1:8099/healthz" >/dev/null 2>&1 || break; sleep 0.1; done
curl -fsS "http://127.0.0.1:8099/healthz" >/dev/null 2>&1 && fail "kontodienst auf 8099 laeuft nach dem beenden weiter (kein echter neustart)"
start 8099 a2; a2_pid=$LAST_PID; wait_up 8099
[ "$a2_pid" != "$a_pid" ] && kill -0 "$a2_pid" 2>/dev/null || fail "neu gestarteter kontodienst laeuft nicht"
[ "$(kids 8099 | tr -d ' ')" = "$kid0" ] || fail "kid nach neustart geaendert"
valid 8099 "$tok0" || fail "token vor dem neustart nach neustart ungueltig"
pass "neustart (alter prozess beendet, neuer prozess $a2_pid): dieselbe kid, token vor dem neustart gueltig"
start 8098 b; wait_up 8098
[ "$(kids 8098 | tr -d ' ')" = "$kid0" ] || fail "zweite instanz hat andere schluessel"
login 8098
tokb="$(mint 8098)"
tok_a="$(mint 8099)"
valid 8099 "$tokb" || fail "token der instanz b gegen jwks von a ungueltig"
valid 8098 "$tok_a" || fail "token der instanz a gegen jwks von b ungueltig"
[ "$(psql "$TEST_TENANT_DSN" -Atc 'SELECT count(*) FROM module_signing_keys')" = 1 ] || fail "mehr als ein schluessel nach start zweier instanzen"
pass "zwei instanzen: gleiche schluessel, gegenseitig verifizierbar, genau ein schluessel in der datenbank"
rot_start=$(date +%s)
env "${base_env[@]}" NEXARCH_ACCOUNT_MODULE_KEY_ENCRYPTION_KEY="$enc_key" "$work/account-api" -rotate-module-key >"$work/rotate.log" 2>&1 || fail "rotation fehlgeschlagen"
sleep 3
[ "$(kids 8099 | wc -w)" = 2 ] || fail "a kennt den neuen schluessel nach 3 s nicht"
[ "$(kids 8098 | wc -w)" = 2 ] || fail "b kennt den neuen schluessel nach 3 s nicht"
tok_pending="$(mint 8099)"
[ "$(tokkid "$tok_pending")" = "$kid0" ] || fail "neuer schluessel signiert vor der aktivierungsverzoegerung"
pass "rotation: beide instanzen listen 2 schluessel, signiert wird bis zur aktivierung mit dem alten"
sleep $(( ACT + 3 ))
tok_new="$(mint 8099)"
kid1="$(tokkid "$tok_new")"
[ -n "$kid1" ] && [ "$kid1" != "$kid0" ] || fail "nach der aktivierungsverzoegerung signiert weiter der alte schluessel"
[ "$(tokkid "$(mint 8098)")" = "$kid1" ] || fail "instanz b signiert nicht mit dem neuen schluessel"
valid 8098 "$tok_new" || fail "token mit neuer kid ungueltig"
[ "$(kids 8099 | wc -w)" = 2 ] || fail "alter schluessel vor ablauf von ttl und toleranz entfernt"
pass "nach aktivierung: neue kid $kid1, alter schluessel bleibt in der jwks"
wait_until=$(( rot_start + ACT + TTL + 30 + 8 ))
now=$(date +%s)
[ "$now" -lt "$wait_until" ] && sleep $(( wait_until - now ))
[ "$(kids 8099 | tr -d ' ')" = "$kid1" ] || fail "alter schluessel nach ablauf noch in der jwks von a: $(kids 8099)"
[ "$(kids 8098 | tr -d ' ')" = "$kid1" ] || fail "alter schluessel nach ablauf noch in der jwks von b"
[ "$(psql "$TEST_TENANT_DSN" -Atc 'SELECT count(*) FROM module_signing_keys')" = 1 ] || fail "abgelaufener schluessel nicht aus der datenbank entfernt"
tok_last="$(mint 8098)"
valid 8099 "$tok_last" || fail "token nach dem entfernen des alten schluessels ungueltig"
pass "nach ablauf von ttl und toleranz: alter schluessel aus jwks und datenbank entfernt"
psql "$TEST_TENANT_DSN" -q -c "ALTER TABLE module_signing_keys RENAME TO module_signing_keys_x" >/dev/null
sleep 2
[ "$(code "http://127.0.0.1:8099/auth/module-jwks")" = 503 ] || fail "jwks bei nicht lesbarem schluesselspeicher nicht 503"
[ "$(code -b "$work/jar-8099" -X POST "http://127.0.0.1:8099/auth/module-token")" = 503 ] || fail "token bei nicht lesbarem schluesselspeicher nicht 503"
psql "$TEST_TENANT_DSN" -q -c "ALTER TABLE module_signing_keys_x RENAME TO module_signing_keys" >/dev/null
sleep 2
[ "$(code "http://127.0.0.1:8099/auth/module-jwks")" = 200 ] || fail "jwks nach wiederanlauf nicht 200"
[ "$(code -b "$work/jar-8099" -X POST "http://127.0.0.1:8099/auth/module-token")" = 200 ] || fail "token nach wiederanlauf nicht 200"
pass "ausfall des schluesselspeichers: 503 fuer jwks und token, danach wieder 200"
start 8097 wrongkey "$other_key"; wrong_pid=$LAST_PID
for _ in $(seq 1 50); do kill -0 "$wrong_pid" 2>/dev/null || break; sleep 0.2; done
if kill -0 "$wrong_pid" 2>/dev/null; then fail "dienst mit falschem verschluesselungsschluessel laeuft"; fi
grep -q "entschluesseln" "$work/wrongkey.log" || fail "keine meldung zum falschen schluessel: $(tail -3 "$work/wrongkey.log")"
env "${base_env[@]}" NEXARCH_ACCOUNT_MODULE_KEY_ENCRYPTION_KEY="" NEXARCH_ACCOUNT_API_LISTEN_ADDR=127.0.0.1:8096 "$work/account-api" >"$work/nokey.log" 2>&1 && fail "dienst ohne verschluesselungsschluessel startet"
grep -q "MODULE_KEY_ENCRYPTION_KEY" "$work/nokey.log" || fail "keine meldung zum fehlenden schluessel"
pass "falscher oder fehlender verschluesselungsschluessel: dienst startet nicht"
echo "ALLE PRUEFUNGEN BESTANDEN"