RAIN: Phase 13.5 Emergency Rollback CP Repair - target-image-based rollback pre-gates (current/recovery gold). SoT local, no push.
This commit is contained in:
parent
a7336c2585
commit
d6394ef327
1 changed files with 103 additions and 7 deletions
|
|
@ -1305,6 +1305,101 @@ verify_candidate_image() {
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ==== RAIN EMERGENCY ROLLBACK CP REPAIR: Target-spezifische image-only Verifikation ====
|
||||||
|
# ROLLBACK-PRE-GATES muessen das jeweilige ROLLBACK-ZIEL-IMAGE direkt verifizieren,
|
||||||
|
# NICHT den laufenden historical-service Container (der traegt bewusst ein anderes
|
||||||
|
# Image, z.B. Remediation). Originale verify_critical_files/verify_recovery_gold_image
|
||||||
|
# vergleichen docker exec "$HISTORICAL_SERVICE" (Z.916) -> falscher FAIL bei laufendem
|
||||||
|
# Remediation-Container. Diese Helper prüfen ausschliesslich ein festes Ziel-Image.
|
||||||
|
# KEIN freies CLI-Argument, KEIN latest, KEINE auto-discovery.
|
||||||
|
|
||||||
|
# Critical-File-SHAs eines ZIEL-IMAGES gegen Gold. NUR Image, KEIN Container-Vergleich.
|
||||||
|
# $1 = Basisverzeichnis mit app/ (kopierter Target-Tree). FAIL CLOSED.
|
||||||
|
verify_critical_files_image_only() {
|
||||||
|
local base_dir="$1" allok=1 k path is gold
|
||||||
|
for k in "${!CRITICAL_SHA[@]}"; do
|
||||||
|
path="${CRITICAL_PATH[$k]#app/}"
|
||||||
|
is="MISSING"
|
||||||
|
[ -f "$base_dir/$path" ] && is="$(sha256sum "$base_dir/$path" | awk '{print $1}')"
|
||||||
|
gold="${CRITICAL_SHA[$k]}"
|
||||||
|
if [ "$is" = "$gold" ]; then
|
||||||
|
printf ' MATCH %-22s image=%s gold=%s\n' "$k" "${is:0:12}" "${gold:0:12}"
|
||||||
|
else
|
||||||
|
printf ' FAIL %-22s image=%s gold=%s\n' "$k" "${is:0:12}" "${gold:0:12}"
|
||||||
|
allok=0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
[ "$allok" = "1" ] || die "verify: Critical-File-SHA-Mismatch im Ziel-IMAGE (FAIL CLOSED)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Runtime-Config eines ZIEL-IMAGES gegen erwartete Baseline. $1 = Target-ID/Tag.
|
||||||
|
# NUR Image-inspect, KEIN Container. FAIL CLOSED. ENV/secrets redigiert.
|
||||||
|
verify_image_config_target() {
|
||||||
|
local TGT="$1"
|
||||||
|
local exp_entry="[]" exp_workdir="/app" exp_user="" exp_expose="map[8000/tcp:{}]"
|
||||||
|
echo " [config] Entrypoint, Cmd, WorkingDir, User, ExposedPorts:"
|
||||||
|
local c_entry c_cmd c_wd c_user c_expose
|
||||||
|
c_entry="$(docker image inspect -f '{{json .Config.Entrypoint}}' "$TGT" 2>/dev/null)"
|
||||||
|
c_cmd="$(docker image inspect -f '{{json .Config.Cmd}}' "$TGT" 2>/dev/null)"
|
||||||
|
c_wd="$(docker image inspect -f '{{.Config.WorkingDir}}' "$TGT" 2>/dev/null)"
|
||||||
|
c_user="$(docker image inspect -f '{{.Config.User}}' "$TGT" 2>/dev/null)"
|
||||||
|
c_expose="$(docker image inspect -f '{{json .Config.ExposedPorts}}' "$TGT" 2>/dev/null)"
|
||||||
|
printf ' Entrypoint = %s (erwartet %s)\n' "${c_entry:-<nil>}" "$exp_entry"
|
||||||
|
printf ' Cmd = %s\n' "${c_cmd:-<nil>}"
|
||||||
|
printf ' WorkingDir = %s (erwartet %s)\n' "${c_wd:-<nil>}" "$exp_workdir"
|
||||||
|
printf ' User = %s (erwartet leer)\n' "${c_user:-<nil>}"
|
||||||
|
printf ' Exposed = %s (erwartet 8000)\n' "${c_expose:-<nil>}"
|
||||||
|
[ "$c_wd" = "$exp_workdir" ] || die "verify: WorkingDir unerwartet '$c_wd' (FAIL CLOSED)"
|
||||||
|
echo " [config] OK (ENV/secrets NICHT ausgegeben)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Kompletter image-only Gold-Verify eines festen ZIEL-IMAGES.
|
||||||
|
# $1 = feste Target-Image-ID (CANDIDATE_IMAGE_ID | RECOVERY_GOLD_IMAGE_ID), intern gebunden.
|
||||||
|
# inspectable -> 63/63 Gold-Manifest -> Critical SHAs -> Config. KEIN Container-Vergleich.
|
||||||
|
verify_gold_target_image() {
|
||||||
|
local TARGET_ID="$1"
|
||||||
|
echo " [target] Target-Image inspectable:"
|
||||||
|
docker image inspect "$TARGET_ID" >/dev/null 2>&1 || die "target FAIL: Ziel-Image nicht inspectable (FAIL CLOSED)"
|
||||||
|
echo " OK ($TARGET_ID)"
|
||||||
|
echo " [target] 63/63 Gold-Manifest + Critical SHAs + Config (IMAGE-ONLY, kein Container-Vergleich):"
|
||||||
|
local tmpc tmpdir tmpf gret
|
||||||
|
tmpc="rq-goldtgt-$(date +%s)"; tmpdir="/tmp/rq-goldtgt-copy-$$"
|
||||||
|
rm -rf "$tmpdir"; mkdir -p "$tmpdir"
|
||||||
|
docker create --name "$tmpc" --entrypoint /bin/true "$TARGET_ID" >/dev/null 2>&1 || die "target: Temp-Container Fehler (FAIL CLOSED)"
|
||||||
|
docker cp "$tmpc:/app/app" "$tmpdir/appapp" >/dev/null 2>&1 || { docker rm -f "$tmpc" >/dev/null 2>&1; rm -rf "$tmpdir"; die "target: /app/app nicht lesbar (FAIL CLOSED)"; }
|
||||||
|
docker rm -f "$tmpc" >/dev/null 2>&1 || true
|
||||||
|
tmpf="$(mktemp)"
|
||||||
|
( cd "$tmpdir/appapp" && find . -type f -name '*.py' ! -path '*/__pycache__/*' -printf '%P\n' 2>/dev/null | sort \
|
||||||
|
| while IFS= read -r rel; do [ -f "$rel" ] && sha256sum "$rel"; done | awk '{print $1" app/"$2}' ) > "$tmpf"
|
||||||
|
classify_and_report "$tmpf"
|
||||||
|
gret="$G_RET"; rm -f "$tmpf"
|
||||||
|
if [ "$gret" != "ok" ]; then
|
||||||
|
rm -rf "$tmpdir"; die "target FAIL: Ziel-Image != Gold-Manifest (FAIL CLOSED)"
|
||||||
|
fi
|
||||||
|
echo " TARGET vs GOLD: 63 MATCH / 0 DIVERGED / 0 MISSING / 0 unerlaubte EXTRA"
|
||||||
|
echo " [target] Critical-Files (IMAGE-ONLY):"
|
||||||
|
verify_critical_files_image_only "$tmpdir/appapp"
|
||||||
|
rm -rf "$tmpdir"
|
||||||
|
verify_image_config_target "$TARGET_ID"
|
||||||
|
echo " VERDICT: TARGET GOLD OK (63/63, Critical SHAs + Config PASS, IMAGE-ONLY)"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# SEPARATER Current-Service-Safety-Check. Prueft, DASS der historical-service laeuft
|
||||||
|
# und healthy ist. Prueft NICHT gegen das Ziel-Gold-Manifest (der Sinn des Rollbacks
|
||||||
|
# ist, aus einem anderen Zustand zurueckzukehren). Intern, kein freies Argument.
|
||||||
|
current_service_safety() {
|
||||||
|
echo " [service] historical-service Running + health:"
|
||||||
|
local running rh
|
||||||
|
running="$(docker inspect -f '{{.State.Running}}' "$HISTORICAL_SERVICE" 2>/dev/null)"
|
||||||
|
[ "$running" = "true" ] || die "service FAIL: historical-service nicht Running (FAIL CLOSED)"
|
||||||
|
rh="$(docker exec "$HISTORICAL_SERVICE" python -c 'import urllib.request;print(urllib.request.urlopen("http://localhost:8000/health",timeout=5).status)' 2>/dev/null || echo N/A)"
|
||||||
|
[ "$rh" = "200" ] || die "service FAIL: health != 200 (FAIL CLOSED)"
|
||||||
|
echo " OK (running, /health=$rh)"
|
||||||
|
echo " [service] laufendes Image (known state, NICHT gegen Gold geprueft):"
|
||||||
|
docker inspect -f ' {{.Image}}' "$HISTORICAL_SERVICE" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
# ---- recreate-candidate-safe (NUR historical-service; nur Red Queen) ----
|
# ---- recreate-candidate-safe (NUR historical-service; nur Red Queen) ----
|
||||||
recreate_candidate_safe() {
|
recreate_candidate_safe() {
|
||||||
expect_zero "$@"
|
expect_zero "$@"
|
||||||
|
|
@ -1343,8 +1438,10 @@ rollback_to_recovery_gold() {
|
||||||
local rt; rt="$(docker image inspect -f '{{.Id}}' "$RECOVERY_GOLD_TAG_FINAL" 2>/dev/null || true)"
|
local rt; rt="$(docker image inspect -f '{{.Id}}' "$RECOVERY_GOLD_TAG_FINAL" 2>/dev/null || true)"
|
||||||
[ "$rt" = "$RECOVERY_GOLD_IMAGE_ID" ] || die "pre FAIL: Recovery-Tag != Recovery-ID (FAIL CLOSED)"
|
[ "$rt" = "$RECOVERY_GOLD_IMAGE_ID" ] || die "pre FAIL: Recovery-Tag != Recovery-ID (FAIL CLOSED)"
|
||||||
echo " OK"
|
echo " OK"
|
||||||
echo " [pre] verify-recovery-gold-image PASS:"
|
echo " [pre] Current Service Safety (running + health, NICHT Gold-geprueft):"
|
||||||
( verify_recovery_gold_image >/dev/null 2>&1 ) && echo " OK" || die "pre FAIL: Recovery-Gold nicht verifiziert (FAIL CLOSED)"
|
current_service_safety
|
||||||
|
echo " [pre] RECOVERY TARGET direkt verifizieren (IMAGE-ONLY, kein Container-Vergleich):"
|
||||||
|
verify_gold_target_image "$RECOVERY_GOLD_IMAGE_ID"
|
||||||
echo " [pre] historical-db healthy:"
|
echo " [pre] historical-db healthy:"
|
||||||
ensure_db_run && echo " OK"
|
ensure_db_run && echo " OK"
|
||||||
if [ "$RUN_MODE" = "DRY_RUN" ]; then
|
if [ "$RUN_MODE" = "DRY_RUN" ]; then
|
||||||
|
|
@ -1372,11 +1469,10 @@ rollback_to_current_gold_anchor() {
|
||||||
echo " Ziel-Service : $HISTORICAL_SERVICE (NUR dieser; KEIN db, KEINE Volumes)"
|
echo " Ziel-Service : $HISTORICAL_SERVICE (NUR dieser; KEIN db, KEINE Volumes)"
|
||||||
echo " Image : $GOLD_ANCHOR (fixed, KEIN latest, KEIN Tag erforderlich)"
|
echo " Image : $GOLD_ANCHOR (fixed, KEIN latest, KEIN Tag erforderlich)"
|
||||||
echo " Preconditions:"
|
echo " Preconditions:"
|
||||||
echo " [pre] Gold-Anker inspectable:"
|
echo " [pre] Current Service Safety (running + health, NICHT Gold-geprueft):"
|
||||||
docker image inspect "$GOLD_ANCHOR" >/dev/null 2>&1 || die "pre FAIL: Gold-Anker nicht inspectable (FAIL CLOSED)"
|
current_service_safety
|
||||||
echo " OK"
|
echo " [pre] PRIMARY TARGET direkt verifizieren (IMAGE-ONLY, kein Container-Vergleich):"
|
||||||
echo " [pre] Gold-Anker 63/63 Gold-MATCH:"
|
verify_gold_target_image "$GOLD_ANCHOR"
|
||||||
( verify_recovery_gold_image >/dev/null 2>&1 ) && echo " OK (Recovery-Gold 63/63; Gold-Anker separat verifiziert)" || die "pre FAIL: Recovery-Gold nicht verifiziert (FAIL CLOSED)"
|
|
||||||
echo " [pre] Recovery READY:"
|
echo " [pre] Recovery READY:"
|
||||||
( verify_ready recovery >/dev/null 2>&1 ) || die "pre FAIL: Recovery-READY nicht PASS (FAIL CLOSED)"
|
( verify_ready recovery >/dev/null 2>&1 ) || die "pre FAIL: Recovery-READY nicht PASS (FAIL CLOSED)"
|
||||||
echo " OK"
|
echo " OK"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue