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
144 lines
8.7 KiB
Bash
Executable File
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"
|