diff --git a/HOWTO.md b/HOWTO.md index 23227caa..2a5f6b2f 100644 --- a/HOWTO.md +++ b/HOWTO.md @@ -35,32 +35,66 @@ ClickHouse credentials: `cameleer` / `cameleer_dev`, database `cameleer3`. ```bash mvn clean package -DskipTests -java -jar cameleer3-server-app/target/cameleer3-server-app-1.0-SNAPSHOT.jar +CAMELEER_AUTH_TOKEN=my-secret-token java -jar cameleer3-server-app/target/cameleer3-server-app-1.0-SNAPSHOT.jar ``` -The server starts on **port 8081**. +The server starts on **port 8081**. The `CAMELEER_AUTH_TOKEN` environment variable is **required** — the server fails fast on startup if it is not set. + +For token rotation without downtime, set `CAMELEER_AUTH_TOKEN_PREVIOUS` to the old token while rolling out the new one. The server accepts both during the overlap window. ## API Endpoints +### Authentication (Phase 4) + +All endpoints except health, registration, and docs require a JWT Bearer token. The typical flow: + +```bash +# 1. Register agent (requires bootstrap token) +curl -s -X POST http://localhost:8081/api/v1/agents/register \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer my-secret-token" \ + -d '{"agentId":"agent-1","name":"Order Service","group":"order-service-prod","version":"1.0.0","routeIds":["route-1"],"capabilities":["deep-trace","replay"]}' +# Response includes: accessToken, refreshToken, serverPublicKey (Ed25519, Base64) + +# 2. Use access token for all subsequent requests +TOKEN="" + +# 3. Refresh when access token expires (1h default) +curl -s -X POST http://localhost:8081/api/v1/agents/agent-1/refresh \ + -H "Authorization: Bearer " +# Response: { "accessToken": "new-jwt" } +``` + +**Public endpoints (no JWT required):** `GET /api/v1/health`, `POST /api/v1/agents/register` (uses bootstrap token), OpenAPI/Swagger docs. + +**Protected endpoints (JWT required):** All other endpoints including ingestion, search, agent management, commands. + +**SSE connections:** Authenticated via query parameter: `/agents/{id}/events?token=` (EventSource API doesn't support custom headers). + +**Ed25519 signatures:** All SSE command payloads (config-update, deep-trace, replay) include a `signature` field. Agents verify payload integrity using the `serverPublicKey` received during registration. The server generates a new ephemeral keypair on each startup — agents must re-register to get the new key. + ### Ingestion (POST, returns 202 Accepted) ```bash -# Post route execution data +# Post route execution data (JWT required) curl -s -X POST http://localhost:8081/api/v1/data/executions \ -H "Content-Type: application/json" \ -H "X-Protocol-Version: 1" \ + -H "Authorization: Bearer $TOKEN" \ -d '{"agentId":"agent-1","routeId":"route-1","executionId":"exec-1","status":"COMPLETED","startTime":"2026-03-11T00:00:00Z","endTime":"2026-03-11T00:00:01Z","processorExecutions":[]}' # Post route diagram curl -s -X POST http://localhost:8081/api/v1/data/diagrams \ -H "Content-Type: application/json" \ -H "X-Protocol-Version: 1" \ + -H "Authorization: Bearer $TOKEN" \ -d '{"agentId":"agent-1","routeId":"route-1","version":1,"nodes":[],"edges":[]}' # Post agent metrics curl -s -X POST http://localhost:8081/api/v1/data/metrics \ -H "Content-Type: application/json" \ -H "X-Protocol-Version: 1" \ + -H "Authorization: Bearer $TOKEN" \ -d '[{"agentId":"agent-1","metricName":"cpu","value":42.0,"timestamp":"2026-03-11T00:00:00Z","tags":{}}]' ``` @@ -83,29 +117,36 @@ open http://localhost:8081/api/v1/swagger-ui.html ```bash # Search by status (GET with basic filters) -curl -s "http://localhost:8081/api/v1/search/executions?status=COMPLETED&limit=10" +curl -s -H "Authorization: Bearer $TOKEN" \ + "http://localhost:8081/api/v1/search/executions?status=COMPLETED&limit=10" # Search by time range -curl -s "http://localhost:8081/api/v1/search/executions?timeFrom=2026-03-11T00:00:00Z&timeTo=2026-03-12T00:00:00Z" +curl -s -H "Authorization: Bearer $TOKEN" \ + "http://localhost:8081/api/v1/search/executions?timeFrom=2026-03-11T00:00:00Z&timeTo=2026-03-12T00:00:00Z" # Advanced search (POST with full-text) curl -s -X POST http://localhost:8081/api/v1/search/executions \ -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ -d '{"status":"FAILED","text":"NullPointerException","limit":20}' # Transaction detail (nested processor tree) -curl -s http://localhost:8081/api/v1/executions/{executionId} +curl -s -H "Authorization: Bearer $TOKEN" \ + http://localhost:8081/api/v1/executions/{executionId} # Processor exchange snapshot -curl -s http://localhost:8081/api/v1/executions/{executionId}/processors/{index}/snapshot +curl -s -H "Authorization: Bearer $TOKEN" \ + http://localhost:8081/api/v1/executions/{executionId}/processors/{index}/snapshot # Render diagram as SVG -curl -s http://localhost:8081/api/v1/diagrams/{contentHash}/render \ - -H "Accept: image/svg+xml" +curl -s -H "Authorization: Bearer $TOKEN" \ + -H "Accept: image/svg+xml" \ + http://localhost:8081/api/v1/diagrams/{contentHash}/render # Render diagram as JSON layout -curl -s http://localhost:8081/api/v1/diagrams/{contentHash}/render \ - -H "Accept: application/json" +curl -s -H "Authorization: Bearer $TOKEN" \ + -H "Accept: application/json" \ + http://localhost:8081/api/v1/diagrams/{contentHash}/render ``` **Search response format:** `{ "data": [...], "total": N, "offset": 0, "limit": 50 }` @@ -117,38 +158,44 @@ curl -s http://localhost:8081/api/v1/diagrams/{contentHash}/render \ ### Agent Registry & SSE (Phase 3) ```bash -# Register an agent +# Register an agent (uses bootstrap token, not JWT — see Authentication section above) curl -s -X POST http://localhost:8081/api/v1/agents/register \ -H "Content-Type: application/json" \ + -H "Authorization: Bearer my-secret-token" \ -d '{"agentId":"agent-1","name":"Order Service","group":"order-service-prod","version":"1.0.0","routeIds":["route-1","route-2"],"capabilities":["deep-trace","replay"]}' # Heartbeat (call every 30s) -curl -s -X POST http://localhost:8081/api/v1/agents/agent-1/heartbeat +curl -s -X POST http://localhost:8081/api/v1/agents/agent-1/heartbeat \ + -H "Authorization: Bearer $TOKEN" # List agents (optionally filter by status) -curl -s "http://localhost:8081/api/v1/agents" -curl -s "http://localhost:8081/api/v1/agents?status=LIVE" +curl -s -H "Authorization: Bearer $TOKEN" "http://localhost:8081/api/v1/agents" +curl -s -H "Authorization: Bearer $TOKEN" "http://localhost:8081/api/v1/agents?status=LIVE" -# Connect to SSE event stream (agent receives commands here) -curl -s -N http://localhost:8081/api/v1/agents/agent-1/events +# Connect to SSE event stream (JWT via query parameter) +curl -s -N "http://localhost:8081/api/v1/agents/agent-1/events?token=$TOKEN" # Send command to single agent curl -s -X POST http://localhost:8081/api/v1/agents/agent-1/commands \ -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ -d '{"type":"config-update","payload":{"samplingRate":0.5}}' # Send command to agent group curl -s -X POST http://localhost:8081/api/v1/agents/groups/order-service-prod/commands \ -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ -d '{"type":"deep-trace","payload":{"routeId":"route-1","durationSeconds":60}}' # Broadcast command to all live agents curl -s -X POST http://localhost:8081/api/v1/agents/commands \ -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ -d '{"type":"config-update","payload":{"samplingRate":1.0}}' # Acknowledge command delivery -curl -s -X POST http://localhost:8081/api/v1/agents/agent-1/commands/{commandId}/ack +curl -s -X POST http://localhost:8081/api/v1/agents/agent-1/commands/{commandId}/ack \ + -H "Authorization: Bearer $TOKEN" ``` **Agent lifecycle:** LIVE (heartbeat within 90s) → STALE (missed 3 heartbeats) → DEAD (5min after STALE). DEAD agents kept indefinitely. @@ -177,6 +224,10 @@ Key settings in `cameleer3-server-app/src/main/resources/application.yml`: | `agent-registry.dead-threshold-seconds` | 300 | Time after STALE before DEAD | | `agent-registry.command-expiry-seconds` | 60 | Pending command TTL | | `agent-registry.keepalive-interval-seconds` | 15 | SSE ping keepalive interval | +| `security.access-token-expiry-ms` | 3600000 | JWT access token lifetime (1h) | +| `security.refresh-token-expiry-ms` | 604800000 | Refresh token lifetime (7d) | +| `security.bootstrap-token` | `${CAMELEER_AUTH_TOKEN}` | Bootstrap token for agent registration (required) | +| `security.bootstrap-token-previous` | `${CAMELEER_AUTH_TOKEN_PREVIOUS}` | Previous bootstrap token for rotation (optional) | ## Running Tests