#!/bin/bash # ----------------------------------------------------------------------------- # disaster-swap-endpoint.sh # Sprint: SPRINT_20260125_003_Attestor_trust_workflows_conformance # Task: WORKFLOW-003 - Create disaster endpoint swap script # Description: Emergency endpoint swap via TUF (no client reconfiguration) # ----------------------------------------------------------------------------- set -euo pipefail RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } log_step() { echo -e "${BLUE}[STEP]${NC} $1"; } usage() { echo "Usage: $0 --repo --new-rekor-url [options]" echo "" echo "Emergency endpoint swap via TUF update." echo "Clients will auto-discover new endpoints without reconfiguration." echo "" echo "Options:" echo " --repo DIR TUF repository directory (required)" echo " --new-rekor-url URL New Rekor URL (required)" echo " --new-fulcio-url URL New Fulcio URL (optional)" echo " --note TEXT Note explaining the change" echo " --version N New service map version (auto-increment if not specified)" echo " -h, --help Show this help message" echo "" echo "Example:" echo " $0 --repo /path/to/tuf \\" echo " --new-rekor-url https://rekor-mirror.internal:8080 \\" echo " --note 'Emergency: Production Rekor outage'" echo "" echo "IMPORTANT: This changes where ALL clients send requests!" exit 1 } REPO_DIR="" NEW_REKOR_URL="" NEW_FULCIO_URL="" NOTE="" VERSION="" while [[ $# -gt 0 ]]; do case $1 in --repo) REPO_DIR="$2"; shift 2 ;; --new-rekor-url) NEW_REKOR_URL="$2"; shift 2 ;; --new-fulcio-url) NEW_FULCIO_URL="$2"; shift 2 ;; --note) NOTE="$2"; shift 2 ;; --version) VERSION="$2"; shift 2 ;; -h|--help) usage ;; *) log_error "Unknown argument: $1"; usage ;; esac done if [[ -z "$REPO_DIR" ]] || [[ -z "$NEW_REKOR_URL" ]]; then log_error "--repo and --new-rekor-url are required" usage fi if [[ ! -d "$REPO_DIR" ]]; then log_error "TUF repository not found: $REPO_DIR" exit 1 fi echo "" echo "================================================" echo -e "${RED} EMERGENCY ENDPOINT SWAP${NC}" echo "================================================" echo "" log_warn "This will redirect ALL clients to new endpoints!" echo "" log_info "TUF Repository: $REPO_DIR" log_info "New Rekor URL: $NEW_REKOR_URL" if [[ -n "$NEW_FULCIO_URL" ]]; then log_info "New Fulcio URL: $NEW_FULCIO_URL" fi if [[ -n "$NOTE" ]]; then log_info "Note: $NOTE" fi echo "" read -p "Type 'SWAP' to confirm endpoint change: " CONFIRM if [[ "$CONFIRM" != "SWAP" ]]; then log_error "Aborted" exit 1 fi # Find current service map CURRENT_MAP=$(ls "$REPO_DIR/targets/" 2>/dev/null | grep -E '^sigstore-services-v[0-9]+\.json$' | sort -V | tail -1 || echo "") if [[ -z "$CURRENT_MAP" ]]; then log_error "No service map found in $REPO_DIR/targets/" exit 1 fi CURRENT_PATH="$REPO_DIR/targets/$CURRENT_MAP" log_info "Current service map: $CURRENT_MAP" # Determine new version if [[ -z "$VERSION" ]]; then CURRENT_VERSION=$(echo "$CURRENT_MAP" | grep -oE '[0-9]+' | tail -1) VERSION=$((CURRENT_VERSION + 1)) fi NEW_MAP="sigstore-services-v${VERSION}.json" NEW_PATH="$REPO_DIR/targets/$NEW_MAP" log_step "Creating new service map: $NEW_MAP" # Read current map and update if command -v python3 &>/dev/null; then python3 - "$CURRENT_PATH" "$NEW_PATH" "$NEW_REKOR_URL" "$NEW_FULCIO_URL" "$NOTE" "$VERSION" << 'PYTHON_SCRIPT' import json import sys from datetime import datetime current_path = sys.argv[1] new_path = sys.argv[2] new_rekor_url = sys.argv[3] new_fulcio_url = sys.argv[4] if len(sys.argv) > 4 and sys.argv[4] else None note = sys.argv[5] if len(sys.argv) > 5 and sys.argv[5] else None version = int(sys.argv[6]) if len(sys.argv) > 6 else 1 with open(current_path) as f: data = json.load(f) # Update endpoints data['version'] = version data['rekor']['url'] = new_rekor_url if new_fulcio_url and 'fulcio' in data: data['fulcio']['url'] = new_fulcio_url # Update metadata if 'metadata' not in data: data['metadata'] = {} data['metadata']['updated_at'] = datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ') if note: data['metadata']['note'] = note with open(new_path, 'w') as f: json.dump(data, f, indent=2) print(f"Created: {new_path}") PYTHON_SCRIPT else # Fallback: simple JSON creation cat > "$NEW_PATH" << EOF { "version": $VERSION, "rekor": { "url": "$NEW_REKOR_URL" }, "metadata": { "updated_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)", "note": "$NOTE" } } EOF fi log_info "New service map created: $NEW_PATH" # Add to targets log_step "Adding new service map to TUF targets..." if [[ -x "$REPO_DIR/scripts/add-target.sh" ]]; then "$REPO_DIR/scripts/add-target.sh" "$NEW_PATH" "$NEW_MAP" --repo "$REPO_DIR" fi echo "" echo "================================================" echo -e "${GREEN} Endpoint Swap Prepared${NC}" echo "================================================" echo "" log_warn "NEXT STEPS (REQUIRED):" echo " 1. Review the new service map: cat $NEW_PATH" echo " 2. Sign the updated targets.json with targets key" echo " 3. Update snapshot.json and sign with snapshot key" echo " 4. Update timestamp.json and sign with timestamp key" echo " 5. Deploy updated metadata to TUF server" echo "" log_info "Clients will auto-discover the new endpoint within their refresh interval." log_info "For immediate effect, clients can run: stella trust sync --force" echo "" log_warn "Monitor client traffic to ensure failover is working!" echo ""