diff --git a/red-queen-architecture/control-plane/cp2a2_2b/rq-historical b/red-queen-architecture/control-plane/cp2a2_2b/rq-historical index 8a985ad..e46a44e 100755 --- a/red-queen-architecture/control-plane/cp2a2_2b/rq-historical +++ b/red-queen-architecture/control-plane/cp2a2_2b/rq-historical @@ -1131,10 +1131,16 @@ recreate_pre_gates() { # Alle echten Remediation-Gates bleiben FAIL CLOSED. Kein freies Image, keine freie ID. remediation_recreate_pre_gates() { load_recovery_state - echo " [gate A] verify-recovery-gold-image PASS:" - ( verify_recovery_gold_image >/dev/null 2>&1 ) && echo " OK" || die "Gate A FAIL: Recovery-Gold nicht verifiziert (FAIL CLOSED)" - echo " [gate B] verify-candidate-image PASS:" - ( verify_candidate_image >/dev/null 2>&1 ) && echo " OK" || die "Gate B FAIL: Candidate nicht verifiziert (FAIL CLOSED)" + echo " [gate A] verify-gold-target-image (RECOVERY/GOLD TARGET-State, image-only) PASS:" + # Phase 13.5 Pre-Gate-A-Fix: verifiziere das RECOVERY/GOLD-ZIEL (Target-State) direkt, + # NICHT den laufenden historical-service Source-State. Ein Remediation-Recreate muss + # auch moeglich sein, wenn der aktuelle Service bereits Remediation traegt. Der fix + # wiederverwendet den bereits geprueften image-basierten Helper verify_gold_target_image + # (63/63 Gold + Critical SHAs + Config, via Temp-Container aus dem ZIEL-Image). + # RECOVERY_GOLD_IMAGE_ID ist fest aus Recovery-State (load_recovery_state), keine freie ID/kein latest. + ( verify_gold_target_image "$RECOVERY_GOLD_IMAGE_ID" >/dev/null 2>&1 ) && echo " OK" || die "Gate A FAIL: Recovery/Gold-Ziel nicht verifiziert (FAIL CLOSED)" + echo " [gate B] verify-candidate-target-image (REMEDIATION/CANDIDATE TARGET-State, image-only 62+1) PASS:" + ( verify_candidate_target_image "$(remediation_full_image_id)" >/dev/null 2>&1 ) && echo " OK" || die "Gate B FAIL: Candidate/Remediation-Ziel nicht verifiziert (FAIL CLOSED)" echo " [gate C] Recovery Image inspectable:" docker image inspect "$RECOVERY_GOLD_IMAGE_ID" >/dev/null 2>&1 || die "Gate C FAIL (FAIL CLOSED)" echo " OK" @@ -1316,12 +1322,17 @@ verify_candidate_image() { # 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 + local base_dir="$1" allok=1 k path is gold allow_runner="${2:-}" 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]}" + # Autorisierte Runner-Divergenz: NUR run_backfill_year.py, NUR im Image, NUR auf RUNNER_REMEDIATION_SHA. + if [ "$allow_runner" = "runner" ] && [ "$k" = "run_backfill_year.py" ] && [ "$is" = "$RUNNER_REMEDIATION_SHA" ]; then + printf ' AUTH %-22s image=%s gold=%s (autorisierte Runner-Divergenz)\n' "$k" "${is:0:12}" "${gold:0:12}" + continue + fi if [ "$is" = "$gold" ]; then printf ' MATCH %-22s image=%s gold=%s\n' "$k" "${is:0:12}" "${gold:0:12}" else @@ -1353,6 +1364,46 @@ verify_image_config_target() { echo " [config] OK (ENV/secrets NICHT ausgegeben)" } +# Phase 13.5 Gate-B-Fix: image-only 62+1 Candidate-Target-Verifikation. +# Prueft das REMEDIATION-CANDIDATE-ZIEL-IMAGE direkt (feste ID aus Session), NICHT den +# laufenden historical-service Container. Erlaubt exakt 1 autorisierte Runner-Divergenz +# (run_backfill_year.py = RUNNER_REMEDIATION_SHA). repository.py MUSS Gold bleiben. +# Wiederverwendet: authorized_gold_delta, verify_critical_files_image_only, verify_image_config_target. +# KEIN freies Image / latest / autodiscovery. FAIL CLOSED bei jeder Abweichung. +verify_candidate_target_image() { + local TARGET_ID="$1" + echo " [candidate-target] Remediation-Candidate-Image inspectable:" + docker image inspect "$TARGET_ID" >/dev/null 2>&1 || die "candidate-target FAIL: Ziel-Image nicht inspectable (FAIL CLOSED)" + echo " OK ($TARGET_ID)" + echo " [candidate-target] 62+1 Gold-Manifest (1 autorisierter Runner-Overlay) + Critical SHAs + Config (IMAGE-ONLY):" + local tmpc tmpdir tmpf + tmpc="rq-candtgt-$(date +%s)"; tmpdir="/tmp/rq-candtgt-copy-$$" + rm -rf "$tmpdir"; mkdir -p "$tmpdir" + docker create --name "$tmpc" --entrypoint /bin/true "$TARGET_ID" >/dev/null 2>&1 || die "candidate-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 "candidate-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" + authorized_gold_delta "$tmpf" + echo " Image vs GOLD: MATCH=$AD_MATCH AUTHORIZED_DIVERGED=$AD_AUTH UNAUTHORIZED_DIVERGED=$AD_UNAUTH MISSING=$AD_MISSING unerlaubte EXTRA=$AD_EXTRA" + [ "$AD_AUTH" = "1" ] || { rm -rf "$tmpdir"; rm -f "$tmpf"; die "candidate-target: Gold-Delta != exakt 1 autorisierte Runner-Divergenz (FAIL CLOSED)"; } + [ "$AD_RET" = "ok" ] || { rm -rf "$tmpdir"; rm -f "$tmpf"; die "candidate-target: Image != Gold-Manifest (unzulaessige Divergenz) (FAIL CLOSED)"; } + # [repository hard gate] app/persistence/repository.py im Image MUSS Gold sein (8fff892f...) + local irepo; irepo="$(awk '$2=="app/persistence/repository.py"{print $1}' "$tmpf")" + [ "$irepo" = "8fff892f52a612f464b5be21c66ef7787a47f2d7d049f815ef87837553c2a228" ] \ + || { rm -rf "$tmpdir"; rm -f "$tmpf"; die "candidate-target: repository.py im Image != Gold (8fff892f...) (FAIL CLOSED)"; } + echo " repository.py = Gold (8fff892f...) OK" + rm -f "$tmpf" + echo " [Critical-Files] Candidate-Target vs gold (image-only, autorisierte Runner-Divergenz erlaubt):" + verify_critical_files_image_only "$tmpdir/appapp" runner + rm -rf "$tmpdir" + echo " [Config] Candidate-Target Runtime Config:" + verify_image_config_target "$TARGET_ID" + echo "VERDICT: CANDIDATE TARGET OK (62 Gold-MATCH + 1 autorisierter Runner-Overlay; repository.py Gold; Critical-SHAs + Config PASS)" + return 0 +} + # 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. @@ -1925,8 +1976,8 @@ verify_runner_remediation_image() { echo " HOST_SOT_DRIFT=DETECTED (repository.py $hrepo vs Gold 8fff892f...)" echo " HOST_SOT_DRIFT=NON-BLOCKING_FOR_REMEDIATION_BUILD" rm -f "$tmpf" - echo " [Critical-Files] $REMEDIATION_TAG vs gold:" - verify_critical_files "$tmpdir/appapp" runner + echo " [Critical-Files] $REMEDIATION_TAG vs gold (image-only, autorisierte Runner-Divergenz erlaubt):" + verify_critical_files_image_only "$tmpdir/appapp" runner rm -rf "$tmpdir" echo " [Runner] Image == autorisierter Overlay (6cca3c55...):" local ir; ir="$(docker run --rm --entrypoint /bin/sh "$imgid" -c "sha256sum /app/app/run_backfill_year.py 2>/dev/null" | awk '{print $1}')" || ir=""