#!/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 <> "$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" <