From bb03d1d03d28a728f5125620cd778022573a12d4 Mon Sep 17 00:00:00 2001 From: Guy Date: Wed, 6 May 2026 21:24:56 +0000 Subject: [PATCH 1/2] Improve sidecar gateway error diagnostics --- sidecar/src/bridge.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/sidecar/src/bridge.rs b/sidecar/src/bridge.rs index 17f16be..bb57c52 100644 --- a/sidecar/src/bridge.rs +++ b/sidecar/src/bridge.rs @@ -288,6 +288,7 @@ async fn handle( dest_authority = %authority, gateway_endpoint = %connector.gateway_endpoint(), error = %e, + error_chain = %format_args!("{e:#}"), "gateway connect failed" ); return response(StatusCode::BAD_GATEWAY, "gateway unreachable"); From 876152010a5fe80e5de15491c0d2ce3d50edb6d8 Mon Sep 17 00:00:00 2001 From: Peter Wagstaff Date: Thu, 7 May 2026 13:23:37 -0700 Subject: [PATCH 2/2] Add dashboard integration and demo improvements - Add dashboard service and otel-collector to docker-compose.demo.yml - Add extra_hosts for host.docker.internal on dashboard container (Linux) - Extract enroll()/clear_enrollment() in setup.sh; add --reset flag for full re-enrollment without restarting infrastructure - Start background reset server (port 8765) so dashboard Reset button triggers complete re-enrollment - Replace per-agent CLAUDE.md with hardcoded --system-prompt in connect.sh - Consolidate agent.requested + agent.prompted into single agent.prompted event carrying both user.id and agent.id - Emit agent.created event to dashboard on agent provisioning - Remove verification prompt from enrollment --- .gitignore | 4 + demo/connect.sh | 17 +++- demo/demo-agent.sh | 15 ++++ demo/setup.sh | 169 +++++++++++++++++++++++++--------------- docker-compose.demo.yml | 26 +++++++ 5 files changed, 164 insertions(+), 67 deletions(-) diff --git a/.gitignore b/.gitignore index 6fb8237..932581e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ /target .cursor/ __pycache__/ +/agent-gateway-dashboard/ +.run/ +/certs/ +/config.toml diff --git a/demo/connect.sh b/demo/connect.sh index 993bfa2..a875604 100755 --- a/demo/connect.sh +++ b/demo/connect.sh @@ -206,19 +206,30 @@ run_prompt() { PROXY_URL="$(<"$proxy_file")" WORK_DIR="${WORK_DIR:-$STATE_DIR/work}" mkdir -p "$WORK_DIR" - CLAUDE_CURL_PERMISSIONS=( + CLAUDE_ARGS=( --allowedTools "Bash(curl *)" + --system-prompt "You have access to two internal services via HTTPS: + +- docstore (https://docstore) - document storage + - GET /health - health check + - GET /documents - list available documents + +- messaging (https://messaging) - internal messaging (read-only) + - GET /health - health check + - GET /messages - list recent messages + +Use the Bash tool with curl to interact with these services." ) if [[ -f "$STATE_DIR/claude_started" ]]; then ( cd "$WORK_DIR" - HTTP_PROXY="$PROXY_URL" HTTPS_PROXY="$PROXY_URL" CURL_CA_BUNDLE="${CURL_CA_BUNDLE:-}" SSL_CERT_FILE="${SSL_CERT_FILE:-}" claude "${CLAUDE_CURL_PERMISSIONS[@]}" -c -p "$PROMPT" + HTTP_PROXY="$PROXY_URL" HTTPS_PROXY="$PROXY_URL" CURL_CA_BUNDLE="${CURL_CA_BUNDLE:-}" SSL_CERT_FILE="${SSL_CERT_FILE:-}" claude "${CLAUDE_ARGS[@]}" -c -p "$PROMPT" ) else ( cd "$WORK_DIR" - HTTP_PROXY="$PROXY_URL" HTTPS_PROXY="$PROXY_URL" CURL_CA_BUNDLE="${CURL_CA_BUNDLE:-}" SSL_CERT_FILE="${SSL_CERT_FILE:-}" claude "${CLAUDE_CURL_PERMISSIONS[@]}" -p "$PROMPT" + HTTP_PROXY="$PROXY_URL" HTTPS_PROXY="$PROXY_URL" CURL_CA_BUNDLE="${CURL_CA_BUNDLE:-}" SSL_CERT_FILE="${SSL_CERT_FILE:-}" claude "${CLAUDE_ARGS[@]}" -p "$PROMPT" ) : > "$STATE_DIR/claude_started" fi diff --git a/demo/demo-agent.sh b/demo/demo-agent.sh index 0c0c6a6..c26d773 100755 --- a/demo/demo-agent.sh +++ b/demo/demo-agent.sh @@ -15,6 +15,7 @@ DEFAULT_SWTPM_PORT="${AGENT_GATEWAY_DEMO_SWTPM_PORT:-2321}" TPM2_PKCS11_STORE="${TPM2_PKCS11_STORE:-$HOME/.tpm2_pkcs11}" TOKEN_LABEL="${AGENT_GATEWAY_TPM_TOKEN_LABEL:-agent-gateway}" USER_PIN="${AGENT_GATEWAY_TPM_USER_PIN:-}" +DASHBOARD_URL="${AGENT_GATEWAY_DEMO_DASHBOARD_URL:-http://localhost:3000}" export TPM2_PKCS11_STORE usage() { @@ -229,6 +230,11 @@ cmd_create() { grant_permissions ensure_sidecar "$STATE_DIR_CURRENT" + curl -sf --max-time 2 -X POST "$DASHBOARD_URL/api/events" \ + -H 'Content-Type: application/json' \ + -d "{\"event_type\":\"agent.created\",\"source\":\"agent-platform\",\"attributes\":{\"user.id\":\"$PRINCIPAL\",\"agent.id\":\"$IDENTITY\"}}" \ + >/dev/null || true + echo "$HANDLE" } @@ -254,6 +260,14 @@ cmd_grant() { grant_permissions } +emit_prompt_events() { + local principal="$1" identity="$2" + curl -sf --max-time 2 -X POST "$DASHBOARD_URL/api/events" \ + -H 'Content-Type: application/json' \ + -d "{\"event_type\":\"agent.prompted\",\"source\":\"agent-platform\",\"attributes\":{\"user.id\":\"$principal\",\"agent.id\":\"$identity\"}}" \ + >/dev/null || true +} + cmd_prompt() { [[ $# -ge 1 ]] || { echo "error: prompt requires AGENT_HANDLE" >&2; usage; exit 2; } HANDLE="$1" @@ -271,6 +285,7 @@ cmd_prompt() { done [[ -n "$PROMPT" ]] || { echo "error: prompt requires --prompt" >&2; exit 2; } + emit_prompt_events "$PRINCIPAL" "$IDENTITY" ensure_sidecar "$STATE_DIR_CURRENT" [[ -f "$DEFAULT_MOCK_CA" ]] || { echo "error: mock service CA file not found: $DEFAULT_MOCK_CA" >&2 diff --git a/demo/setup.sh b/demo/setup.sh index f716d2d..e5246ba 100755 --- a/demo/setup.sh +++ b/demo/setup.sh @@ -20,7 +20,13 @@ GATEWAY="${AGENT_GATEWAY_DEMO_GATEWAY:-127.0.0.1:8443}" GATEWAY_CA="${AGENT_GATEWAY_DEMO_GATEWAY_CA:-$REPO_ROOT/certs/server-ca.pem}" MOCK_CA="${AGENT_GATEWAY_DEMO_MOCK_CA:-$REPO_ROOT/certs/mock-ca.pem}" SIDECAR_BIN="$REPO_ROOT/target/debug/agent_gateway_sidecar" -VERIFY_TIMEOUT_SECONDS="${AGENT_GATEWAY_DEMO_VERIFY_TIMEOUT_SECONDS:-120}" +RESET_SERVER_PORT="${AGENT_GATEWAY_DEMO_RESET_SERVER_PORT:-8765}" +PIDS_DIR="$REPO_ROOT/.run" + +RESET_MODE=false +if [[ "${1:-}" == "--reset" ]]; then + RESET_MODE=true +fi export COMPOSE_PROJECT_NAME @@ -95,44 +101,107 @@ state_dir() { printf '%s/%s\n' "$STATE_ROOT" "$HANDLE" } -print_verification_diagnostics() { - local sidecar_log - sidecar_log="$(state_dir)/sidecar.log" +clear_enrollment() { + echo "==> Stopping sidecar" + local sidecar_pid_file + sidecar_pid_file="$(state_dir)/sidecar_pid" + if [[ -f "$sidecar_pid_file" ]]; then + local pid + pid="$(<"$sidecar_pid_file")" + kill "$pid" 2>/dev/null || true + fi + + echo "==> Clearing agent state and TPM store" + rm -rf "$STATE_ROOT" "$TPM2_PKCS11_STORE" + + echo "==> Clearing enrollment records from database" + psql "$DATABASE_URL" -v ON_ERROR_STOP=1 <<'SQL' +DELETE FROM permission_registry; +DELETE FROM principal_key_permissions; +DELETE FROM principal_signing_keys; +SQL +} + +enroll() { + demo_env=( + "AGENT_GATEWAY_DATABASE_URL=$DATABASE_URL" + "AGENT_GATEWAY_DEMO_GATEWAY=$GATEWAY" + "AGENT_GATEWAY_DEMO_GATEWAY_CA=$GATEWAY_CA" + "AGENT_GATEWAY_DEMO_MOCK_CA=$MOCK_CA" + "AGENT_GATEWAY_DEMO_SIDECAR_BIN=$SIDECAR_BIN" + "AGENT_GATEWAY_DEMO_STATE_DIR=$STATE_ROOT" + "AGENT_GATEWAY_TPM_USER_PIN=$USER_PIN" + "AGENT_GATEWAY_TPM_SO_PIN=$SO_PIN" + "AGENT_GATEWAY_RESET_TPM_STORE=false" + "CLAUDE_CODE_PROXY_RESOLVES_HOSTS=1" + "CURL_CA_BUNDLE=$MOCK_CA" + "NODE_EXTRA_CA_CERTS=$MOCK_CA" + "SSL_CERT_FILE=$MOCK_CA" + "TPM2_PKCS11_STORE=$TPM2_PKCS11_STORE" + "AGENT_GATEWAY_DEMO_DASHBOARD_URL=http://localhost:3000" + ) + + echo "==> Registering demo principal $PRINCIPAL" + env "${demo_env[@]}" "$REPO_ROOT/registry-cli/register-principal-key.sh" "$PRINCIPAL" + + echo "==> Granting demo scopes" + env "${demo_env[@]}" "$REPO_ROOT/registry-cli/grant-principal-scope.sh" "$PRINCIPAL" docstore messaging api.anthropic.com + + echo "==> Creating demo agent $HANDLE" + env "${demo_env[@]}" "$SCRIPT_DIR/demo-agent.sh" create \ + --identity "$IDENTITY" \ + --handle "$HANDLE" \ + --grant docstore \ + --grant api.anthropic.com >/dev/null +} + +start_reset_server() { + mkdir -p "$PIDS_DIR" - echo "Check the gateway, sidecar, and mock service logs before retrying." >&2 - echo "Sidecar log: $sidecar_log" >&2 - if [[ -f "$sidecar_log" ]]; then - echo >&2 - echo "Recent sidecar log lines:" >&2 - tail -n 80 "$sidecar_log" >&2 || true + # Kill any existing reset server + if [[ -f "$PIDS_DIR/reset-server.pid" ]]; then + kill "$(<"$PIDS_DIR/reset-server.pid")" 2>/dev/null || true + rm -f "$PIDS_DIR/reset-server.pid" fi + + local setup_script="$SCRIPT_DIR/setup.sh" + nohup node -e " + const http = require('http'); + const { spawn } = require('child_process'); + http.createServer((req, res) => { + if (req.method !== 'POST' || new URL(req.url, 'http://x').pathname !== '/reset') { + res.writeHead(404); res.end(); return; + } + const proc = spawn('bash', ['$setup_script', '--reset'], { stdio: 'inherit' }); + proc.on('exit', code => { + res.writeHead(code === 0 ? 200 : 500, {'Content-Type': 'application/json'}); + res.end(JSON.stringify({ ok: code === 0 })); + }); + }).listen($RESET_SERVER_PORT, '0.0.0.0', () => { + process.stdout.write('Reset server listening on $RESET_SERVER_PORT\n'); + }); + " > "$PIDS_DIR/reset-server.log" 2>&1 & + echo $! > "$PIDS_DIR/reset-server.pid" + echo "==> Reset server started on port $RESET_SERVER_PORT" } -demo_env=( - "AGENT_GATEWAY_DATABASE_URL=$DATABASE_URL" - "AGENT_GATEWAY_DEMO_GATEWAY=$GATEWAY" - "AGENT_GATEWAY_DEMO_GATEWAY_CA=$GATEWAY_CA" - "AGENT_GATEWAY_DEMO_MOCK_CA=$MOCK_CA" - "AGENT_GATEWAY_DEMO_SIDECAR_BIN=$SIDECAR_BIN" - "AGENT_GATEWAY_DEMO_STATE_DIR=$STATE_ROOT" - "AGENT_GATEWAY_TPM_USER_PIN=$USER_PIN" - "AGENT_GATEWAY_TPM_SO_PIN=$SO_PIN" - "AGENT_GATEWAY_RESET_TPM_STORE=false" - "CLAUDE_CODE_PROXY_RESOLVES_HOSTS=1" - "CURL_CA_BUNDLE=$MOCK_CA" - "NODE_EXTRA_CA_CERTS=$MOCK_CA" - "SSL_CERT_FILE=$MOCK_CA" - "TPM2_PKCS11_STORE=$TPM2_PKCS11_STORE" -) +# ── Main ────────────────────────────────────────────────────────────────────── select_compose require_cmd cargo require_cmd openssl require_cmd psql -require_cmd timeout +require_cmd node cd "$REPO_ROOT" +if [[ "$RESET_MODE" == "true" ]]; then + clear_enrollment + enroll + echo "Reset complete." + exit 0 +fi + echo "==> Generating demo TLS certificates" "$SCRIPT_DIR/generate-server-certs.sh" @@ -140,6 +209,9 @@ if [[ ! -f "$REPO_ROOT/config.toml" ]]; then echo "==> Creating config.toml from config.example.toml" cp "$REPO_ROOT/config.example.toml" "$REPO_ROOT/config.toml" fi +# Ensure the gateway (running in Docker) sends OTLP to the collector service name, +# not localhost (which would be the gateway container itself). +sed -i 's|otlp_endpoint = "http://localhost:4317"|otlp_endpoint = "http://otel-collector:4317"|' "$REPO_ROOT/config.toml" echo "==> Building local sidecar" cargo build -p agent_gateway_sidecar @@ -152,49 +224,18 @@ compose up -d --force-recreate postgres mock-services wait_for_postgres apply_migrations -echo "==> Starting gateway" -compose up -d --force-recreate gateway - -echo "==> Registering demo principal $PRINCIPAL" -env "${demo_env[@]}" "$REPO_ROOT/registry-cli/register-principal-key.sh" "$PRINCIPAL" - -echo "==> Granting demo scopes" -env "${demo_env[@]}" "$REPO_ROOT/registry-cli/grant-principal-scope.sh" "$PRINCIPAL" docstore messaging api.anthropic.com - -echo "==> Creating demo agent $HANDLE" -env "${demo_env[@]}" "$SCRIPT_DIR/demo-agent.sh" create \ - --identity "$IDENTITY" \ - --handle "$HANDLE" \ - --grant docstore \ - --grant api.anthropic.com \ - --grant messaging >/dev/null - -echo "==> Verifying Claude Code HTTP requests through the gateway" -if ! timeout "$VERIFY_TIMEOUT_SECONDS" env "${demo_env[@]}" "$SCRIPT_DIR/demo-agent.sh" prompt "$HANDLE" --prompt \ - "Use the Bash tool to run exactly this command: curl -sS https://docstore/health -Return only the raw response body."; then - cat >&2 < Starting gateway, otel-collector, and dashboard" +compose up -d --force-recreate gateway otel-collector dashboard -rm -f "$(state_dir)/claude_started" +enroll +start_reset_server cat <