#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) cd "$SCRIPT_DIR" COMPOSE_FILE=${COMPOSE_FILE:-"$SCRIPT_DIR/sealed-mode-compose.yml"} PROJECT_NAME=${COMPOSE_PROJECT_NAME:-sealedmode} NETWORK_NAME="${PROJECT_NAME}_sealed-ci" ARTIFACT_ROOT=${ARTIFACT_ROOT:-"$SCRIPT_DIR/artifacts/sealed-mode-ci"} STAMP=$(date -u +"%Y%m%dT%H%M%SZ") OUT_DIR="$ARTIFACT_ROOT/$STAMP" mkdir -p "$OUT_DIR" log() { printf '[%s] %s\n' "$(date -u +%H:%M:%S)" "$*" } EXIT_CODE=0 IPTABLES_SNAPSHOT="" cleanup() { local exit_code=$? log "Collecting docker compose logs" docker compose -f "$COMPOSE_FILE" -p "$PROJECT_NAME" logs >"$OUT_DIR/compose.log" 2>&1 || true docker compose -f "$COMPOSE_FILE" -p "$PROJECT_NAME" ps -a >"$OUT_DIR/compose.ps" 2>&1 || true log "Tearing down sealed-mode stack" docker compose -f "$COMPOSE_FILE" -p "$PROJECT_NAME" down -v >"$OUT_DIR/docker-down.log" 2>&1 || true if [[ -n "$IPTABLES_SNAPSHOT" && -f "$IPTABLES_SNAPSHOT" ]]; then log "Restoring iptables snapshot" sudo iptables-restore <"$IPTABLES_SNAPSHOT" || true rm -f "$IPTABLES_SNAPSHOT" fi log "Artifacts stored at $OUT_DIR" exit $exit_code } trap cleanup EXIT log "Pulling compose images (best effort)" docker compose -f "$COMPOSE_FILE" -p "$PROJECT_NAME" pull --ignore-pull-failures || true log "Starting sealed-mode stack" docker compose -f "$COMPOSE_FILE" -p "$PROJECT_NAME" up -d --remove-orphans wait_for_port() { local port=$1 local label=$2 for attempt in $(seq 1 30); do if curl -fsS --max-time 5 "http://127.0.0.1:${port}/healthz" >/dev/null 2>&1; then log "$label responded on port $port" return 0 fi sleep 2 done log "$label failed to respond on port $port" return 1 } wait_for_port 5088 "Authority" || EXIT_CODE=1 wait_for_port 6088 "Signer" || EXIT_CODE=1 wait_for_port 7088 "Attestor" || EXIT_CODE=1 log "Fetching probe helper image" docker pull curlimages/curl:8.6.0 >/dev/null 2>&1 || true log "Snapshotting iptables state" IPTABLES_SNAPSHOT=$(mktemp) sudo iptables-save >"$IPTABLES_SNAPSHOT" log "Applying sealed-mode egress policy" CHAIN="STELLAOPS_SEALED" sudo iptables -N "$CHAIN" 2>/dev/null || sudo iptables -F "$CHAIN" for cidr in 127.0.0.0/8 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16; do sudo iptables -A "$CHAIN" -d "$cidr" -j RETURN done sudo iptables -A "$CHAIN" -j LOG --log-prefix "stellaops-sealed-deny " --log-level 4 sudo iptables -A "$CHAIN" -j DROP sudo iptables -I DOCKER-USER 1 -j "$CHAIN" sudo iptables -I OUTPUT 1 -j "$CHAIN" check_health() { local name=$1 local port=$2 local url="http://127.0.0.1:${port}/healthz" local log_file="$OUT_DIR/${name}.health.log" local status="fail" for attempt in $(seq 1 20); do if curl -fsS --max-time 5 "$url" >"$log_file" 2>&1; then status="pass" break fi sleep 2 done if [[ "$status" == "pass" ]]; then log "$name health check succeeded" else log "$name health check failed" EXIT_CODE=1 fi local upper upper=$(echo "$name" | tr '[:lower:]' '[:upper:]') eval "${upper}_HEALTH_STATUS=$status" eval "${upper}_HEALTH_URL=$url" } check_health authority 5088 check_health signer 6088 check_health attestor 7088 log "Running egress probe via docker network $NETWORK_NAME" EGRESS_JSON="$OUT_DIR/egress-probe.json" if python3 "$SCRIPT_DIR/egress_probe.py" --network "$NETWORK_NAME" --image curlimages/curl:8.6.0 --timeout 8 --output "$EGRESS_JSON"; then EGRESS_STATUS="pass" else EGRESS_STATUS="fail" EXIT_CODE=1 fi log "Dumping iptables counters" sudo iptables -v -x -L DOCKER-USER >"$OUT_DIR/iptables-docker-user.txt" sudo iptables -v -x -L OUTPUT >"$OUT_DIR/iptables-output.txt" log "Recording summary JSON" export PROJECT_NAME NETWORK_NAME EGRESS_STATUS EGRESS_JSON export AUTHORITY_HEALTH_STATUS SIGNER_HEALTH_STATUS ATTESTOR_HEALTH_STATUS export AUTHORITY_HEALTH_URL SIGNER_HEALTH_URL ATTESTOR_HEALTH_URL python3 - <<'PY' >"$OUT_DIR/authority-sealed-ci.json" import json import os import sys from datetime import datetime, timezone summary = { "timestamp": datetime.now(timezone.utc).isoformat(), "project": os.environ.get("PROJECT_NAME"), "network": os.environ.get("NETWORK_NAME"), "health": { "authority": { "status": os.environ.get("AUTHORITY_HEALTH_STATUS", "unknown"), "url": os.environ.get("AUTHORITY_HEALTH_URL"), "log": "authority.health.log", }, "signer": { "status": os.environ.get("SIGNER_HEALTH_STATUS", "unknown"), "url": os.environ.get("SIGNER_HEALTH_URL"), "log": "signer.health.log", }, "attestor": { "status": os.environ.get("ATTESTOR_HEALTH_STATUS", "unknown"), "url": os.environ.get("ATTESTOR_HEALTH_URL"), "log": "attestor.health.log", }, }, "egressProbe": { "status": os.environ.get("EGRESS_STATUS", "unknown"), "report": os.path.basename(os.environ.get("EGRESS_JSON", "egress-probe.json")), }, } json.dump(summary, sys.stdout, indent=2) print() PY if [[ $EXIT_CODE -eq 0 ]]; then log "Sealed-mode CI run completed successfully" else log "Sealed-mode CI run completed with failures" fi exit $EXIT_CODE