synops/docs/infra/nøkkelhåndtering.md
vegard f98ad11081 Spec: nøkkelhåndtering — API-nøkler kryptert i PG, admin-UI
Erstatter .env-filer for API-nøkler. AES-256-GCM kryptert i PG,
administrert via admin-UI, injisert av maskinrommet som env ved
verktøy-spawning. Audit trail, test-tilkobling, flere nøkler per
provider, deaktivering uten sletting. Ingen endring i verktøykode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 17:48:00 +00:00

5.4 KiB

Nøkkelhåndtering — API-nøkler i PG, administrert via UI

Problemstilling

API-nøkler (OpenRouter, Anthropic, Gemini, xAI, OpenAI, ElevenLabs etc.) ligger i klartekst i /srv/synops/.env. Ingen audit trail, ingen granulær tilgang, lett å glemme å oppdatere.

Modell

Maskinrommet er nøkkelforvalter. Nøkler lagres kryptert i PG, administreres via admin-UI, injiseres i verktøy ved oppstart.

Admin-UI → maskinrommet → PG (kryptert)
                ↓
        spawner verktøy med nøkler som env
                ↓
        synops-agent, synops-ai, synops-clip osv.

Database-skjema

CREATE TABLE api_keys (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    provider    TEXT NOT NULL,          -- 'openrouter', 'anthropic', 'gemini', 'xai', 'openai', 'elevenlabs'
    label       TEXT,                   -- valgfritt navn ("Vegards OpenRouter", "Prod Anthropic")
    key_encrypted BYTEA NOT NULL,       -- AES-256-GCM kryptert
    key_hint    TEXT,                   -- siste 4 tegn for identifisering ("...c08b")
    is_active   BOOLEAN DEFAULT true,   -- deaktivert uten å slette
    created_at  TIMESTAMPTZ DEFAULT now(),
    updated_at  TIMESTAMPTZ DEFAULT now(),
    created_by  UUID REFERENCES nodes(id), -- hvem la inn nøkkelen
    last_used   TIMESTAMPTZ,           -- sist brukt (oppdateres ved bruk)
    usage_count BIGINT DEFAULT 0       -- antall ganger brukt
);

CREATE INDEX idx_api_keys_provider ON api_keys(provider, is_active);

Kryptering

Nøkkelen krypteres med AES-256-GCM. Krypteringsnøkkelen (master key) lever som:

  • Env-variabel SYNOPS_MASTER_KEY på hosten
  • Eller i systemd credentials (LoadCredential)
  • Aldri i PG, aldri i .env
// Maskinrommet krypterer ved lagring
let encrypted = aes_gcm_encrypt(&api_key, &master_key);

// Maskinrommet dekrypterer ved bruk
let api_key = aes_gcm_decrypt(&encrypted, &master_key);

Kun maskinrommet kjenner master key. Admin-UI sender klartekst til maskinrommet via HTTPS, maskinrommet krypterer og lagrer.

Nøkkeloppslag

Når et verktøy trenger en API-nøkkel:

// I maskinrommet, ved spawning av synops-agent:
let key = db::get_active_key("openrouter").await?;
cmd.env("OPENROUTER_API_KEY", key.decrypt(&master_key)?);

Verktøyet ser nøkkelen som vanlig env-variabel. Ingen endring i verktøykoden — de leser allerede fra env.

Alternativt for synops-agent (som kjører lenge): maskinrommet eksponerer et internt endepunkt:

GET /internal/api-key/{provider}
Authorization: Bearer <intern-token>
→ { "key": "sk-..." }

Admin-UI

Nøkkelliste

┌─ API-nøkler ──────────────────────────────────┐
│                                                │
│ Provider      Label              Status  Sist  │
│ OpenRouter    Vegards OR         ✅ Aktiv  nå  │
│ Anthropic     Prod               ✅ Aktiv  2t  │
│ Gemini        —                  ⚫ Inaktiv    │
│ xAI           —                  ⚫ Mangler    │
│ OpenAI        —                  ⚫ Mangler    │
│ ElevenLabs    Prod TTS           ✅ Aktiv  1d  │
│                                                │
│ [+ Legg til nøkkel]                           │
└────────────────────────────────────────────────┘

Legg til / rediger

┌─ Ny nøkkel ───────────────────────────────────┐
│                                                │
│ Provider:  [OpenRouter ▼]                      │
│ Label:     [Vegards OpenRouter         ]       │
│ Nøkkel:   [sk-or-v1-...               ]       │
│                                                │
│ [Test tilkobling]    [Lagre]    [Avbryt]       │
└────────────────────────────────────────────────┘

"Test tilkobling" gjør et minimalt API-kall (list models) for å verifisere at nøkkelen fungerer før lagring.

Sikkerhetsregler

  • Nøkkelen vises aldri etter lagring — kun hint ("...c08b")
  • For å endre: slett gammel, legg inn ny
  • Deaktivering beholder nøkkelen men bruker den ikke
  • Sletting fjerner den permanent
  • Audit: created_by, created_at, last_used, usage_count

Flere nøkler per provider

En provider kan ha flere nøkler (f.eks. ulike OpenRouter-kontoer):

SELECT key_encrypted FROM api_keys
WHERE provider = 'openrouter' AND is_active = true
ORDER BY usage_count ASC  -- round-robin / minst brukt
LIMIT 1

Gir enkel lastbalansering og mulighet for å rotere nøkler uten nedetid.

Migrering fra .env

  1. Admin legger inn nøkler via UI
  2. Maskinrommet bruker PG-nøkler i stedet for env
  3. .env-filen beholder nøklene som fallback
  4. Når alt fungerer: fjern nøkler fra .env
  5. Beholder kun SYNOPS_MASTER_KEY som env

Hva som endres

Komponent Endring
PG Ny api_keys-tabell
Maskinrommet Kryptering, nøkkeloppslag, API-endepunkt
Admin-UI Ny side for nøkkelhåndtering
synops-agent Ingen (leser fortsatt fra env)
CLI-verktøy Ingen (leser fortsatt fra env)
.env Nøkler fjernes gradvis

Prioritet

Middels. Fungerer med .env nå. Viktigere når flere brukere skal administrere egne nøkler (BYOK-modell) eller når nøkler roteres jevnlig.