feat(server): v2 secure-session-core Task 3 - secure relay WS
SPEC-002 Phase 1 Task 3 (specs/v2-secure-session-core), code-reviewed APPROVED. - viewer_ws_handler: verify the session-scoped VIEWER token (validate_viewer_token sig+exp+purpose) + token_blacklist.is_revoked + session_id claim == requested session, before upgrade. Raw login JWTs no longer accepted on the viewer plane (closes audit CRITICAL #2; closes the *mechanism* of CRITICAL #1). - mint_viewer_token: authz gate is_admin() || has_permission("view") -> 403. - Agent identity binding: validate_agent_api_key returns AgentKeyAuth; a cak_- verified agent rebinds to the key's machine identity (fails closed if unresolvable), so a key for machine X cannot seize machine Y's session slot. - Frame caps on both WS upgrades (agent 4 MiB, viewer 64 KiB) - closes WS-OOM HIGH. - Viewer->agent input throttle (200 ev/s token bucket, bounded try_send) - closes input-injection MEDIUM. - Startup managed-session reconcile clarified. KNOWN FOLLOW-UPS (tracked todos): (1) authz STRENGTH - the "view" permission is held by every default role incl. viewer, and a viewer token grants input control, so the gate should be "control" or a VIEW_ONLY/CONTROL token split; CRITICAL #1 is mechanism-closed, strength pending decision. (2) revoke minted viewer tokens on logout (currently bounded only by 5-min TTL). Not cargo-check-verified (no toolchain on the authoring host). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
# v2 Secure Session Core — Implementation Plan
|
||||
|
||||
> Spec created: 2026-05-29
|
||||
> Status: in progress — Tasks 1-2 DONE 2026-05-29; Task 3 (relay WS) next.
|
||||
> Status: in progress — Tasks 1-3 DONE 2026-05-29 (Task 3 code-reviewed APPROVED). REQUIRED follow-up
|
||||
> before Phase-1 exit: viewer-token authz STRENGTH — the gate uses `view` (held by EVERY default role
|
||||
> incl. `viewer`) but a viewer token grants input CONTROL; flip to `control`, or split VIEW_ONLY/CONTROL
|
||||
> tokens (proto already models SCREEN_CONTROL vs VIEW_ONLY). PENDING Mike. Also: nothing revokes a minted
|
||||
> viewer token on logout (bounded by 5-min TTL) — follow-up todo. Task 4 (rate limiting + single-use codes) next.
|
||||
> CARRY-FORWARD: Task 3 MUST add a viewer-token AUTHORIZATION check (admin/permission gate) — Task 2
|
||||
> fixed only the token *mechanism*; the authz gate is what actually closes audit CRITICAL #1. Policy
|
||||
> (admin-only vs admin-or-view-permission) pending Mike's decision.
|
||||
> fixed only the token *mechanism*; the authz gate is what actually closes audit CRITICAL #1.
|
||||
> Policy DECIDED (Mike, 2026-05-29): admin-or-view-permission (`is_admin() || has_permission(...)`).
|
||||
> Parent: `docs/specs/SPEC-002-v2-modernization-architecture.md` (Phase 1)
|
||||
> Keystone: Tasks 1–4 are the "get-right-first" secure auth/session core — every audit CRITICAL/HIGH
|
||||
> is closed there. Tasks 5–7 deliver the product capability on top. Do them in order.
|
||||
@@ -87,7 +91,29 @@ Reference: `relay/mod.rs:224` (`validate_agent_api_key` — the CRITICAL), `auth
|
||||
|
||||
---
|
||||
|
||||
## Task 3 (KEYSTONE): Secure relay WS handlers + bounded relay
|
||||
## Task 3 (KEYSTONE) [IMPLEMENTED 2026-05-29 — self-reviewed; no Rust toolchain on this machine, not yet `cargo check`-verified]: Secure relay WS handlers + bounded relay
|
||||
|
||||
> [IMPLEMENTED] Viewer WS now verifies the session-scoped VIEWER token
|
||||
> (`validate_viewer_token`: sig+exp+`purpose`) + `token_blacklist.is_revoked` +
|
||||
> `session_id` claim == requested session, before upgrade — raw login JWTs are no
|
||||
> longer accepted (closes CRITICAL #1 mechanism + #2). Authz gate added to
|
||||
> `mint_viewer_token`: `is_admin() || has_permission("view")` → 403 envelope on
|
||||
> failure (closes CRITICAL #1 — uses the EXISTING `view` permission from GC's
|
||||
> catalog; no new permission defined). Agent WS now binds persistent reattach to
|
||||
> the authenticated machine identity: `validate_agent_api_key` returns an
|
||||
> `AgentKeyAuth` enum carrying the `cak_` key's machine `agent_id` (resolved via
|
||||
> new `db::machines::get_machine_by_id`); a mismatched query-string `agent_id` is
|
||||
> ignored, a per-agent key whose machine can't be resolved fails closed
|
||||
> (503). Frame caps set on BOTH upgrades (agent 4 MiB, viewer 64 KiB via
|
||||
> `max_message_size`/`max_frame_size`) (closes WS-OOM HIGH). Viewer→agent input
|
||||
> throttled to 200 events/sec/viewer via a refilling token bucket + non-blocking
|
||||
> bounded `try_send` (drop/coalesce on overflow) (closes input-injection MEDIUM).
|
||||
> Startup managed-session reconcile retained + clarified (persistent machines →
|
||||
> offline in-memory sessions). Removed `#[allow(dead_code)]` on
|
||||
> `validate_viewer_token` and `AuthenticatedUser::has_permission`. No token/secret
|
||||
> logged; runtime `sqlx::query`/`query_as`. Files: `server/src/relay/mod.rs`,
|
||||
> `server/src/api/sessions.rs`, `server/src/db/machines.rs`, `server/src/auth/mod.rs`,
|
||||
> `server/src/auth/jwt.rs`, `server/src/main.rs`.
|
||||
|
||||
Files touched: `server/src/relay/mod.rs`, `server/src/session/mod.rs`.
|
||||
|
||||
@@ -99,8 +125,10 @@ Files touched: `server/src/relay/mod.rs`, `server/src/session/mod.rs`.
|
||||
must enforce a real permission predicate, not just `AuthenticatedUser`: `user.is_admin() ||
|
||||
user.has_permission(<policy>)`. GC's role model (`admin|operator|viewer`) + permissions table already
|
||||
exist (`server/src/auth/mod.rs`), so honoring the intra-tenant role distinction is cheap. **Policy
|
||||
decision (Mike): admin-only, or admin-or-`view_sessions`-permission.** Multi-tenant client-access
|
||||
isolation stays deferred to Phase 4; this is only the intra-tenant role gate.
|
||||
DECIDED (Mike, 2026-05-29): admin-or-view-permission** — `user.is_admin() || user.has_permission(<the
|
||||
session-view/control permission in GC's catalog — use the real name; define one if absent>)`. Enforce
|
||||
at the minting endpoint `mint_viewer_token` (the authz decision point); the WS then trusts the
|
||||
session-scoped token. Multi-tenant client-access isolation stays deferred to Phase 4.
|
||||
- **`agent_ws_handler`** (`relay/mod.rs:55`): authenticate via per-agent key OR support code only
|
||||
(Task 2). Persistent reattach must bind to the authenticated machine identity, not a query-string
|
||||
`agent_id` alone (`session/mod.rs:98`).
|
||||
|
||||
Reference in New Issue
Block a user