CD/CD consolidation
This commit is contained in:
88
devops/tools/replay/verify-policy-sim-lock.sh
Normal file
88
devops/tools/replay/verify-policy-sim-lock.sh
Normal file
@@ -0,0 +1,88 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Offline verifier for policy-sim inputs lock (PS1–PS10 remediation).
|
||||
# Usage: verify-policy-sim-lock.sh lock.json --policy path --graph path --sbom path --time-anchor path --dataset path [--max-age-hours 24]
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 lock.json --policy <file> --graph <file> --sbom <file> --time-anchor <file> --dataset <file> [--max-age-hours <n>]" >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
[[ $# -lt 11 ]] && usage
|
||||
|
||||
lock=""
|
||||
policy=""
|
||||
graph=""
|
||||
sbom=""
|
||||
time_anchor=""
|
||||
dataset=""
|
||||
max_age_hours=0
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--policy) policy=${2:-}; shift ;;
|
||||
--graph) graph=${2:-}; shift ;;
|
||||
--sbom) sbom=${2:-}; shift ;;
|
||||
--time-anchor) time_anchor=${2:-}; shift ;;
|
||||
--dataset) dataset=${2:-}; shift ;;
|
||||
--max-age-hours) max_age_hours=${2:-0}; shift ;;
|
||||
*) if [[ -z "$lock" ]]; then lock=$1; else usage; fi ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
[[ -z "$lock" || -z "$policy" || -z "$graph" || -z "$sbom" || -z "$time_anchor" || -z "$dataset" ]] && usage
|
||||
|
||||
require() { command -v "$1" >/dev/null || { echo "$1 is required" >&2; exit 2; }; }
|
||||
require jq
|
||||
require sha256sum
|
||||
|
||||
calc_sha() { sha256sum "$1" | awk '{print $1}'; }
|
||||
|
||||
lock_policy=$(jq -r '.policyBundleSha256' "$lock")
|
||||
lock_graph=$(jq -r '.graphSha256' "$lock")
|
||||
lock_sbom=$(jq -r '.sbomSha256' "$lock")
|
||||
lock_anchor=$(jq -r '.timeAnchorSha256' "$lock")
|
||||
lock_dataset=$(jq -r '.datasetSha256' "$lock")
|
||||
lock_shadow=$(jq -r '.shadowIsolation' "$lock")
|
||||
lock_scopes=$(jq -r '.requiredScopes[]?' "$lock" | tr '\n' ' ')
|
||||
lock_generated=$(jq -r '.generatedAt' "$lock")
|
||||
|
||||
sha_ok() {
|
||||
[[ $1 =~ ^[A-Fa-f0-9]{64}$ ]]
|
||||
}
|
||||
|
||||
for h in "$lock_policy" "$lock_graph" "$lock_sbom" "$lock_anchor" "$lock_dataset"; do
|
||||
sha_ok "$h" || { echo "invalid digest format: $h" >&2; exit 3; }
|
||||
done
|
||||
|
||||
[[ "$lock_shadow" == "true" ]] || { echo "shadowIsolation must be true" >&2; exit 5; }
|
||||
if ! grep -qi "policy:simulate:shadow" <<< "$lock_scopes"; then
|
||||
echo "requiredScopes missing policy:simulate:shadow" >&2; exit 5;
|
||||
fi
|
||||
|
||||
[[ "$lock_policy" == "$(calc_sha "$policy")" ]] || { echo "policy digest mismatch" >&2; exit 3; }
|
||||
[[ "$lock_graph" == "$(calc_sha "$graph")" ]] || { echo "graph digest mismatch" >&2; exit 3; }
|
||||
[[ "$lock_sbom" == "$(calc_sha "$sbom")" ]] || { echo "sbom digest mismatch" >&2; exit 3; }
|
||||
[[ "$lock_anchor" == "$(calc_sha "$time_anchor")" ]] || { echo "time anchor digest mismatch" >&2; exit 3; }
|
||||
[[ "$lock_dataset" == "$(calc_sha "$dataset")" ]] || { echo "dataset digest mismatch" >&2; exit 3; }
|
||||
|
||||
if [[ $max_age_hours -gt 0 ]]; then
|
||||
now=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||
age_hours=$(python3 - <<'PY'
|
||||
import sys,datetime
|
||||
lock=sys.argv[1].replace('Z','+00:00')
|
||||
now=sys.argv[2].replace('Z','+00:00')
|
||||
l=datetime.datetime.fromisoformat(lock)
|
||||
n=datetime.datetime.fromisoformat(now)
|
||||
print((n-l).total_seconds()/3600)
|
||||
PY
|
||||
"$lock_generated" "$now")
|
||||
if (( $(printf '%.0f' "$age_hours") > max_age_hours )); then
|
||||
echo "lock stale: ${age_hours}h > ${max_age_hours}h" >&2
|
||||
exit 4
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "policy-sim lock verified (shadow mode enforced)."
|
||||
Reference in New Issue
Block a user