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

@@ -169,8 +169,9 @@ pub async fn agent_ws_handler(
// Validate API key if provided (for persistent agents)
if let Some(ref key) = api_key {
// For now, we'll accept API keys that match the JWT secret or a configured agent key
// In production, this should validate against a database of registered agents
// Agent-plane auth ONLY: a per-agent `cak_` key (hash-compared against
// connect_agent_keys, rejecting revoked) or the deprecated shared
// AGENT_API_KEY fallback. A dashboard/user JWT is NEVER accepted here.
if !validate_agent_api_key(&state, key).await {
warn!(
"Agent connection rejected: {} from {} - invalid API key",
@@ -220,21 +221,45 @@ pub async fn agent_ws_handler(
}))
}
/// Validate an agent API key
/// Validate an agent key presented on the agent plane.
///
/// SECURITY (v2): a dashboard/user JWT is NEVER a valid agent credential — the
/// old `jwt_config.validate_token` branch was the relay CRITICAL and is gone.
/// Accepts, in order:
/// 1. A per-agent `cak_` key: SHA-256 hash compared against
/// `connect_agent_keys`; revoked keys are rejected (the DB query filters
/// `revoked_at IS NULL`). This is the supported path.
/// 2. The shared `AGENT_API_KEY` env value — DEPRECATED fallback, retained
/// only for not-yet-migrated agents. Its use is logged at WARNING and it
/// should be removed once all managed agents carry per-agent keys.
///
/// Never logs the presented key or any hash.
async fn validate_agent_api_key(state: &AppState, api_key: &str) -> bool {
// Check if API key is a valid JWT (allows using dashboard token for testing)
if state.jwt_config.validate_token(api_key).is_ok() {
return true;
}
// Check against configured agent API key if set
if let Some(ref configured_key) = state.agent_api_key {
if api_key == configured_key {
// 1. Per-agent key (the supported path). Requires a database; without one,
// only the deprecated shared-key fallback below can apply.
if let Some(ref db) = state.db {
if crate::auth::agent_keys::verify_agent_key(db.pool(), api_key)
.await
.is_some()
{
return true;
}
}
// 2. DEPRECATED shared-key fallback. Constant-time-ish equality is not
// critical here (the key is high-entropy and this path is sunset), but
// we still avoid logging the value.
if let Some(ref configured_key) = state.agent_api_key {
if api_key == configured_key {
warn!(
"[WARNING] Agent authenticated via the DEPRECATED shared AGENT_API_KEY \
fallback. Migrate this agent to a per-agent cak_ key; the shared key \
will be removed."
);
return true;
}
}
// In future: validate against database of registered agents
false
}