diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index dc4571e4..dc7e625e 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -46,7 +46,7 @@ Requirements for initial release. Each maps to roadmap phases. Tracked as Gitea - [ ] **SECU-01**: All API endpoints (except health and register) require valid JWT Bearer token (#23) - [ ] **SECU-02**: JWT refresh flow via `POST /api/v1/agents/{id}/refresh` (#24) - [x] **SECU-03**: Server generates Ed25519 keypair; public key delivered at registration (#25) -- [ ] **SECU-04**: All config-update and replay SSE payloads are signed with server's Ed25519 private key (#26) +- [x] **SECU-04**: All config-update and replay SSE payloads are signed with server's Ed25519 private key (#26) - [x] **SECU-05**: Bootstrap token from `CAMELEER_AUTH_TOKEN` env var validates initial agent registration (#27) ### REST API diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index a41fdce1..5ff7b330 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -76,7 +76,7 @@ Plans: 2. Agents can refresh expired JWTs via the refresh endpoint without re-registering 3. Server generates an Ed25519 keypair at startup, delivers the public key during registration, and all config-update and replay SSE payloads carry a valid Ed25519 signature 4. Bootstrap token from CAMELEER_AUTH_TOKEN environment variable is required for initial agent registration -**Plans:** 3 plans +**Plans:** 2/3 plans executed Plans: - [x] 04-01-PLAN.md -- Security service foundation: JwtService, Ed25519SigningService, BootstrapTokenValidator, Maven deps, config @@ -94,4 +94,4 @@ Note: Phases 2 and 3 both depend only on Phase 1 and could execute in parallel. | 1. Ingestion Pipeline + API Foundation | 3/3 | Complete | 2026-03-11 | | 2. Transaction Search + Diagrams | 3/4 | Gap Closure | | | 3. Agent Registry + SSE Push | 2/2 | Complete | 2026-03-11 | -| 4. Security | 1/3 | In Progress | - | +| 4. Security | 2/3 | In Progress| | diff --git a/.planning/STATE.md b/.planning/STATE.md index dd062fa3..58ea78d0 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,15 +3,15 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone status: in-progress -stopped_at: Completed 04-01-PLAN.md -last_updated: "2026-03-11T19:08:55Z" -last_activity: 2026-03-11 -- Completed 04-01 (Security service foundation) +stopped_at: Completed 04-03-PLAN.md +last_updated: "2026-03-11T19:29:30Z" +last_activity: 2026-03-11 -- Completed 04-03 (SSE payload signing) progress: total_phases: 4 - completed_phases: 3 + completed_phases: 4 total_plans: 12 - completed_plans: 10 - percent: 83 + completed_plans: 12 + percent: 100 --- # Project State @@ -26,11 +26,11 @@ See: .planning/PROJECT.md (updated 2026-03-11) ## Current Position Phase: 4 of 4 (Security) -Plan: 1 of 3 in current phase (Security service foundation) -Status: Phase 04 in progress, Plan 01 complete -Last activity: 2026-03-11 -- Completed 04-01 (Security service foundation) +Plan: 3 of 3 in current phase (SSE payload signing) +Status: Phase 04 complete, all plans done +Last activity: 2026-03-11 -- Completed 04-03 (SSE payload signing) -Progress: [████████░░] 83% +Progress: [██████████] 100% ## Performance Metrics @@ -60,6 +60,7 @@ Progress: [████████░░] 83% | Phase 03 P01 | 15min | 2 tasks | 15 files | | Phase 03 P02 | 32min | 2 tasks | 7 files | | Phase 04 P01 | 12min | 1 tasks | 15 files | +| Phase 04 P03 | 17min | 1 tasks | 4 files | ## Accumulated Context @@ -106,6 +107,8 @@ Recent decisions affecting current work: - [Phase 04]: JDK 17 built-in Ed25519 KeyPairGenerator (no Bouncy Castle dependency needed) - [Phase 04]: TestSecurityConfig as @Configuration in test sources for automatic @SpringBootTest scanning - [Phase 04]: InitializingBean pattern for fail-fast bootstrap token validation on startup +- [Phase 04]: Signed payload parsed to JsonNode for correct SseEmitter serialization (avoids double-quoting) +- [Phase 04]: SseSigningIT adapted to Plan 02 security layer (bootstrap token + JWT auth) ### Pending Todos @@ -120,6 +123,6 @@ None yet. ## Session Continuity -Last session: 2026-03-11T19:08:55Z -Stopped at: Completed 04-01-PLAN.md -Resume file: .planning/phases/04-security/04-02-PLAN.md +Last session: 2026-03-11T19:29:30Z +Stopped at: Completed 04-03-PLAN.md +Resume file: All plans complete diff --git a/.planning/phases/04-security/04-03-SUMMARY.md b/.planning/phases/04-security/04-03-SUMMARY.md new file mode 100644 index 00000000..a92d18f0 --- /dev/null +++ b/.planning/phases/04-security/04-03-SUMMARY.md @@ -0,0 +1,134 @@ +--- +phase: 04-security +plan: 03 +subsystem: auth +tags: [ed25519, sse-signing, payload-integrity, server-sent-events] + +# Dependency graph +requires: + - phase: 04-security + provides: "Ed25519SigningService interface and implementation from Plan 01" + - phase: 03-agent-registry + provides: "SseConnectionManager, AgentCommand, SSE event delivery" +provides: + - "SsePayloadSigner component for Ed25519 signing of SSE command payloads" + - "All SSE command events (config-update, deep-trace, replay) carry verifiable signature field" +affects: [] + +# Tech tracking +tech-stack: + added: [] + patterns: [sign-then-serialize for SSE payloads, JsonNode passthrough for correct SseEmitter serialization] + +key-files: + created: + - cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/SsePayloadSigner.java + - cameleer3-server-app/src/test/java/com/cameleer3/server/app/agent/SsePayloadSignerTest.java + - cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/SseSigningIT.java + modified: + - cameleer3-server-app/src/main/java/com/cameleer3/server/app/agent/SseConnectionManager.java + +key-decisions: + - "Signed payload parsed to JsonNode before passing to SseEmitter to avoid double-quoting raw JSON strings" + - "SseSigningIT uses bootstrap token + JWT auth (adapts to Plan 02 security layer introduced during parallel execution)" + +patterns-established: + - "Sign-then-serialize: signature computed over original payload string, then payload parsed and signature field added" + - "Defensive null/blank handling in SsePayloadSigner returns payload unchanged with warning log" + +requirements-completed: [SECU-04] + +# Metrics +duration: 17min +completed: 2026-03-11 +--- + +# Phase 4 Plan 03: SSE Payload Signing Summary + +**Ed25519 signature injection into all SSE command events (config-update, deep-trace, replay) with end-to-end verification tests using JDK Signature API** + +## Performance + +- **Duration:** 17 min +- **Started:** 2026-03-11T19:12:25Z +- **Completed:** 2026-03-11T19:29:30Z +- **Tasks:** 1 (TDD: RED + GREEN) +- **Files modified:** 4 + +## Accomplishments +- SsePayloadSigner signs JSON payloads with Ed25519 and adds Base64-encoded signature field +- SseConnectionManager signs all command payloads before SSE delivery, parses to JsonNode for correct serialization +- 7 unit tests verify signature roundtrip, edge cases (null/empty/blank), and Base64 encoding +- 2 integration tests verify end-to-end: command sent with bootstrap+JWT auth, SSE event received with valid Ed25519 signature +- Ping keepalive events remain unsigned (they are SSE comments, not data events) + +## Task Commits + +Each task was committed atomically (TDD flow): + +1. **Task 1 RED: Failing tests for SSE payload signing** - `b3b4e62` (test) +2. **Task 1 GREEN: Implement SSE payload signing** - `0215fd9` (feat) + +_No REFACTOR commit needed -- implementation is clean and minimal._ + +## Files Created/Modified + +- `cameleer3-server-app/.../agent/SsePayloadSigner.java` - Component that signs JSON payloads with Ed25519 and adds signature field +- `cameleer3-server-app/.../agent/SseConnectionManager.java` - Updated onCommandReady to sign payload before SSE delivery +- `cameleer3-server-app/.../agent/SsePayloadSignerTest.java` - 7 unit tests for signing behavior and edge cases +- `cameleer3-server-app/.../security/SseSigningIT.java` - 2 integration tests for end-to-end signature verification + +## Decisions Made + +- **JsonNode passthrough for SseEmitter:** The signed payload string is parsed to a Jackson JsonNode before passing to SseEmitter.event().data(). This avoids the double-quoting problem where a raw JSON string would be wrapped in additional quotes by Jackson's message converter. +- **Adapted to Plan 02 security layer:** SseSigningIT was updated to use bootstrap token for registration and JWT query param for SSE connection, since Plan 02 (Spring Security filter chain) was committed during parallel execution of this plan. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 3 - Blocking] Updated SseSigningIT for Plan 02 security requirements** +- **Found during:** Task 1 GREEN phase (integration test execution) +- **Issue:** Plan 02 was committed in parallel, introducing real SecurityConfig that requires bootstrap token + JWT. The original test plan assumed TestSecurityConfig permit-all would be active. +- **Fix:** Updated SseSigningIT to register with bootstrap token, extract JWT from response, and use JWT query param for SSE connection. +- **Files modified:** SseSigningIT.java +- **Verification:** Both integration tests pass with full auth flow +- **Committed in:** 0215fd9 (GREEN phase commit) + +--- + +**Total deviations:** 1 auto-fixed (1 blocking) +**Impact on plan:** Necessary adaptation to parallel plan execution. No scope creep. + +## Pre-existing Failures (Out of Scope) + +8 integration test failures pre-exist from Plan 02's security integration (not caused by this plan's changes): +- AgentSseControllerIT: 1 failure (unknownAgent expected 404, gets 403) +- AgentCommandControllerIT: 2 failures (unauthenticated requests get 403 instead of 404) +- JwtRefreshIT: 5 failures (all tests, likely missing bootstrap token in setup) + +Logged to `deferred-items.md` in this phase directory. + +## Issues Encountered + +None specific to this plan's scope. + +## User Setup Required + +None - no external service configuration required. + +## Next Phase Readiness + +- All SSE command events now carry verifiable Ed25519 signatures +- Security phase implementation is complete (Plans 01, 02, 03) +- Pre-existing test failures from Plan 02 need resolution (documented in deferred-items.md) + +## Self-Check: PASSED + +- All 4 created/modified files verified present on disk +- Both commits (b3b4e62, 0215fd9) verified in git log +- Unit tests: 7 pass, Integration tests: 2 pass + +--- +*Phase: 04-security* +*Completed: 2026-03-11* diff --git a/.planning/phases/04-security/deferred-items.md b/.planning/phases/04-security/deferred-items.md new file mode 100644 index 00000000..b1860e87 --- /dev/null +++ b/.planning/phases/04-security/deferred-items.md @@ -0,0 +1,15 @@ +# Phase 04 — Deferred Items + +## Pre-existing Test Failures (from Plan 02 security integration) + +These tests fail because Plan 02 introduced real Spring Security but did not update all existing integration tests to pass JWT auth headers. The security filter returns 403 before controllers can return the expected error codes. + +1. **AgentSseControllerIT.sseConnect_unknownAgent_returns404** — expects 404, gets 403 (security blocks unauthenticated request) +2. **AgentCommandControllerIT.sendCommandToUnregisteredAgent_returns404** — expects 404, gets 403 +3. **AgentCommandControllerIT.acknowledgeUnknownCommand_returns404** — expects 404, gets 403 +4. **JwtRefreshIT (all 5 tests)** — all failing, likely needs bootstrap token for agent registration step + +**Root cause:** Plan 02 emptied TestSecurityConfig and activated real SecurityConfig, but did not update pre-existing ITs to include JWT auth or adjust expected status codes for unauthenticated requests. + +**Discovered during:** Plan 03 execution (04-03) +**Scope:** Out of scope for Plan 03 (pre-existing, not caused by signing changes)