Files
cameleer-server/.planning/phases/04-security/04-01-PLAN.md
hsiegeln b7c35037e6 docs(04-security): create phase plan
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>
2026-03-11 19:51:22 +01:00

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
cameleer3-server-app/pom.xml
cameleer3-server-core/src/main/java/com/cameleer3/server/core/security/JwtService.java
cameleer3-server-core/src/main/java/com/cameleer3/server/core/security/Ed25519SigningService.java
cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/JwtServiceImpl.java
cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/Ed25519SigningServiceImpl.java
cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/BootstrapTokenValidator.java
cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityProperties.java
cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityBeanConfig.java
cameleer3-server-app/src/main/resources/application.yml
cameleer3-server-app/src/test/resources/application-test.yml
cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/JwtServiceTest.java
cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/Ed25519SigningServiceTest.java
cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/BootstrapTokenValidatorTest.java
true
SECU-03
SECU-05
truths artifacts key_links
Ed25519 keypair is generated at server startup and public key is available as Base64
JwtService can create access tokens (1h expiry) and refresh tokens (7d expiry) with agentId and group claims
JwtService can validate tokens and extract agentId, distinguishing access vs refresh type
BootstrapTokenValidator accepts CAMELEER_AUTH_TOKEN and optionally CAMELEER_AUTH_TOKEN_PREVIOUS using constant-time comparison
Server fails fast on startup if CAMELEER_AUTH_TOKEN is not set
path provides
cameleer3-server-core/src/main/java/com/cameleer3/server/core/security/JwtService.java JWT service interface with createAccessToken, createRefreshToken, validateAndExtractAgentId
path provides
cameleer3-server-core/src/main/java/com/cameleer3/server/core/security/Ed25519SigningService.java Ed25519 signing interface with sign(payload) and getPublicKeyBase64()
path provides
cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/JwtServiceImpl.java Nimbus JOSE+JWT HMAC-SHA256 implementation
path provides
cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/Ed25519SigningServiceImpl.java JDK 17 Ed25519 KeyPairGenerator implementation
path provides
cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/BootstrapTokenValidator.java Constant-time bootstrap token validation with dual-token rotation
from to via pattern
JwtServiceImpl Nimbus JOSE+JWT MACSigner/MACVerifier HMAC-SHA256 signing with ephemeral 256-bit secret MACSigner|MACVerifier|SignedJWT
from to via pattern
Ed25519SigningServiceImpl JDK KeyPairGenerator/Signature Ed25519 algorithm from java.security KeyPairGenerator.getInstance.*Ed25519
from to via pattern
BootstrapTokenValidator SecurityProperties reads token values from config properties MessageDigest.isEqual
Create the security service foundation: interfaces in core module, implementations in app module, Maven dependencies, and configuration properties. This provides all cryptographic building blocks (JWT creation/validation, Ed25519 signing, bootstrap token validation) that the filter chain and endpoint integration plans depend on.

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 { ... }
Task 1: Core interfaces + app implementations + Maven deps cameleer3-server-app/pom.xml, cameleer3-server-core/src/main/java/com/cameleer3/server/core/security/JwtService.java, cameleer3-server-core/src/main/java/com/cameleer3/server/core/security/Ed25519SigningService.java, cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/JwtServiceImpl.java, cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/Ed25519SigningServiceImpl.java, cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/BootstrapTokenValidator.java, cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityProperties.java, cameleer3-server-app/src/main/java/com/cameleer3/server/app/security/SecurityBeanConfig.java, cameleer3-server-app/src/main/resources/application.yml, cameleer3-server-app/src/test/resources/application-test.yml, cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/JwtServiceTest.java, cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/Ed25519SigningServiceTest.java, cameleer3-server-app/src/test/java/com/cameleer3/server/app/security/BootstrapTokenValidatorTest.java JwtService tests: - createAccessToken(agentId, group) returns a signed JWT string with sub=agentId, claim "group"=group, claim "type"="access", expiry ~1h from now - createRefreshToken(agentId, group) returns a signed JWT string with sub=agentId, claim "type"="refresh", expiry ~7d from now - validateAndExtractAgentId(validAccessToken) returns the agentId - validateAndExtractAgentId(expiredToken) throws exception - validateAndExtractAgentId(refreshToken) throws exception (wrong type for access validation) - validateRefreshToken(validRefreshToken) returns the agentId - validateRefreshToken(accessToken) throws exception (wrong type)
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>
After completion, create `.planning/phases/04-security/04-01-SUMMARY.md`