- Complete Cloudflare Terraform configuration (DNS, WAF, tunnels, access) - WAF Intelligence MCP server with threat analysis and ML classification - GitOps automation with PR workflows and drift detection - Observatory monitoring stack with Prometheus/Grafana - IDE operator rules for governed development - Security playbooks and compliance frameworks - Autonomous remediation and state reconciliation
210 lines
5.9 KiB
Bash
Executable File
210 lines
5.9 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# Cloudflare State Anchor
|
|
# Orchestrates state reconciliation, invariant checking, and ProofChain anchoring.
|
|
#
|
|
# Usage:
|
|
# ./anchor-cloudflare-state.sh [--zone-id ZONE_ID] [--account-id ACCOUNT_ID]
|
|
#
|
|
# Environment Variables:
|
|
# CLOUDFLARE_API_TOKEN - Required
|
|
# CLOUDFLARE_ZONE_ID - Zone ID (or use --zone-id)
|
|
# CLOUDFLARE_ACCOUNT_ID - Account ID (or use --account-id)
|
|
# VAULTMESH_ANCHORS_PATH - Path to ProofChain anchors file (optional)
|
|
#
|
|
# Exit Codes:
|
|
# 0 - Success, all invariants passed
|
|
# 1 - Success, but invariants failed (anomalies detected)
|
|
# 2 - Error during execution
|
|
|
|
set -euo pipefail
|
|
|
|
# Configuration
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
BASE_DIR="$(dirname "$SCRIPT_DIR")"
|
|
SNAPSHOTS_DIR="${BASE_DIR}/snapshots"
|
|
RECEIPTS_DIR="${BASE_DIR}/receipts"
|
|
ANOMALIES_DIR="${BASE_DIR}/anomalies"
|
|
ANCHORS_PATH="${VAULTMESH_ANCHORS_PATH:-${BASE_DIR}/proofchain-anchors.jsonl}"
|
|
|
|
# Parse arguments
|
|
ZONE_ID="${CLOUDFLARE_ZONE_ID:-}"
|
|
ACCOUNT_ID="${CLOUDFLARE_ACCOUNT_ID:-}"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case $1 in
|
|
--zone-id)
|
|
ZONE_ID="$2"
|
|
shift 2
|
|
;;
|
|
--account-id)
|
|
ACCOUNT_ID="$2"
|
|
shift 2
|
|
;;
|
|
*)
|
|
echo "Unknown argument: $1"
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Validate
|
|
if [[ -z "${CLOUDFLARE_API_TOKEN:-}" ]]; then
|
|
echo "Error: CLOUDFLARE_API_TOKEN environment variable required"
|
|
exit 2
|
|
fi
|
|
|
|
if [[ -z "$ZONE_ID" ]]; then
|
|
echo "Error: Zone ID required (--zone-id or CLOUDFLARE_ZONE_ID)"
|
|
exit 2
|
|
fi
|
|
|
|
if [[ -z "$ACCOUNT_ID" ]]; then
|
|
echo "Error: Account ID required (--account-id or CLOUDFLARE_ACCOUNT_ID)"
|
|
exit 2
|
|
fi
|
|
|
|
# Ensure directories exist
|
|
mkdir -p "$SNAPSHOTS_DIR" "$RECEIPTS_DIR" "$ANOMALIES_DIR"
|
|
|
|
# Timestamp for this run
|
|
TIMESTAMP=$(date -u +%Y-%m-%dT%H-%M-%SZ)
|
|
|
|
echo "======================================"
|
|
echo "Cloudflare State Anchor"
|
|
echo "======================================"
|
|
echo "Timestamp: $TIMESTAMP"
|
|
echo "Zone ID: $ZONE_ID"
|
|
echo "Account ID: $ACCOUNT_ID"
|
|
echo ""
|
|
|
|
# Step 1: Run State Reconciler
|
|
echo ">>> Step 1: Fetching Cloudflare state..."
|
|
python3 "${SCRIPT_DIR}/state-reconciler.py" \
|
|
--zone-id "$ZONE_ID" \
|
|
--account-id "$ACCOUNT_ID" \
|
|
--output-dir "$SNAPSHOTS_DIR" \
|
|
--receipt-dir "$RECEIPTS_DIR"
|
|
|
|
# Find the latest snapshot
|
|
LATEST_SNAPSHOT=$(ls -t "${SNAPSHOTS_DIR}"/cloudflare-*.json 2>/dev/null | head -1)
|
|
if [[ -z "$LATEST_SNAPSHOT" ]]; then
|
|
echo "Error: No snapshot found"
|
|
exit 2
|
|
fi
|
|
echo "Snapshot: $LATEST_SNAPSHOT"
|
|
|
|
# Extract Merkle root from snapshot
|
|
MERKLE_ROOT=$(python3 -c "
|
|
import json
|
|
with open('$LATEST_SNAPSHOT') as f:
|
|
data = json.load(f)
|
|
print(data['integrity']['merkle_root'])
|
|
")
|
|
echo "Merkle Root: $MERKLE_ROOT"
|
|
echo ""
|
|
|
|
# Step 2: Run Invariant Checker
|
|
echo ">>> Step 2: Checking invariants..."
|
|
INVARIANT_EXIT=0
|
|
python3 "${SCRIPT_DIR}/invariant-checker.py" \
|
|
--snapshot "$LATEST_SNAPSHOT" \
|
|
--output-dir "$ANOMALIES_DIR" || INVARIANT_EXIT=$?
|
|
|
|
# Find latest report
|
|
LATEST_REPORT=$(ls -t "${ANOMALIES_DIR}"/invariant-report-*.json 2>/dev/null | head -1)
|
|
echo "Invariant Report: $LATEST_REPORT"
|
|
echo ""
|
|
|
|
# Extract summary
|
|
if [[ -n "$LATEST_REPORT" ]]; then
|
|
PASSED=$(python3 -c "import json; print(json.load(open('$LATEST_REPORT'))['summary']['passed'])")
|
|
FAILED=$(python3 -c "import json; print(json.load(open('$LATEST_REPORT'))['summary']['failed'])")
|
|
echo "Passed: $PASSED"
|
|
echo "Failed: $FAILED"
|
|
fi
|
|
|
|
# Step 3: Create ProofChain Anchor
|
|
echo ""
|
|
echo ">>> Step 3: Creating ProofChain anchor..."
|
|
|
|
# Compute combined hash
|
|
COMBINED_HASH=$(cat "$LATEST_SNAPSHOT" "$LATEST_REPORT" 2>/dev/null | sha256sum | cut -d' ' -f1)
|
|
|
|
# Create anchor JSON
|
|
ANCHOR_JSON=$(cat <<EOF
|
|
{
|
|
"anchor_type": "cf_state_anchor",
|
|
"schema_version": "vm_anchor_v1",
|
|
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
"zone_id": "$ZONE_ID",
|
|
"account_id": "$ACCOUNT_ID",
|
|
"snapshot_path": "$LATEST_SNAPSHOT",
|
|
"report_path": "$LATEST_REPORT",
|
|
"merkle_root": "$MERKLE_ROOT",
|
|
"combined_hash": "$COMBINED_HASH",
|
|
"invariants_passed": $PASSED,
|
|
"invariants_failed": $FAILED,
|
|
"status": "$([ $INVARIANT_EXIT -eq 0 ] && echo 'clean' || echo 'anomalies_detected')"
|
|
}
|
|
EOF
|
|
)
|
|
|
|
# Append to anchors file
|
|
echo "$ANCHOR_JSON" >> "$ANCHORS_PATH"
|
|
echo "Anchor appended to: $ANCHORS_PATH"
|
|
|
|
# Step 4: Create combined receipt
|
|
echo ""
|
|
echo ">>> Step 4: Creating combined receipt..."
|
|
|
|
RECEIPT_PATH="${RECEIPTS_DIR}/cf-anchor-${TIMESTAMP}.json"
|
|
cat > "$RECEIPT_PATH" <<EOF
|
|
{
|
|
"receipt_type": "cf_state_anchor_complete",
|
|
"schema_version": "vm_cf_anchor_v1",
|
|
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
|
|
"zone_id": "$ZONE_ID",
|
|
"account_id": "$ACCOUNT_ID",
|
|
"artifacts": {
|
|
"snapshot": "$LATEST_SNAPSHOT",
|
|
"invariant_report": "$LATEST_REPORT",
|
|
"anchors_file": "$ANCHORS_PATH"
|
|
},
|
|
"integrity": {
|
|
"merkle_root": "$MERKLE_ROOT",
|
|
"combined_hash": "$COMBINED_HASH",
|
|
"hash_algorithm": "sha256"
|
|
},
|
|
"invariants": {
|
|
"passed": $PASSED,
|
|
"failed": $FAILED,
|
|
"status": "$([ $INVARIANT_EXIT -eq 0 ] && echo 'all_passed' || echo 'failures_detected')"
|
|
}
|
|
}
|
|
EOF
|
|
echo "Receipt: $RECEIPT_PATH"
|
|
|
|
# Summary
|
|
echo ""
|
|
echo "======================================"
|
|
echo "Anchor Complete"
|
|
echo "======================================"
|
|
echo "Snapshot: $LATEST_SNAPSHOT"
|
|
echo "Report: $LATEST_REPORT"
|
|
echo "Receipt: $RECEIPT_PATH"
|
|
echo "Merkle Root: $MERKLE_ROOT"
|
|
echo "Status: $([ $INVARIANT_EXIT -eq 0 ] && echo 'CLEAN' || echo 'ANOMALIES DETECTED')"
|
|
echo "======================================"
|
|
|
|
# Output for CI pipelines
|
|
echo ""
|
|
echo "CI_MERKLE_ROOT=$MERKLE_ROOT"
|
|
echo "CI_SNAPSHOT_PATH=$LATEST_SNAPSHOT"
|
|
echo "CI_REPORT_PATH=$LATEST_REPORT"
|
|
echo "CI_RECEIPT_PATH=$RECEIPT_PATH"
|
|
echo "CI_INVARIANTS_STATUS=$([ $INVARIANT_EXIT -eq 0 ] && echo 'passed' || echo 'failed')"
|
|
|
|
# Exit with invariant check result
|
|
exit $INVARIANT_EXIT
|