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:
@@ -196,12 +196,18 @@ async fn main() -> Result<()> {
|
||||
// Create session manager
|
||||
let sessions = session::SessionManager::new();
|
||||
|
||||
// Restore persistent machines from database
|
||||
// Reconcile managed (persistent) sessions from the database on startup so
|
||||
// they are not orphaned after a server restart (Task 3F). Each persistent
|
||||
// machine is reloaded into the in-memory SessionManager as an OFFLINE
|
||||
// session; when the agent reconnects with its per-agent key, `register_agent`
|
||||
// reattaches to this preserved session (now bound to the authenticated
|
||||
// identity — see relay::agent_ws_handler). Support-code (attended) sessions
|
||||
// are intentionally NOT reconciled: they are ephemeral and end on disconnect.
|
||||
if let Some(ref db) = database {
|
||||
match db::machines::get_all_machines(db.pool()).await {
|
||||
Ok(machines) => {
|
||||
info!(
|
||||
"Restoring {} persistent machines from database",
|
||||
"Reconciling {} managed session(s) from database",
|
||||
machines.len()
|
||||
);
|
||||
for machine in machines {
|
||||
@@ -211,7 +217,7 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!("Failed to restore machines: {}", e);
|
||||
tracing::warn!("Failed to reconcile managed sessions: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user