Phase 13.5: Authorized Remediation Divergence Verify Fix - verify-runner-remediation-image nutzt gemeinsame authorized_gold_delta-Policy (exakt 1 autorisierte Runner-Divergenz, alles andere FAIL CLOSED); Candidate-vs-Host-SoT direkter Vergleich; verify_critical_files runner-Flag
This commit is contained in:
parent
6194868100
commit
8187a4bc1d
1 changed files with 69 additions and 26 deletions
|
|
@ -526,10 +526,14 @@ gen_host_manifest() {
|
||||||
}
|
}
|
||||||
|
|
||||||
# read-only: Critical-Files-SHAs Recovery-Image + laufender Gold-Container + Gold-Baseline (Punkt 11)
|
# read-only: Critical-Files-SHAs Recovery-Image + laufender Gold-Container + Gold-Baseline (Punkt 11)
|
||||||
|
# $2 (optional) = "runner" -> erlaubt die EINE autorisierte Runner-Divergenz im IMAGE:
|
||||||
|
# run_backfill_year.py image=RUNNER_REMEDIATION_SHA (Container bleibt Gold).
|
||||||
|
# Nur fuer verify-runner-remediation-image; alle anderen Aufrufer bleiben strikt 0-Divergenz.
|
||||||
verify_critical_files() {
|
verify_critical_files() {
|
||||||
# $1 = Basisverzeichnis mit app/ (kopierter Recovery-Tree) ODER leer -> nur Container+Baseline
|
# $1 = Basisverzeichnis mit app/ (kopierter Recovery-Tree) ODER leer -> nur Container+Baseline
|
||||||
local tmpc tmpdir k path imgsha csha
|
local tmpc tmpdir k path imgsha csha
|
||||||
local base_dir="${1:-}"
|
local base_dir="${1:-}"
|
||||||
|
local allow_runner="${2:-}"
|
||||||
local img_shas=""
|
local img_shas=""
|
||||||
if [ -n "$base_dir" ]; then
|
if [ -n "$base_dir" ]; then
|
||||||
# Recovery-Image-SHAs aus kopiertem Tree. Der Tree ist prefixlos (docker cp
|
# Recovery-Image-SHAs aus kopiertem Tree. Der Tree ist prefixlos (docker cp
|
||||||
|
|
@ -549,6 +553,12 @@ verify_critical_files() {
|
||||||
is=$(echo "$img_shas" | grep -oE "${k}:[a-f0-9]+|${k}:MISSING" | cut -d: -f2)
|
is=$(echo "$img_shas" | grep -oE "${k}:[a-f0-9]+|${k}:MISSING" | cut -d: -f2)
|
||||||
img="$is"
|
img="$is"
|
||||||
fi
|
fi
|
||||||
|
# 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" ] \
|
||||||
|
&& [ "$img" = "$RUNNER_REMEDIATION_SHA" ] && [ "$csha" = "$gold" ]; then
|
||||||
|
printf ' AUTH %-22s image=%s container=%s gold=%s (autorisierte Runner-Divergenz)\n' "$k" "${img:0:12}" "${csha:0:12}" "${gold:0:12}"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
if [ "$csha" = "$gold" ] && { [ -z "$base_dir" ] || [ "$img" = "$gold" ]; }; then
|
if [ "$csha" = "$gold" ] && { [ -z "$base_dir" ] || [ "$img" = "$gold" ]; }; then
|
||||||
printf ' MATCH %-22s container=%s gold=%s\n' "$k" "${csha:0:12}" "${gold:0:12}"
|
printf ' MATCH %-22s container=%s gold=%s\n' "$k" "${csha:0:12}" "${gold:0:12}"
|
||||||
else
|
else
|
||||||
|
|
@ -1106,41 +1116,55 @@ remediation_guards() {
|
||||||
echo " [guard O] kein freier Parameter/User-Input:"
|
echo " [guard O] kein freier Parameter/User-Input:"
|
||||||
echo " OK (keine Argumente akzeptiert)"
|
echo " OK (keine Argumente akzeptiert)"
|
||||||
}
|
}
|
||||||
# Read-only Build-Input-Check: nur app/run_backfill_year.py darf vom Gold-Manifest
|
# ---- GEMEINSAME AUTHORIZED-GOLD-DELTA-POLICY (Phase 13.5) ----
|
||||||
# divergieren (und zwar exakt auf RUNNER_REMEDIATION_SHA). alles andere -> FAIL.
|
# Einzige autorisierte Divergenz gegen GOLD_MANIFEST:
|
||||||
verify_remediation_build_input() {
|
# app/run_backfill_year.py ALT=RUNNER_GOLD_SHA -> NEU=RUNNER_REMEDIATION_SHA
|
||||||
local hm gold d=0 m=0 x=0 mat=0 extra_ok=0 runner_seen=0
|
# (exakte Pfad- UND SHA-Bindung; KEINE generische "1 Divergenz ok"-Lockerung).
|
||||||
hm="$(mktemp)"; gen_host_manifest > "$hm"
|
# Setzt globale Zaehler AD_MATCH/AD_AUTH/AD_UNAUTH/AD_MISSING/AD_EXTRA und
|
||||||
|
# AD_RET (ok|fail). fail wenn UNAUTH>0 || MISSING>0 || EXTRA>0.
|
||||||
|
# Wird von verify_remediation_build_input UND verify_runner_remediation_image
|
||||||
|
# verwendet -> EINE Policy, keine unterschiedlichen Policies.
|
||||||
|
authorized_gold_delta() {
|
||||||
|
local new="$1" gold goldrel goldsha nsha nrel found
|
||||||
|
AD_MATCH=0; AD_AUTH=0; AD_UNAUTH=0; AD_MISSING=0; AD_EXTRA=0
|
||||||
while read -r goldsha goldrel; do
|
while read -r goldsha goldrel; do
|
||||||
goldrel="${goldrel#./}"; [ -n "$goldrel" ] || continue
|
goldrel="${goldrel#./}"; [ -n "$goldrel" ] || continue
|
||||||
local nsha
|
nsha="$(awk -v r="$goldrel" '$2==r{print $1}' "$new")"
|
||||||
nsha="$(awk -v r="$goldrel" '$2==r{print $1}' "$hm")"
|
if [ -z "$nsha" ]; then AD_MISSING=$((AD_MISSING+1))
|
||||||
if [ -z "$nsha" ]; then m=$((m+1))
|
elif [ "$nsha" = "$goldsha" ]; then AD_MATCH=$((AD_MATCH+1))
|
||||||
elif [ "$nsha" = "$goldsha" ]; then mat=$((mat+1))
|
|
||||||
else
|
else
|
||||||
if [ "$goldrel" = "app/run_backfill_year.py" ]; then
|
if [ "$goldrel" = "app/run_backfill_year.py" ] \
|
||||||
runner_seen=1
|
&& [ "$goldsha" = "$RUNNER_GOLD_SHA" ] \
|
||||||
if [ "$nsha" = "$RUNNER_REMEDIATION_SHA" ]; then extra_ok=1; else die "BUILD-INPUT FAIL: Runner divergiert auf unerwartete SHA $nsha"; fi
|
&& [ "$nsha" = "$RUNNER_REMEDIATION_SHA" ]; then
|
||||||
|
AD_AUTH=$((AD_AUTH+1))
|
||||||
else
|
else
|
||||||
echo " DIVERGED(UNERWARTET) $goldrel"; d=$((d+1))
|
AD_UNAUTH=$((AD_UNAUTH+1))
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done < "$GOLD_MANIFEST"
|
done < "$GOLD_MANIFEST"
|
||||||
while read -r nsha nrel; do
|
while read -r nsha nrel; do
|
||||||
nrel="${nrel#./}"
|
nrel="${nrel#./}"
|
||||||
local found
|
|
||||||
found="$(awk -v r="$nrel" '$2==r{print $1}' "$GOLD_MANIFEST")"
|
found="$(awk -v r="$nrel" '$2==r{print $1}' "$GOLD_MANIFEST")"
|
||||||
if [ -z "$found" ]; then
|
if [ -z "$found" ]; then
|
||||||
case "$nrel" in
|
case "$nrel" in
|
||||||
app/app/backfill.py|app/app/test_idempotency.py|app/eligibility.py) ;;
|
app/app/backfill.py|app/app/test_idempotency.py|app/eligibility.py) ;;
|
||||||
*) echo " EXTRA(UNERLAUBT) $nrel"; x=$((x+1)) ;;
|
*) AD_EXTRA=$((AD_EXTRA+1)) ;;
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
done < "$hm"
|
done < "$new"
|
||||||
|
AD_RET="ok"
|
||||||
|
[ $AD_UNAUTH -eq 0 ] && [ $AD_MISSING -eq 0 ] && [ $AD_EXTRA -eq 0 ] || AD_RET="fail"
|
||||||
|
}
|
||||||
|
# Read-only Build-Input-Check: nur app/run_backfill_year.py darf vom Gold-Manifest
|
||||||
|
# divergieren (und zwar exakt auf RUNNER_REMEDIATION_SHA). alles andere -> FAIL.
|
||||||
|
verify_remediation_build_input() {
|
||||||
|
local hm
|
||||||
|
hm="$(mktemp)"; gen_host_manifest > "$hm"
|
||||||
|
authorized_gold_delta "$hm"
|
||||||
rm -f "$hm"
|
rm -f "$hm"
|
||||||
echo " Gold $((mat+d+m)): MATCH=$mat DIVERGED=$d MISSING=$m ; unerlaubte EXTRA=$x ; autorisierte Runner-Divergenz=$extra_ok"
|
echo " Gold $((AD_MATCH+AD_AUTH+AD_UNAUTH+AD_MISSING)): MATCH=$AD_MATCH AUTHORIZED_DIVERGED=$AD_AUTH UNAUTHORIZED_DIVERGED=$AD_UNAUTH MISSING=$AD_MISSING ; unerlaubte EXTRA=$AD_EXTRA"
|
||||||
[ "$runner_seen" = "1" ] && [ "$extra_ok" = "1" ] || die "BUILD-INPUT FAIL: autorisierte Runner-Divergenz fehlt (FAIL CLOSED)"
|
[ "$AD_AUTH" = "1" ] || die "BUILD-INPUT FAIL: autorisierte Runner-Divergenz fehlt (FAIL CLOSED)"
|
||||||
[ $d -eq 0 ] && [ $m -eq 0 ] && [ $x -eq 0 ] || die "BUILD-INPUT FAIL: unerwartete Divergenz (FAIL CLOSED)"
|
[ "$AD_RET" = "ok" ] || die "BUILD-INPUT FAIL: unerwartete Divergenz (FAIL CLOSED)"
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
# ---- build-runner-remediation (DRY_RUN = kein Build; KEIN Overwrite) ----
|
# ---- build-runner-remediation (DRY_RUN = kein Build; KEIN Overwrite) ----
|
||||||
|
|
@ -1202,13 +1226,32 @@ verify_runner_remediation_image() {
|
||||||
tmpf="$(mktemp)"
|
tmpf="$(mktemp)"
|
||||||
( cd "$tmpdir/appapp" && find . -type f -name '*.py' ! -path '*/__pycache__/*' -printf '%P\n' 2>/dev/null | sort \
|
( 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"
|
| while IFS= read -r rel; do [ -f "$rel" ] && sha256sum "$rel"; done | awk '{print $1" app/"$2}' ) > "$tmpf"
|
||||||
classify_and_report "$tmpf"; local gret="$G_RET"; rm -f "$tmpf"
|
# [Gold-Delta] Image vs GOLD_MANIFEST: exakt 1 autorisierte Runner-Divergenz erlaubt.
|
||||||
if [ "$gret" != "ok" ]; then
|
authorized_gold_delta "$tmpf"
|
||||||
rm -rf "$tmpdir"; die "verify: Image != Gold-Manifest (unzulaessige Divergenz) (FAIL CLOSED)"
|
echo " Image vs GOLD: MATCH=$AD_MATCH AUTHORIZED_DIVERGED=$AD_AUTH UNAUTHORIZED_DIVERGED=$AD_UNAUTH MISSING=$AD_MISSING unerlaubte EXTRA=$AD_EXTRA"
|
||||||
fi
|
[ "$AD_AUTH" = "1" ] || { rm -rf "$tmpdir"; die "verify: Gold-Delta != exakt 1 autorisierte Runner-Divergenz (FAIL CLOSED)"; }
|
||||||
echo " Image vs GOLD (63 MATCH / 0 DIVERGED / 0 MISSING / 0 unerlaubte EXTRA)"
|
[ "$AD_RET" = "ok" ] || { rm -rf "$tmpdir"; die "verify: Image != Gold-Manifest (unzulaessige Divergenz) (FAIL CLOSED)"; }
|
||||||
|
# [Candidate vs Host-SoT] Image-App-Manifest == Host-SoT-App-Manifest (direkter
|
||||||
|
# Manifest-zu-Manifest-Vergleich, OHNE Gold-Bezug; vollstaendig MATCH).
|
||||||
|
# Host-only-Legacy-Dateien (im Image via .dockerignore ausgeschlossen) sind erlaubt.
|
||||||
|
local hmf cmiss cextra
|
||||||
|
hmf="$(mktemp)"; gen_host_manifest > "$hmf"
|
||||||
|
cmiss=0; cextra=0
|
||||||
|
while read -r hsha hrel; do
|
||||||
|
hrel="${hrel#./}"; [ -n "$hrel" ] || continue
|
||||||
|
case "$hrel" in
|
||||||
|
app/app/backfill.py|app/app/test_idempotency.py|app/eligibility.py) continue ;;
|
||||||
|
esac
|
||||||
|
isha="$(awk -v r="$hrel" '$2==r{print $1}' "$tmpf")"
|
||||||
|
if [ -z "$isha" ]; then cmiss=$((cmiss+1))
|
||||||
|
elif [ "$isha" != "$hsha" ]; then cextra=$((cextra+1)); fi
|
||||||
|
done < "$hmf"
|
||||||
|
rm -f "$hmf"
|
||||||
|
echo " Candidate vs Host-SoT: MATCH (direkt) MISSING=$cmiss DIVERGED=$cextra"
|
||||||
|
[ "$cmiss" = "0" ] && [ "$cextra" = "0" ] || { rm -rf "$tmpdir"; die "verify: Candidate != Host-SoT (FAIL CLOSED)"; }
|
||||||
|
rm -f "$tmpf"
|
||||||
echo " [Critical-Files] $REMEDIATION_TAG vs gold:"
|
echo " [Critical-Files] $REMEDIATION_TAG vs gold:"
|
||||||
verify_critical_files "$tmpdir/appapp"
|
verify_critical_files "$tmpdir/appapp" runner
|
||||||
rm -rf "$tmpdir"
|
rm -rf "$tmpdir"
|
||||||
echo " [Runner] Image vs Host-SoT persistent:"
|
echo " [Runner] Image vs Host-SoT persistent:"
|
||||||
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=""
|
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=""
|
||||||
|
|
@ -1230,7 +1273,7 @@ verify_runner_remediation_image() {
|
||||||
local srunner; srunner="$(grep '^HOST_SOT_RUNNER_SHA=' "$REMEDIATION_SESSION_META" | cut -d= -f2)"
|
local srunner; srunner="$(grep '^HOST_SOT_RUNNER_SHA=' "$REMEDIATION_SESSION_META" | cut -d= -f2)"
|
||||||
[ "$srunner" = "$RUNNER_REMEDIATION_SHA" ] || die "verify: Session-Runner != erwartet (FAIL CLOSED)"
|
[ "$srunner" = "$RUNNER_REMEDIATION_SHA" ] || die "verify: Session-Runner != erwartet (FAIL CLOSED)"
|
||||||
echo " OK ($srunner)"
|
echo " OK ($srunner)"
|
||||||
echo "VERDICT: REMEDIATION IMAGE OK (63 MATCH / 0 DIVERGED / 0 MISSING / 0 unerlaubte EXTRA; Runner=Host-SoT; Critical-SHAs + Config PASS)"
|
echo "VERDICT: REMEDIATION IMAGE OK (Candidate==Host-SoT; Gold-Delta exakt 1 autorisierte Runner-Divergenz; Critical-SHAs + Config PASS)"
|
||||||
exit 0
|
exit 0
|
||||||
}
|
}
|
||||||
# ---- recreate-runner-remediation-safe (NUR historical-service; nur Red Queen) ----
|
# ---- recreate-runner-remediation-safe (NUR historical-service; nur Red Queen) ----
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue