# 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 ```sql 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 ```rust // 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: ```rust // 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 → { "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): ```sql 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.