Files
vm-cloudflare/scripts/waf-and-plan-invariants.sh
Vault Sovereign f0b8d962de
Some checks failed
WAF Intelligence Guardrail / waf-intel (push) Waiting to run
Cloudflare Registry Validation / validate-registry (push) Has been cancelled
chore: pre-migration snapshot
Layer0, MCP servers, Terraform consolidation
2025-12-27 01:52:27 +00:00

394 lines
12 KiB
Bash

#!/usr/bin/env bash
# ============================================================================
# WAF + PLAN INVARIANTS CHECKER
# ============================================================================
# Enforces security+plan gating invariants for VaultMesh Cloudflare IaC.
# Run from repo root: bash scripts/waf-and-plan-invariants.sh
#
# Exit codes:
# 0 = All invariants pass
# 1 = One or more invariants violated
#
# Governed by: RED-BOOK.md
# ============================================================================
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
NC='\033[0m'
echo "============================================"
echo " VaultMesh WAF + Plan Invariants Check"
echo "============================================"
echo ""
FAILED=0
echo "── 0. Toolchain Versions ──"
terraform version || true
python3 --version || true
python3 -m pip --version || true
python3 -m pytest --version || true
python3 -m mcp.waf_intelligence --version || true
echo ""
echo "── 1. WAF Intel Analyzer Regression ──"
if python3 -m pytest -q tests/test_waf_intelligence_analyzer.py; then
echo -e "${GREEN}${NC} 1.1 Analyzer regression test passed"
else
echo -e "${RED}${NC} 1.1 Analyzer regression test failed"
FAILED=1
fi
echo ""
echo "── 2. WAF Intel CLI Contract ──"
TMP_DIR="${TMPDIR:-/tmp}"
WAF_JSON_FILE="$(mktemp -p "$TMP_DIR" waf-intel.XXXXXX.json)"
if python3 -m mcp.waf_intelligence --file terraform/waf.tf --format json --limit 5 >"$WAF_JSON_FILE"; then
if python3 - "$WAF_JSON_FILE" <<'PY'
import json
import sys
path = sys.argv[1]
with open(path, "r", encoding="utf-8") as f:
payload = json.load(f)
insights = payload.get("insights")
if not isinstance(insights, list):
raise SystemExit("waf_intel: insights is not a list")
if insights:
raise SystemExit(f"waf_intel: expected 0 insights, got {len(insights)}")
print("ok")
PY
then
echo -e "${GREEN}${NC} 2.1 WAF Intel JSON parses and insights are empty"
else
echo -e "${RED}${NC} 2.1 WAF Intel JSON contract violated"
cat "$WAF_JSON_FILE"
FAILED=1
fi
else
echo -e "${RED}${NC} 2.1 WAF Intel CLI failed"
FAILED=1
fi
rm -f "$WAF_JSON_FILE"
echo ""
echo "── 3. Terraform Format + Validate + Plan Gates ──"
cd terraform
if terraform fmt -check -recursive >/dev/null 2>&1; then
echo -e "${GREEN}${NC} 3.1 Terraform formatting OK"
else
echo -e "${RED}${NC} 3.1 Terraform formatting required"
echo " Run: cd terraform && terraform fmt -recursive"
FAILED=1
fi
terraform init -backend=false -input=false >/dev/null 2>&1
if terraform validate -no-color >/dev/null 2>&1; then
echo -e "${GREEN}${NC} 3.2 Terraform validate OK"
else
echo -e "${RED}${NC} 3.2 Terraform validate failed"
terraform validate -no-color
FAILED=1
fi
PLAN_FREE_OUT="$(mktemp -p "$TMP_DIR" tf-plan-free.XXXXXX.out)"
PLAN_PRO_OUT="$(mktemp -p "$TMP_DIR" tf-plan-pro.XXXXXX.out)"
PLAN_FREE_JSON="$(mktemp -p "$TMP_DIR" tf-plan-free.XXXXXX.json)"
PLAN_PRO_JSON="$(mktemp -p "$TMP_DIR" tf-plan-pro.XXXXXX.json)"
rm -f "$PLAN_FREE_OUT" "$PLAN_PRO_OUT"
if terraform plan -no-color -input=false -lock=false -refresh=false -out="$PLAN_FREE_OUT" -var-file=assurance_free.tfvars >/dev/null; then
if terraform show -json "$PLAN_FREE_OUT" >"$PLAN_FREE_JSON"; then
if output="$(
python3 - "$PLAN_FREE_JSON" <<'PY'
import json
import sys
path = sys.argv[1]
try:
with open(path, "r", encoding="utf-8") as f:
payload = json.load(f)
except json.JSONDecodeError as e:
print(f"json parse error: {e}")
raise SystemExit(2)
resource_changes = payload.get("resource_changes")
planned_values = payload.get("planned_values")
if not isinstance(resource_changes, list) or not isinstance(planned_values, dict):
print("invalid plan json: missing resource_changes[] and/or planned_values{}")
raise SystemExit(2)
addresses = [
rc.get("address", "")
for rc in resource_changes
if isinstance(rc, dict) and isinstance(rc.get("address"), str)
]
managed_waf = sum(1 for a in addresses if a.startswith("cloudflare_ruleset.managed_waf["))
bot_mgmt = sum(1 for a in addresses if a.startswith("cloudflare_bot_management.domains["))
if managed_waf != 0 or bot_mgmt != 0:
print(f"expected managed_waf=0 bot_management=0, got managed_waf={managed_waf} bot_management={bot_mgmt}")
for addr in sorted(
a
for a in addresses
if a.startswith("cloudflare_ruleset.managed_waf[") or a.startswith("cloudflare_bot_management.domains[")
):
print(f"- {addr}")
raise SystemExit(2)
PY
)"; then
echo -e "${GREEN}${NC} 3.3 Free-plan gate OK (managed_waf=0 bot_management=0)"
else
echo -e "${RED}${NC} 3.3 Free-plan gate violated"
if [[ -n "${output:-}" ]]; then
echo "$output" | sed 's/^/ /'
fi
FAILED=1
fi
else
echo -e "${RED}${NC} 3.3 terraform show -json failed (free)"
FAILED=1
fi
else
echo -e "${RED}${NC} 3.3 Terraform plan failed (free)"
terraform show -no-color "$PLAN_FREE_OUT" 2>/dev/null || true
FAILED=1
fi
if terraform plan -no-color -input=false -lock=false -refresh=false -out="$PLAN_PRO_OUT" -var-file=assurance_pro.tfvars >/dev/null; then
if terraform show -json "$PLAN_PRO_OUT" >"$PLAN_PRO_JSON"; then
if output="$(
python3 - "$PLAN_PRO_JSON" <<'PY'
import json
import sys
path = sys.argv[1]
try:
with open(path, "r", encoding="utf-8") as f:
payload = json.load(f)
except json.JSONDecodeError as e:
print(f"json parse error: {e}")
raise SystemExit(2)
resource_changes = payload.get("resource_changes")
planned_values = payload.get("planned_values")
if not isinstance(resource_changes, list) or not isinstance(planned_values, dict):
print("invalid plan json: missing resource_changes[] and/or planned_values{}")
raise SystemExit(2)
addresses = [
rc.get("address", "")
for rc in resource_changes
if isinstance(rc, dict) and isinstance(rc.get("address"), str)
]
managed_waf = sum(1 for a in addresses if a.startswith("cloudflare_ruleset.managed_waf["))
bot_mgmt = sum(1 for a in addresses if a.startswith("cloudflare_bot_management.domains["))
if managed_waf != 1 or bot_mgmt != 1:
print("expected managed_waf=1 bot_management=1")
print(f"got managed_waf={managed_waf} bot_management={bot_mgmt}")
print("observed:")
for addr in sorted(
a
for a in addresses
if a.startswith("cloudflare_ruleset.managed_waf[") or a.startswith("cloudflare_bot_management.domains[")
):
print(f"- {addr}")
raise SystemExit(2)
PY
)"; then
echo -e "${GREEN}${NC} 3.4 Paid-plan gate OK (managed_waf=1 bot_management=1)"
else
echo -e "${RED}${NC} 3.4 Paid-plan gate violated"
if [[ -n "${output:-}" ]]; then
echo "$output" | sed 's/^/ /'
fi
FAILED=1
fi
else
echo -e "${RED}${NC} 3.4 terraform show -json failed (pro)"
FAILED=1
fi
else
echo -e "${RED}${NC} 3.4 Terraform plan failed (pro)"
terraform show -no-color "$PLAN_PRO_OUT" 2>/dev/null || true
FAILED=1
fi
PLAN_NEG_FREE_OUT="$(mktemp -p "$TMP_DIR" tf-plan-neg-free.XXXXXX.out)"
PLAN_NEG_PRO_OUT="$(mktemp -p "$TMP_DIR" tf-plan-neg-pro.XXXXXX.out)"
PLAN_NEG_FREE_JSON="$(mktemp -p "$TMP_DIR" tf-plan-neg-free.XXXXXX.json)"
PLAN_NEG_PRO_JSON="$(mktemp -p "$TMP_DIR" tf-plan-neg-pro.XXXXXX.json)"
rm -f "$PLAN_NEG_FREE_OUT" "$PLAN_NEG_PRO_OUT"
echo ""
echo "── 4. Negative Controls (Prove the gate bites) ──"
if terraform plan -no-color -input=false -lock=false -refresh=false -out="$PLAN_NEG_FREE_OUT" -var-file=assurance_negative_free_should_fail.tfvars >/dev/null; then
if terraform show -json "$PLAN_NEG_FREE_OUT" >"$PLAN_NEG_FREE_JSON"; then
if output="$(
python3 - "$PLAN_NEG_FREE_JSON" <<'PY'
import json
import sys
path = sys.argv[1]
try:
with open(path, "r", encoding="utf-8") as f:
payload = json.load(f)
except json.JSONDecodeError as e:
print(f"json parse error: {e}")
raise SystemExit(2)
resource_changes = payload.get("resource_changes")
planned_values = payload.get("planned_values")
if not isinstance(resource_changes, list) or not isinstance(planned_values, dict):
print("invalid plan json: missing resource_changes[] and/or planned_values{}")
raise SystemExit(2)
addresses = [
rc.get("address", "")
for rc in resource_changes
if isinstance(rc, dict) and isinstance(rc.get("address"), str)
]
managed_waf = sum(1 for a in addresses if a.startswith("cloudflare_ruleset.managed_waf["))
bot_mgmt = sum(1 for a in addresses if a.startswith("cloudflare_bot_management.domains["))
if managed_waf != 0 or bot_mgmt != 0:
print(f"expected managed_waf=0 bot_management=0, got managed_waf={managed_waf} bot_management={bot_mgmt}")
for addr in sorted(
a
for a in addresses
if a.startswith("cloudflare_ruleset.managed_waf[") or a.startswith("cloudflare_bot_management.domains[")
):
print(f"- {addr}")
raise SystemExit(2)
print("ok")
PY
)"; then
echo -e "${RED}${NC} 4.1 Negative free-plan control unexpectedly passed"
FAILED=1
else
if [[ "${output:-}" == *"expected managed_waf=0 bot_management=0"* ]]; then
echo -e "${GREEN}${NC} 4.1 Negative free-plan control failed as expected"
else
echo -e "${RED}${NC} 4.1 Negative free-plan control failed (unexpected error)"
if [[ -n "${output:-}" ]]; then
echo "$output" | sed 's/^/ /'
fi
FAILED=1
fi
fi
else
echo -e "${RED}${NC} 4.1 terraform show -json failed (negative free)"
FAILED=1
fi
else
echo -e "${RED}${NC} 4.1 Terraform plan failed (negative free)"
FAILED=1
fi
if terraform plan -no-color -input=false -lock=false -refresh=false -out="$PLAN_NEG_PRO_OUT" -var-file=assurance_negative_pro_should_fail.tfvars >/dev/null; then
if terraform show -json "$PLAN_NEG_PRO_OUT" >"$PLAN_NEG_PRO_JSON"; then
if output="$(
python3 - "$PLAN_NEG_PRO_JSON" <<'PY'
import json
import sys
path = sys.argv[1]
try:
with open(path, "r", encoding="utf-8") as f:
payload = json.load(f)
except json.JSONDecodeError as e:
print(f"json parse error: {e}")
raise SystemExit(2)
resource_changes = payload.get("resource_changes")
planned_values = payload.get("planned_values")
if not isinstance(resource_changes, list) or not isinstance(planned_values, dict):
print("invalid plan json: missing resource_changes[] and/or planned_values{}")
raise SystemExit(2)
addresses = [
rc.get("address", "")
for rc in resource_changes
if isinstance(rc, dict) and isinstance(rc.get("address"), str)
]
managed_waf = sum(1 for a in addresses if a.startswith("cloudflare_ruleset.managed_waf["))
bot_mgmt = sum(1 for a in addresses if a.startswith("cloudflare_bot_management.domains["))
if managed_waf != 1 or bot_mgmt != 1:
print("expected managed_waf=1 bot_management=1")
print(f"got managed_waf={managed_waf} bot_management={bot_mgmt}")
print("observed:")
for addr in sorted(
a
for a in addresses
if a.startswith("cloudflare_ruleset.managed_waf[") or a.startswith("cloudflare_bot_management.domains[")
):
print(f"- {addr}")
raise SystemExit(2)
print("ok")
PY
)"; then
echo -e "${RED}${NC} 4.2 Negative paid-plan control unexpectedly passed"
FAILED=1
else
if [[ "${output:-}" == *"expected managed_waf=1 bot_management=1"* ]]; then
echo -e "${GREEN}${NC} 4.2 Negative paid-plan control failed as expected"
else
echo -e "${RED}${NC} 4.2 Negative paid-plan control failed (unexpected error)"
if [[ -n "${output:-}" ]]; then
echo "$output" | sed 's/^/ /'
fi
FAILED=1
fi
fi
else
echo -e "${RED}${NC} 4.2 terraform show -json failed (negative pro)"
FAILED=1
fi
else
echo -e "${RED}${NC} 4.2 Terraform plan failed (negative pro)"
FAILED=1
fi
rm -f "$PLAN_FREE_OUT" "$PLAN_PRO_OUT" "$PLAN_FREE_JSON" "$PLAN_PRO_JSON" "$PLAN_NEG_FREE_OUT" "$PLAN_NEG_PRO_OUT" "$PLAN_NEG_FREE_JSON" "$PLAN_NEG_PRO_JSON"
cd "$REPO_ROOT"
echo ""
echo "============================================"
echo " Summary"
echo "============================================"
if [[ $FAILED -gt 0 ]]; then
echo -e "${RED}WAF + plan invariants violated. Fix before merging.${NC}"
exit 1
fi
echo -e "${GREEN}All WAF + plan invariants pass. ✓${NC}"
exit 0