3 plans in 2 waves covering all 5 SECU requirements: - Plan 01 (W1): Security service foundation (JWT, Ed25519, bootstrap token) - Plan 02 (W2): Spring Security filter chain, endpoint protection, test adaptation - Plan 03 (W2): SSE payload signing with Ed25519 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
13 KiB
13 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 04-security | 01 | execute | 1 |
|
true |
|
|
Purpose: Establishes the security primitives before they are wired into Spring Security and controllers. Output: Working JwtService, Ed25519SigningService, BootstrapTokenValidator with passing unit tests.
<execution_context> @C:/Users/Hendrik/.claude/get-shit-done/workflows/execute-plan.md @C:/Users/Hendrik/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/04-security/04-CONTEXT.md @.planning/phases/04-security/04-RESEARCH.md @.planning/phases/04-security/04-VALIDATION.md@cameleer3-server-app/pom.xml @cameleer3-server-app/src/main/resources/application.yml @cameleer3-server-app/src/test/resources/application-test.yml @cameleer3-server-app/src/main/java/com/cameleer3/server/app/config/AgentRegistryConfig.java
From core/agent/AgentRegistryService.java:
// Plain class in core module, wired as bean by app module config
public class AgentRegistryService {
public AgentInfo register(String id, String name, String group, ...);
public AgentInfo findById(String id);
}
From app/config/AgentRegistryConfig.java:
@ConfigurationProperties(prefix = "agent-registry")
public class AgentRegistryConfig { ... }
Ed25519SigningService tests:
- getPublicKeyBase64() returns non-null Base64 string
- sign(payload) returns Base64 signature string
- Signature verifies against public key using JDK Signature.getInstance("Ed25519")
- Different payloads produce different signatures
- Tampered payload fails verification
BootstrapTokenValidator tests:
- validate(correctToken) returns true
- validate(wrongToken) returns false
- validate(previousToken) returns true when CAMELEER_AUTH_TOKEN_PREVIOUS is set
- validate(null) returns false
- Uses constant-time comparison (MessageDigest.isEqual)
1. Add Maven dependencies to cameleer3-server-app/pom.xml:
- `spring-boot-starter-security` (managed version)
- `com.nimbusds:nimbus-jose-jwt:9.47` (explicit, may not be transitive without OAuth2 resource server)
- `spring-security-test` scope test (managed version)
2. Create core module interfaces:
- `JwtService` interface: `createAccessToken(String agentId, String group)`, `createRefreshToken(String agentId, String group)`, `validateAndExtractAgentId(String token)` (access only), `validateRefreshToken(String token)` (refresh only). Returns String tokens, throws `InvalidTokenException` (new checked or runtime exception in core).
- `Ed25519SigningService` interface: `sign(String payload)` returns Base64 signature string, `getPublicKeyBase64()` returns Base64-encoded X.509 SubjectPublicKeyInfo DER public key.
3. Create app module implementations:
- `SecurityProperties` as `@ConfigurationProperties(prefix = "security")` with fields: `accessTokenExpiryMs` (default 3600000), `refreshTokenExpiryMs` (default 604800000), `bootstrapToken` (from env CAMELEER_AUTH_TOKEN), `bootstrapTokenPrevious` (from env CAMELEER_AUTH_TOKEN_PREVIOUS, nullable).
- `JwtServiceImpl`: Generate random 256-bit HMAC secret in constructor (`new SecureRandom().nextBytes(secret)`). Use Nimbus `MACSigner`/`MACVerifier` with `JWSAlgorithm.HS256`. Claims: `sub`=agentId, `group`=group, `type`="access"|"refresh", `iat`=now, `exp`=now+expiry. Validation checks: signature valid, not expired, correct `type` claim.
- `Ed25519SigningServiceImpl`: Generate `KeyPair` via `KeyPairGenerator.getInstance("Ed25519")` in constructor. `sign()` uses `Signature.getInstance("Ed25519")`, `initSign(privateKey)`, returns Base64-encoded signature bytes. `getPublicKeyBase64()` returns `Base64.getEncoder().encodeToString(publicKey.getEncoded())`.
- `BootstrapTokenValidator`: Constructor takes `SecurityProperties`. `validate(String provided)` returns boolean. Uses `MessageDigest.isEqual(provided.getBytes(UTF_8), expected.getBytes(UTF_8))`. If first token fails and previousToken is non-null, tries previousToken. Returns false for null/blank input.
- `SecurityBeanConfig` as `@Configuration` with `@EnableConfigurationProperties(SecurityProperties.class)`. Creates beans for `JwtServiceImpl`, `Ed25519SigningServiceImpl`, `BootstrapTokenValidator`. Add `@PostConstruct` or `InitializingBean` validation: if `SecurityProperties.bootstrapToken` is null or blank, throw `IllegalStateException("CAMELEER_AUTH_TOKEN environment variable must be set")`.
4. Update application.yml: Add `security.access-token-expiry-ms: 3600000`, `security.refresh-token-expiry-ms: 604800000`. Map env vars: `security.bootstrap-token: ${CAMELEER_AUTH_TOKEN:}`, `security.bootstrap-token-previous: ${CAMELEER_AUTH_TOKEN_PREVIOUS:}`.
5. Update application-test.yml: Add `security.bootstrap-token: test-bootstrap-token`, `security.bootstrap-token-previous: old-bootstrap-token`. Also set `CAMELEER_AUTH_TOKEN: test-bootstrap-token` as an env override if needed.
6. IMPORTANT: Adding spring-boot-starter-security will break ALL existing tests immediately (401 on all endpoints). To prevent this during Plan 01 (before the security filter chain is configured in Plan 02), add a temporary test security config class `src/test/java/com/cameleer3/server/app/security/TestSecurityConfig.java` annotated `@TestConfiguration` that creates a `SecurityFilterChain` permitting all requests. This keeps existing tests green while security services are built. Plan 02 will replace this with real security config and update tests.
7. Write unit tests per the behavior spec above. Tests should NOT require Spring context -- construct implementations directly with test SecurityProperties.
cd /c/Users/Hendrik/Documents/projects/cameleer3-server && mvn test -pl cameleer3-server-app -Dtest="JwtServiceTest,Ed25519SigningServiceTest,BootstrapTokenValidatorTest" -Dsurefire.reuseForks=false
- JwtService creates and validates access/refresh JWTs with correct claims and expiry
- Ed25519SigningService generates keypair, signs payloads, signatures verify with public key
- BootstrapTokenValidator uses constant-time comparison, supports dual-token rotation
- Server startup fails if CAMELEER_AUTH_TOKEN is not set (tested via SecurityBeanConfig @PostConstruct)
- All existing tests still pass (TestSecurityConfig permits all requests temporarily)
- Maven compiles with new dependencies
mvn clean verify
All new unit tests pass. All existing integration tests still pass (no 401 regressions).
<success_criteria>
- JwtServiceImpl creates signed JWTs with correct HMAC-SHA256, validates them, and rejects expired/wrong-type tokens
- Ed25519SigningServiceImpl generates ephemeral keypair, signs payloads with verifiable signatures
- BootstrapTokenValidator performs constant-time comparison with dual-token support
- SecurityProperties loaded from application.yml with env var mapping
- Startup fails fast when CAMELEER_AUTH_TOKEN is missing
- Existing test suite remains green via TestSecurityConfig permit-all </success_criteria>