#!/usr/bin/env bash
# 20-verify-offline.sh — run on the AIR-GAPPED host AFTER 10-load.sh.
#
# End-to-end offline proof: starts the deliberately-vulnerable demo app in a
# container, runs a ZAP baseline scan against it over a private container
# network (no internet), and asserts that ZAP produced a report containing the
# expected findings — all with add-on auto-update disabled.
#
#   ./20-verify-offline.sh [runtime]     # docker | podman (default: autodetect)
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
source "$ROOT/config/versions.env"

RUNTIME="${1:-}"
if [[ -z "$RUNTIME" ]]; then
  if command -v docker >/dev/null; then RUNTIME=docker; else RUNTIME=podman; fi
fi

NET="devsecops-zap-verify"
APP="zap-verify-demo"
REPORTS="$ROOT/evidence/verify-$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p "$REPORTS"
# The in-container 'zap' user (uid 1000) must be able to write reports into the
# bind-mounted dir. Make it group/other-writable so this works under both
# rootful and rootless runtimes without assuming a uid mapping.
chmod 0777 "$REPORTS"
cp "$ROOT/config/zap-baseline.conf" "$REPORTS/rules.conf"

cleanup() {
  "$RUNTIME" rm -f "$APP" >/dev/null 2>&1 || true
  "$RUNTIME" network rm "$NET" >/dev/null 2>&1 || true
}
trap cleanup EXIT

echo "== 1/4 private network with NO internet gateway =="
"$RUNTIME" network rm "$NET" >/dev/null 2>&1 || true
"$RUNTIME" network create --internal "$NET"

echo "== 2/4 start vulnerable demo app (python from the ZAP image itself) =="
"$RUNTIME" run -d --name "$APP" --network "$NET" \
  -v "$ROOT/demo/app.py:/app.py:ro,Z" \
  --entrypoint python3 \
  "$ZAP_IMAGE_LOCAL" /app.py 8080 >/dev/null
sleep 3
"$RUNTIME" ps --filter "name=$APP" --format '  {{.Names}} {{.Status}}'

echo "== 3/4 ZAP baseline scan over the internal network (offline) =="
status=0
"$RUNTIME" run --rm --network "$NET" --pull=never \
  -v "$REPORTS:/zap/wrk:Z" \
  "$ZAP_IMAGE_LOCAL" \
  zap-baseline.py -t "http://$APP:8080" \
    -r verify.html -J verify.json -x verify.xml \
    -c rules.conf \
    -z '-silent -config autoupdate.checkOnStart=false -config autoupdate.checkAddonUpdates=false -config telemetry.enabled=false' \
  || status=$?

echo "== 4/4 assert the report is real =="
python3 - "$REPORTS/verify.json" "$status" <<'PY'
import json, sys
report, status = sys.argv[1], int(sys.argv[2])
data = json.load(open(report))
sites = data.get("site", [])
assert sites, "ZAP produced no scanned site — offline scan did not run"
alerts = sites[0].get("alerts", [])
assert alerts, "ZAP found no alerts against the vulnerable demo — scan is suspect"
print(f"  scanned {sites[0].get('@name')}: {len(alerts)} alert types")
for a in alerts[:12]:
    print(f"    [{a.get('riskcode')}] {a.get('alert')}")
# Baseline exit codes: 0 clean, 1 FAIL, 2 WARN, 3 error. The vulnerable demo
# must trip at least a WARN/FAIL; 3 (error) means the scan itself broke.
assert status in (1, 2), f"unexpected ZAP exit {status} (expected 1 or 2)"
print(f"  ZAP exit={status} (policy gate would BLOCK — correct for this demo)")
PY

echo
echo "PASS: OWASP ZAP ran a full DAST scan fully offline and the gate behaved correctly."
echo "Evidence: $REPORTS"
