feat(server): v2 secure-session-core Task 2 - auth rebuild
Some checks failed
Build and Test / Build Server (Linux) (push) Failing after 3m37s
Build and Test / Build Agent (Windows) (push) Successful in 6m37s
Build and Test / Security Audit (push) Successful in 4m10s
Build and Test / Build Summary (push) Has been skipped

SPEC-002 Phase 1 Task 2 (specs/v2-secure-session-core), code-reviewed APPROVED.

- DELETE the JWT-as-agent-key branch in relay validate_agent_api_key (audit
  CRITICAL): agent auth now = per-agent cak_ key (SHA-256 -> connect_agent_keys,
  revoked filtered) OR support code OR deprecated shared AGENT_API_KEY (warned).
  A user JWT can no longer authenticate an agent.
- auth/agent_keys.rs: cak_ gen (OsRng 256-bit) + SHA-256 hash + verify.
- auth/jwt.rs: ViewerClaims + create/validate_viewer_token (5-min TTL,
  purpose=viewer, session_id+tenant_id claims; non-interchangeable with login).
- Admin key issuance: POST/GET/DELETE /api/machines/:agent_id/keys.
- POST /api/sessions/:id/viewer-token mints a session-bound short-lived token.
- Migration 005: organization/site/tags on connect_machines (fixes the silent
  update_machine_metadata write, coord todo faf39fe0).

NOTE: viewer-token minting is gated by AuthenticatedUser only; the AUTHORIZATION
check (admin/permission gate) that closes audit CRITICAL #1 lands in Task 3 (the
viewer WS verification). The viewer WS path (relay/mod.rs:285) is untouched here.
Not cargo-check-verified (no toolchain on the authoring host) - self-reviewed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-29 18:57:12 -07:00
parent fef8111ff3
commit 41691bfb2c
12 changed files with 788 additions and 16 deletions

View File

@@ -312,6 +312,11 @@ async fn main() -> Result<()> {
.route("/api/sessions", get(list_sessions))
.route("/api/sessions/:id", get(get_session))
.route("/api/sessions/:id", delete(disconnect_session))
// Session-scoped viewer-token minting (dashboard JWT; bound to one session)
.route(
"/api/sessions/:id/viewer-token",
post(api::sessions::mint_viewer_token),
)
// REST API - Machines
.route("/api/machines", get(list_machines))
.route("/api/machines/:agent_id", get(get_machine))
@@ -321,6 +326,21 @@ async fn main() -> Result<()> {
"/api/machines/:agent_id/update",
post(trigger_machine_update),
)
// Per-agent key issuance (admin only). `:agent_id` matches the param
// name used by the other /api/machines/:agent_id routes — matchit 0.7
// panics if the same path position uses two different param names.
.route(
"/api/machines/:agent_id/keys",
post(api::machine_keys::create_key),
)
.route(
"/api/machines/:agent_id/keys",
get(api::machine_keys::list_keys),
)
.route(
"/api/machines/:agent_id/keys/:key_id",
delete(api::machine_keys::revoke_key),
)
// REST API - Releases and Version
.route("/api/version", get(api::releases::get_version)) // No auth - for agent polling
.route("/api/releases", get(api::releases::list_releases))