diff --git a/BUILD_WEEK.md b/BUILD_WEEK.md index f326ff0..fe4a845 100644 --- a/BUILD_WEEK.md +++ b/BUILD_WEEK.md @@ -43,6 +43,16 @@ Work started during the submission period includes: | Phase 4 | Guarded real-cluster demo automation and evidence collection | Issue #98 / PR #100 | | Phase 5 | Real-cluster agent and console lifecycle integration | Issue #101 / PR #102 | | Phase 6 | OpenAI-native reasoning, configuration, deployment, UI status, and documentation | Issue #103 / PR #104 | +| Phase 7 | One-command deterministic Argus, Phoenix, Sentinel, and SOG judge demo | Issue #110 | + +### Phase 7 — deterministic platform demo + +`make demo-platform` provides one guarded path through the complete product. It starts +or reuses the three consoles and their APIs, connects them to the shared SOG, publishes +seeded Argus replay evidence and a Phoenix simulator recovery outcome for the same +resource, and verifies the resulting cross-agent incident through Sentinel before +declaring the demo ready. `make demo-platform-dry-run` validates the environment without +starting processes, forwarding ports, publishing evidence, or changing cluster state. ### Phase 6 — OpenAI-native reasoning diff --git a/Makefile b/Makefile index e7b2e99..03709cf 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: help cluster-up cluster-down cluster-status deploy-cilium deploy-falco \ deploy-kyverno deploy-observability deploy-agent deploy-ui \ - setup-local dev-agent dev-ui demo-local demo-cluster demo-cluster-dry-run hubble-ui grafana-ui k9s \ - test test-agent test-ui test-cluster-demo simulate-threats clean + setup-local dev-agent dev-ui demo-local demo-platform demo-platform-dry-run demo-cluster demo-cluster-dry-run hubble-ui grafana-ui k9s \ + test test-agent test-ui test-cluster-demo test-platform-demo simulate-threats clean THREAT_COUNT ?= 10 THREAT_SCENARIO ?= mixed @@ -29,6 +29,8 @@ help: @echo " Application" @echo " make setup-local Install local backend and UI dependencies" @echo " make demo-local Start a populated cluster-free demo" + @echo " make demo-platform Start the full Argus + Phoenix + Sentinel judge demo" + @echo " make demo-platform-dry-run Validate the full platform demo without starting anything" @echo " make dev-agent Start only the backend on localhost:8000" @echo " make dev-ui Start only the console on localhost:5173" @echo " make deploy-agent Build and deploy AI agent" @@ -37,6 +39,7 @@ help: @echo " Utilities" @echo " make test Run agent tests and build the UI" @echo " make test-cluster-demo Test cluster-demo safety guards" + @echo " make test-platform-demo Test full-platform demo orchestration" @echo " make simulate-threats Generate randomized demo incidents" @echo " make hubble-ui Open Hubble network flow UI" @echo " make grafana-ui Port-forward Grafana to localhost:3000" @@ -112,6 +115,16 @@ dev-ui: demo-local: @bash scripts/demo-local.sh "$(THREAT_COUNT)" "$(THREAT_SCENARIO)" "$(THREAT_SEED)" +demo-platform: + @PHOENIX_ROOT="$${PHOENIX_ROOT:-$(abspath ../sentinel-stack/phoenix)}" \ + SENTINEL_ROOT="$${SENTINEL_ROOT:-$(abspath ../sentinel-stack/sentinel)}" \ + bash scripts/demo-platform.sh + +demo-platform-dry-run: + @PHOENIX_ROOT="$${PHOENIX_ROOT:-$(abspath ../sentinel-stack/phoenix)}" \ + SENTINEL_ROOT="$${SENTINEL_ROOT:-$(abspath ../sentinel-stack/sentinel)}" \ + DEMO_PLATFORM_DRY_RUN=true bash scripts/demo-platform.sh + demo-cluster: @DEMO_CLUSTER_CONTEXT="$(DEMO_CLUSTER_CONTEXT)" \ DEMO_NAMESPACE="$(or $(DEMO_NAMESPACE),argus-demo)" \ @@ -132,6 +145,9 @@ test-agent: test-ui: @npm --prefix ui run build +test-platform-demo: + @bash scripts/tests/test-demo-platform.sh + test-cluster-demo: @bash scripts/tests/test-demo-cluster.sh diff --git a/README.md b/README.md index 4fbb690..bf53839 100644 --- a/README.md +++ b/README.md @@ -47,13 +47,14 @@ Commit history: https://github.com/CodeBuildder/argus-k8s/commits/main ## Start here -Choose one path. Both finish at the same Argus console on -**http://127.0.0.1:5173**. +Choose one path. The first two focus on Argus; the platform path launches the complete +cross-agent judge experience. | Path | Use it when | Command | |---|---|---| | **Local synthetic** | You want the fastest judge/reviewer experience and do not have Kubernetes | `make demo-local` | | **Live k3s** | You have the three-node Argus cluster and want real Falco, Cilium, and Kyverno evidence | `make demo-cluster-dry-run`, then `make demo-cluster` | +| **Full platform** | You want Argus, Phoenix, Sentinel, and the SOG operating as one deterministic demo | `make demo-platform-dry-run`, then `make demo-platform` | ### Path A — local synthetic, no cluster required @@ -131,6 +132,59 @@ injects the real workloads, prints terminal evidence, and keeps the console avai at **http://127.0.0.1:5173** until `Ctrl-C`. Cleanup stops both local processes and deletes only the namespace created by that run. +### Path C — full deterministic platform demo + +Use this path for the complete judge story. It requires sibling Phoenix and Sentinel +checkouts plus the deployed `sentinel-platform` and `phoenix-system` services in the +selected Kubernetes context. Validate everything without starting processes, opening +port-forwards, or publishing evidence: + +```text +Projects/ +├── argus-k8s/ # run the command here +└── sentinel-stack/ + ├── phoenix/ + └── sentinel/ +``` + +Install each repository's local dependencies once: + +```bash +make setup-local +make -C ../sentinel-stack/sentinel setup-local +npm --prefix ../sentinel-stack/phoenix/dashboard install +``` + +Select the real three-node k3s context, then run the non-mutating preflight: + +```bash +kubectl config use-context argus +make demo-platform-dry-run +``` + +Then launch the complete experience: + +```bash +make demo-platform +``` + +The command starts or reuses all required APIs, port-forwards, and dashboards; publishes +one deterministic Argus security finding and one Phoenix recovery outcome against the +same resource; and refuses to report success until Sentinel exposes their correlated +incident with explicit `replayed` and `simulator` provenance. + +| Console | URL | What to show | +|---|---|---| +| Argus | **http://127.0.0.1:5173** | Security evidence and response | +| Phoenix | **http://127.0.0.1:5174** | Resilience outcome and recovery | +| Sentinel | **http://127.0.0.1:5175** | Unified correlated incident and fleet decision | + +Existing healthy services are reused. On `Ctrl-C`, the command stops only processes it +started. The default sibling paths are `../sentinel-stack/phoenix` and +`../sentinel-stack/sentinel`; override them with `PHOENIX_ROOT` and `SENTINEL_ROOT` when +your checkouts live elsewhere. This deterministic path does not inject live Chaos Mesh +faults; the existing live k3s path remains available for real runtime security evidence. + ## Part of the Sentinel multi-agent platform Argus is the security domain agent in a larger autonomous-infrastructure system. It diff --git a/scripts/demo-platform.sh b/scripts/demo-platform.sh new file mode 100755 index 0000000..9814a82 --- /dev/null +++ b/scripts/demo-platform.sh @@ -0,0 +1,174 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +argus_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +phoenix_root="${PHOENIX_ROOT:-$(cd "${argus_root}/../sentinel-stack/phoenix" 2>/dev/null && pwd)}" +sentinel_root="${SENTINEL_ROOT:-$(cd "${argus_root}/../sentinel-stack/sentinel" 2>/dev/null && pwd)}" +dry_run="${DEMO_PLATFORM_DRY_RUN:-false}" +exit_after_ready="${DEMO_PLATFORM_EXIT_AFTER_READY:-false}" +demo_context="${DEMO_PLATFORM_CONTEXT:-$(kubectl config current-context 2>/dev/null || true)}" +log_dir="$(mktemp -d "${TMPDIR:-/tmp}/sentinel-demo.XXXXXX")" +owned_pids=() + +cleanup() { + trap - INT TERM EXIT + if ((${#owned_pids[@]})); then + echo "" + echo "Stopping command-owned demo processes ..." + for owned_pid in "${owned_pids[@]}"; do + kill "${owned_pid}" 2>/dev/null || true + done + for owned_pid in "${owned_pids[@]}"; do + wait "${owned_pid}" 2>/dev/null || true + done + fi + echo "Demo stopped. Logs: ${log_dir}" +} +trap cleanup EXIT INT TERM + +fail() { echo "ERROR: $*" >&2; exit 1; } +require_command() { command -v "$1" >/dev/null 2>&1 || fail "Missing command: $1"; } +http_ok() { curl --fail --silent --max-time 2 "$1" >/dev/null 2>&1; } +port_in_use() { lsof -nP -iTCP:"$1" -sTCP:LISTEN >/dev/null 2>&1; } + +wait_for_url() { + local label="$1" url="$2" pid="${3:-}" attempt + for attempt in $(seq 1 40); do + if http_ok "${url}"; then return 0; fi + if [[ -n "${pid}" ]] && ! kill -0 "${pid}" 2>/dev/null; then + fail "${label} exited before becoming ready. See ${log_dir}." + fi + sleep 1 + done + fail "${label} did not become ready at ${url}. See ${log_dir}." +} + +start_forward() { + local label="$1" namespace="$2" service="$3" local_port="$4" remote_port="$5" health_url="$6" + if http_ok "${health_url}"; then + echo " reuse: ${label} (${health_url})" + return + fi + port_in_use "${local_port}" && fail "Port ${local_port} is occupied but does not serve ${label}." + echo " start: ${label} on 127.0.0.1:${local_port}" + kubectl --context "${demo_context}" -n "${namespace}" port-forward "svc/${service}" "${local_port}:${remote_port}" \ + >"${log_dir}/${label// /-}.log" 2>&1 & + local forward_pid=$! + owned_pids+=("${forward_pid}") + wait_for_url "${label}" "${health_url}" "${forward_pid}" +} + +start_service() { + local label="$1" port="$2" health_url="$3" workdir="$4" + shift 4 + if http_ok "${health_url}"; then + echo " reuse: ${label} (${health_url})" + return + fi + port_in_use "${port}" && fail "Port ${port} is occupied but ${label} is not healthy." + echo " start: ${label} on 127.0.0.1:${port}" + (cd "${workdir}" && exec "$@") >"${log_dir}/${label// /-}.log" 2>&1 & + local service_pid=$! + owned_pids+=("${service_pid}") + wait_for_url "${label}" "${health_url}" "${service_pid}" +} + +echo "Sentinel platform deterministic demo" +echo " Context: ${demo_context:-}" +echo " Argus: ${argus_root}" +echo " Phoenix: ${phoenix_root}" +echo " Sentinel: ${sentinel_root}" +echo " Mode: replayed Argus evidence + Phoenix simulator (no live Chaos Mesh)" + +for required in curl jq kubectl lsof npm python3; do require_command "${required}"; done +[[ -n "${demo_context}" ]] || fail "No Kubernetes context is selected." +[[ -d "${phoenix_root}/dashboard" ]] || fail "Phoenix checkout not found. Set PHOENIX_ROOT." +[[ -d "${sentinel_root}/dashboard" ]] || fail "Sentinel checkout not found. Set SENTINEL_ROOT." +[[ -x "${argus_root}/.venv/bin/python" ]] || fail "Argus environment missing. Run: make setup-local" +[[ -d "${argus_root}/ui/node_modules" ]] || fail "Argus UI dependencies missing. Run: make setup-local" +[[ -x "${sentinel_root}/.venv/bin/python" ]] || fail "Sentinel environment missing. Run: make -C ${sentinel_root} setup-local" +[[ -d "${sentinel_root}/dashboard/node_modules" ]] || fail "Sentinel dashboard dependencies are missing." +[[ -d "${phoenix_root}/dashboard/node_modules" ]] || fail "Phoenix dashboard dependencies are missing." + +kubectl --context "${demo_context}" get --raw=/readyz >/dev/null +kubectl --context "${demo_context}" -n sentinel-platform get svc sentinel-world-model >/dev/null +for phoenix_service in phoenix-graph phoenix-chaos phoenix-faultlib phoenix-sim phoenix-agent; do + kubectl --context "${demo_context}" -n phoenix-system get svc "${phoenix_service}" >/dev/null +done + +if [[ "${dry_run}" == "true" ]]; then + echo "Preflight passed. No processes, port-forwards, findings, or cluster resources were created." + exit 0 +fi + +if [[ -f "${argus_root}/.env" ]]; then + set -a + # shellcheck disable=SC1091 + source "${argus_root}/.env" + set +a +fi + +echo "==> Connecting shared services" +start_forward "sog" sentinel-platform sentinel-world-model 8010 8000 http://127.0.0.1:8010/health +start_forward "phoenix-graph" phoenix-system phoenix-graph 8080 80 http://127.0.0.1:8080/health +start_forward "phoenix-faultlib" phoenix-system phoenix-faultlib 8081 80 http://127.0.0.1:8081/health +start_forward "phoenix-chaos" phoenix-system phoenix-chaos 8082 80 http://127.0.0.1:8082/health +start_forward "phoenix-sim" phoenix-system phoenix-sim 8083 80 http://127.0.0.1:8083/health +start_forward "phoenix-agent" phoenix-system phoenix-agent 8084 80 http://127.0.0.1:8084/health + +echo "==> Starting or reusing consoles" +start_service "argus-api" 8000 http://127.0.0.1:8000/health "${argus_root}/agent/src" \ + env ARGUS_LOCAL_DEMO=true IN_CLUSTER=false WORLD_MODEL_URL=http://127.0.0.1:8010 \ + "${argus_root}/.venv/bin/python" -m uvicorn main:app --host 127.0.0.1 --port 8000 +start_service "argus-ui" 5173 http://127.0.0.1:5173 "${argus_root}" \ + npm --prefix ui run dev -- --host 127.0.0.1 --port 5173 +start_service "phoenix-ui" 5174 http://127.0.0.1:5174 "${phoenix_root}" \ + env VITE_ARGUS_URL=http://127.0.0.1:5173 VITE_SENTINEL_URL=http://127.0.0.1:5175 \ + npm --prefix dashboard run dev -- --host 127.0.0.1 --port 5174 +start_service "sentinel-api" 8090 http://127.0.0.1:8090/health "${sentinel_root}" \ + env WORLD_MODEL_URL=http://127.0.0.1:8010 OPENAI_API_KEY="${OPENAI_API_KEY:-}" \ + "${sentinel_root}/.venv/bin/python" -m uvicorn main:app --app-dir backend/src --host 127.0.0.1 --port 8090 +start_service "sentinel-ui" 5175 http://127.0.0.1:5175 "${sentinel_root}" \ + env VITE_ARGUS_URL=http://127.0.0.1:5173 VITE_PHOENIX_URL=http://127.0.0.1:5174 \ + npm --prefix dashboard run dev -- --host 127.0.0.1 --port 5175 + +run_id="$(date -u +%Y%m%dT%H%M%SZ)-$$" +correlation_id="judge-demo-${run_id}" +timestamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +entity_id="service/phoenix-system/phoenix-sim" + +echo "==> Publishing deterministic correlated evidence" +curl --fail --silent --show-error -X POST http://127.0.0.1:8010/findings \ + -H "Content-Type: application/json" \ + -d "{\"event_id\":\"argus-${run_id}\",\"type\":\"finding\",\"source\":\"argus\",\"timestamp\":\"${timestamp}\",\"entity_id\":\"${entity_id}\",\"severity\":\"critical\",\"correlation_id\":\"${correlation_id}\",\"replayed\":true,\"payload\":{\"finding_type\":\"falco_alert\",\"rule\":\"Deterministic C2 Callback Proof\",\"description\":\"Replayed Argus evidence for the judge demo\",\"provenance\":\"replayed\",\"seed\":42}}" >/dev/null +curl --fail --silent --show-error -X POST http://127.0.0.1:8010/findings \ + -H "Content-Type: application/json" \ + -d "{\"event_id\":\"phoenix-${run_id}\",\"type\":\"finding\",\"source\":\"phoenix\",\"timestamp\":\"${timestamp}\",\"entity_id\":\"${entity_id}\",\"severity\":\"high\",\"correlation_id\":\"${correlation_id}\",\"payload\":{\"finding_type\":\"healing_action\",\"scenario_id\":\"sim-${run_id}\",\"outcome\":\"verified_recovery\",\"description\":\"Phoenix simulator verified service recovery\",\"provenance\":\"simulator\",\"domain\":\"simulator\",\"seed\":42}}" >/dev/null + +echo "==> Verifying the Sentinel incident" +verified=false +for _attempt in $(seq 1 45); do + if curl --fail --silent --max-time 25 http://127.0.0.1:8090/overview | jq -e --arg correlation_id "${correlation_id}" '.incidents[] | select(.correlation_id == $correlation_id and .sources == ["argus","phoenix"] and .provenance == ["replayed","simulator"])' >/dev/null; then + verified=true + break + fi + sleep 1 +done +[[ "${verified}" == "true" ]] || fail "Sentinel did not expose correlation ${correlation_id}." + +echo "" +echo "✅ Full platform demo is ready" +echo " Argus: http://127.0.0.1:5173" +echo " Phoenix: http://127.0.0.1:5174" +echo " Sentinel: http://127.0.0.1:5175" +echo " SOG API: http://127.0.0.1:8010/health" +echo " Evidence: ${correlation_id} (replayed + simulator, seed 42)" +echo "" +echo "Open Sentinel → Open Incidents → select the correlated lifecycle." +echo "Press Ctrl-C to stop only processes started by this command." + +if [[ "${exit_after_ready}" == "true" ]]; then + exit 0 +fi + +while true; do sleep 60; done diff --git a/scripts/tests/test-demo-platform.sh b/scripts/tests/test-demo-platform.sh new file mode 100755 index 0000000..3465238 --- /dev/null +++ b/scripts/tests/test-demo-platform.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +script="${repo_root}/scripts/demo-platform.sh" + +bash -n "${script}" + +grep -q 'DEMO_PLATFORM_DRY_RUN' "${script}" +grep -q 'DEMO_PLATFORM_EXIT_AFTER_READY' "${script}" +grep -q 'correlation_id="judge-demo-' "${script}" +grep -q 'provenance.*replayed' "${script}" +grep -q 'provenance.*simulator' "${script}" +grep -q 'Press Ctrl-C to stop only processes started by this command' "${script}" + +echo "demo-platform orchestration tests passed"