feat(server): v2 secure-session-core Task 1 - schema + per-agent keys
All checks were successful
Build and Test / Build Agent (Windows) (push) Successful in 6m7s
Build and Test / Build Server (Linux) (push) Successful in 10m15s
Build and Test / Security Audit (push) Successful in 4m24s
Build and Test / Build Summary (push) Successful in 12s

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

Migration 004 (idempotent, server-applied): tenants + seeded default tenant,
connect_agent_keys (hash-only, revocable, FK->connect_machines), nullable
tenant_id on all scoped tables (tenancy-ready, not tenant-yet), connect_sessions
is_managed/source/consent_state, connect_support_codes consumed_at. New db
modules agent_keys.rs (stores only key_hash) + tenancy.rs (DEFAULT_TENANT_ID,
Phase-4 switch point). Struct/query updates across machines/sessions/
support_codes/events/users. Runtime sqlx throughout (GC db layer already uses
it - no compile-time macros).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-29 18:33:26 -07:00
parent 81e4b99a34
commit fef8111ff3
10 changed files with 283 additions and 6 deletions

View File

@@ -0,0 +1,114 @@
-- Migration: 004_v2_secure_session_core.sql
-- Purpose: v2 Secure Session Core (Task 1) - per-agent keys + tenancy-ready schema.
-- Adds the tenants table + a fixed default tenant, the connect_agent_keys
-- table, a nullable tenant_id on every scoped table (backfilled to the
-- default tenant), and the new session/support-code state columns.
--
-- Idempotent: CREATE TABLE IF NOT EXISTS / ADD COLUMN IF NOT EXISTS / ON CONFLICT.
-- Applied on server startup by sqlx::migrate!(); never pre-applied via psql.
-- See .claude/standards/gururmm/sqlx-migrations.md.
-- pgcrypto provides gen_random_uuid(); enabled in 001 but re-asserted for safety.
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- ============================================================================
-- Tenants (tenancy-ready; isolation NOT enforced until Phase 4)
-- ============================================================================
CREATE TABLE IF NOT EXISTS tenants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- Seed exactly one default tenant with a FIXED, hard-coded UUID. This constant
-- is the Phase-4 multi-tenancy switch point (mirrored by
-- db::tenancy::DEFAULT_TENANT_ID). Idempotent via ON CONFLICT.
INSERT INTO tenants (id, name)
VALUES ('00000000-0000-0000-0000-000000000001', 'Default Tenant')
ON CONFLICT (id) DO NOTHING;
-- ============================================================================
-- Per-agent keys (connect_agent_keys) - replaces the shared AGENT_API_KEY
-- ============================================================================
-- Stores ONLY the key hash. Hashing + cak_ issuance is Task 2; this table
-- persists an already-hashed value. revoked_at set marks a key inactive.
CREATE TABLE IF NOT EXISTS connect_agent_keys (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
machine_id UUID NOT NULL REFERENCES connect_machines(id) ON DELETE CASCADE,
key_hash TEXT NOT NULL UNIQUE,
tenant_id UUID,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_used_at TIMESTAMPTZ,
revoked_at TIMESTAMPTZ
);
CREATE INDEX IF NOT EXISTS idx_connect_agent_keys_machine ON connect_agent_keys(machine_id);
CREATE INDEX IF NOT EXISTS idx_connect_agent_keys_key_hash ON connect_agent_keys(key_hash);
-- Backfill the agent-keys tenant_id to the default tenant (table is empty on a
-- fresh DB; this is a no-op there but keeps the migration self-consistent).
UPDATE connect_agent_keys
SET tenant_id = '00000000-0000-0000-0000-000000000001'
WHERE tenant_id IS NULL;
-- ============================================================================
-- tenant_id on all scoped tables (nullable; backfilled to the default tenant)
-- ============================================================================
ALTER TABLE connect_machines ADD COLUMN IF NOT EXISTS tenant_id UUID;
ALTER TABLE connect_sessions ADD COLUMN IF NOT EXISTS tenant_id UUID;
ALTER TABLE connect_support_codes ADD COLUMN IF NOT EXISTS tenant_id UUID;
ALTER TABLE connect_session_events ADD COLUMN IF NOT EXISTS tenant_id UUID;
ALTER TABLE users ADD COLUMN IF NOT EXISTS tenant_id UUID;
UPDATE connect_machines
SET tenant_id = '00000000-0000-0000-0000-000000000001'
WHERE tenant_id IS NULL;
UPDATE connect_sessions
SET tenant_id = '00000000-0000-0000-0000-000000000001'
WHERE tenant_id IS NULL;
UPDATE connect_support_codes
SET tenant_id = '00000000-0000-0000-0000-000000000001'
WHERE tenant_id IS NULL;
UPDATE connect_session_events
SET tenant_id = '00000000-0000-0000-0000-000000000001'
WHERE tenant_id IS NULL;
UPDATE users
SET tenant_id = '00000000-0000-0000-0000-000000000001'
WHERE tenant_id IS NULL;
CREATE INDEX IF NOT EXISTS idx_connect_machines_tenant ON connect_machines(tenant_id);
CREATE INDEX IF NOT EXISTS idx_connect_sessions_tenant ON connect_sessions(tenant_id);
CREATE INDEX IF NOT EXISTS idx_connect_support_codes_tenant ON connect_support_codes(tenant_id);
CREATE INDEX IF NOT EXISTS idx_connect_session_events_tenant ON connect_session_events(tenant_id);
CREATE INDEX IF NOT EXISTS idx_users_tenant ON users(tenant_id);
-- ============================================================================
-- Session state columns (managed/unattended + source + consent)
-- ============================================================================
ALTER TABLE connect_sessions
ADD COLUMN IF NOT EXISTS is_managed BOOLEAN NOT NULL DEFAULT false;
-- source: 'standalone' (ad-hoc support code) | 'gururmm' (managed via RMM).
ALTER TABLE connect_sessions
ADD COLUMN IF NOT EXISTS source TEXT NOT NULL DEFAULT 'standalone'
CHECK (source IN ('standalone', 'gururmm'));
-- consent_state: not_required (managed/unattended) | pending | granted | denied.
-- Attended-consent enforcement is Task 5; this is the schema column only.
ALTER TABLE connect_sessions
ADD COLUMN IF NOT EXISTS consent_state TEXT NOT NULL DEFAULT 'not_required'
CHECK (consent_state IN ('not_required', 'pending', 'granted', 'denied'));
-- ============================================================================
-- Support-code single-use marker (consumed in Task 4 - schema only here)
-- ============================================================================
ALTER TABLE connect_support_codes ADD COLUMN IF NOT EXISTS consumed_at TIMESTAMPTZ;