diff --git a/.planning/research/ARCHITECTURE.md b/.planning/research/ARCHITECTURE.md index 658a57a5..bf7f4a6a 100644 --- a/.planning/research/ARCHITECTURE.md +++ b/.planning/research/ARCHITECTURE.md @@ -43,7 +43,8 @@ Agents (50+) Users / UI | **SSE Channel Manager** | core (interface) + app (impl) | Manage SSE connections, push config/commands | Agent Registry | | **Diagram Service** | core | Version diagrams, link to transactions, trigger rendering | Diagram Store | | **Diagram Renderer** | core | Server-side rendering of route definitions to visual output | Diagram Service | -| **Auth Service** | core | JWT validation, Ed25519 signing, bootstrap token flow | All controllers | +| **Auth Service** | core | JWT validation with RBAC (AGENT/VIEWER/OPERATOR/ADMIN), Ed25519 signing, bootstrap token flow, OIDC token exchange | All controllers | +| **User Repository** | core (interface) + app (ClickHouse) | Persist users from local login and OIDC, role management | Auth controllers, admin API | | **REST Controllers** | app | HTTP endpoints for transactions, agents, diagrams, config | All core services | | **SSE Controller** | app | SSE endpoint, connection lifecycle | SSE Channel Manager | | **Config Controller** | app | Config CRUD, push triggers | SSE Channel Manager, Config store | diff --git a/CLAUDE.md b/CLAUDE.md index fb934df2..28513ac8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -39,7 +39,9 @@ java -jar cameleer3-server-app/target/cameleer3-server-app-1.0-SNAPSHOT.jar - Communication: receives HTTP POST data from agents, serves SSE event streams for config push/commands - Maintains agent instance registry with states: LIVE → STALE → DEAD - Storage: ClickHouse for structured data, text index for full-text search -- Security: JWT auth, Ed25519 config signing, bootstrap token for registration +- Security: JWT auth with RBAC (AGENT/VIEWER/OPERATOR/ADMIN roles), Ed25519 config signing, bootstrap token for registration +- OIDC: Optional external identity provider support (token exchange pattern). Configured via `CAMELEER_OIDC_*` env vars +- User persistence: ClickHouse `users` table, admin CRUD at `/api/v1/admin/users` ## CI/CD & Deployment @@ -50,6 +52,6 @@ java -jar cameleer3-server-app/target/cameleer3-server-app-1.0-SNAPSHOT.jar - Registry: `gitea.siegeln.net/cameleer/cameleer3-server` (container images) - K8s manifests in `deploy/` — ClickHouse StatefulSet + server Deployment + NodePort Service (30081) - Deployment target: k3s at 192.168.50.86, namespace `cameleer` -- Secrets managed in CI deploy step (idempotent `--dry-run=client | kubectl apply`): `cameleer-auth`, `clickhouse-credentials` +- Secrets managed in CI deploy step (idempotent `--dry-run=client | kubectl apply`): `cameleer-auth`, `clickhouse-credentials`, `CAMELEER_JWT_SECRET` - K8s probes: server uses `/api/v1/health`, ClickHouse uses `/ping` - Docker build uses buildx registry cache + `--provenance=false` for Gitea compatibility diff --git a/HOWTO.md b/HOWTO.md index bd712903..d0c9b6ae 100644 --- a/HOWTO.md +++ b/HOWTO.md @@ -27,7 +27,7 @@ Start ClickHouse: docker compose up -d ``` -This starts ClickHouse 25.3 and automatically runs the schema init scripts (`clickhouse/init/01-schema.sql`, `clickhouse/init/02-search-columns.sql`). +This starts ClickHouse 25.3 and automatically runs the schema init scripts (`clickhouse/init/01-schema.sql`, `clickhouse/init/02-search-columns.sql`, `clickhouse/init/03-users.sql`). | Service | Port | Purpose | |------------|------|------------------| @@ -94,6 +94,54 @@ UI credentials are configured via `CAMELEER_UI_USER` / `CAMELEER_UI_PASSWORD` en **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. +### RBAC (Role-Based Access Control) + +JWTs carry a `roles` claim. Endpoints are restricted by role: + +| Role | Access | +|------|--------| +| `AGENT` | Data ingestion (`/data/**`), heartbeat, SSE events, command ack | +| `VIEWER` | Search, execution detail, diagrams, agent list | +| `OPERATOR` | VIEWER + send commands to agents | +| `ADMIN` | OPERATOR + user management (`/admin/**`) | + +The env-var local user gets `ADMIN` role. Agents get `AGENT` role at registration. + +### OIDC Login (Optional) + +When `CAMELEER_OIDC_ENABLED=true`, the server supports external identity providers (e.g. Authentik, Keycloak): + +```bash +# 1. SPA checks if OIDC is available +curl -s http://localhost:8081/api/v1/auth/oidc/config +# Returns: { "issuer": "...", "clientId": "...", "authorizationEndpoint": "..." } + +# 2. After OIDC redirect, SPA sends the authorization code +curl -s -X POST http://localhost:8081/api/v1/auth/oidc/callback \ + -H "Content-Type: application/json" \ + -d '{"code":"auth-code-from-provider","redirectUri":"http://localhost:5173/callback"}' +# Returns: { "accessToken": "...", "refreshToken": "..." } +``` + +Local login remains available as fallback even when OIDC is enabled. + +### User Management (ADMIN only) + +```bash +# List all users +curl -s -H "Authorization: Bearer $TOKEN" http://localhost:8081/api/v1/admin/users + +# Update user roles +curl -s -X PUT http://localhost:8081/api/v1/admin/users/{userId}/roles \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $TOKEN" \ + -d '{"roles":["VIEWER","OPERATOR"]}' + +# Delete user +curl -s -X DELETE http://localhost:8081/api/v1/admin/users/{userId} \ + -H "Authorization: Bearer $TOKEN" +``` + ### Ingestion (POST, returns 202 Accepted) ```bash @@ -252,6 +300,13 @@ Key settings in `cameleer3-server-app/src/main/resources/application.yml`: | `security.ui-user` | `admin` | UI login username (`CAMELEER_UI_USER` env var) | | `security.ui-password` | `admin` | UI login password (`CAMELEER_UI_PASSWORD` env var) | | `security.ui-origin` | `http://localhost:5173` | CORS allowed origin for UI (`CAMELEER_UI_ORIGIN` env var) | +| `security.jwt-secret` | *(random)* | HMAC secret for JWT signing (`CAMELEER_JWT_SECRET`). If set, tokens survive restarts | +| `security.oidc.enabled` | `false` | Enable OIDC login (`CAMELEER_OIDC_ENABLED`) | +| `security.oidc.issuer-uri` | | OIDC provider issuer URL (`CAMELEER_OIDC_ISSUER`) | +| `security.oidc.client-id` | | OAuth2 client ID (`CAMELEER_OIDC_CLIENT_ID`) | +| `security.oidc.client-secret` | | OAuth2 client secret (`CAMELEER_OIDC_CLIENT_SECRET`) | +| `security.oidc.roles-claim` | `realm_access.roles` | JSONPath to roles in OIDC id_token (`CAMELEER_OIDC_ROLES_CLAIM`) | +| `security.oidc.default-roles` | `VIEWER` | Default roles for new OIDC users (`CAMELEER_OIDC_DEFAULT_ROLES`) | ## Web UI Development @@ -324,7 +379,7 @@ cameleer namespace: Push to `main` triggers: **build** (UI npm + Maven, unit tests) → **docker** (buildx amd64 for server + UI, push to Gitea registry) → **deploy** (kubectl apply + rolling update). -Required Gitea org secrets: `REGISTRY_TOKEN`, `KUBECONFIG_BASE64`, `CAMELEER_AUTH_TOKEN`, `CLICKHOUSE_USER`, `CLICKHOUSE_PASSWORD`, `CAMELEER_UI_USER` (optional), `CAMELEER_UI_PASSWORD` (optional). +Required Gitea org secrets: `REGISTRY_TOKEN`, `KUBECONFIG_BASE64`, `CAMELEER_AUTH_TOKEN`, `CLICKHOUSE_USER`, `CLICKHOUSE_PASSWORD`, `CAMELEER_UI_USER` (optional), `CAMELEER_UI_PASSWORD` (optional), `CAMELEER_JWT_SECRET` (recommended — tokens survive restarts). ### Manual K8s Commands