feat(schluessel): erzeugen, importieren, exportieren (#012)

Schluessel werden jetzt verwaltet statt nur im Prototyp gezeigt: Liste
mit Filter, Anlegen (erzeugen oder importieren), Detail nach dem
Prototyp aus #002, Bearbeiten der Metadaten und Download <slug>.pub.

Warum so:
- Erzeugen und Import eines Paars laufen als Vorgang im Worker, weil
  nur er den Master-Key hat. Passphrase und importierter Private Key
  reisen wie das Bootstrap-Passwort mit SSHKV_APP_SECRET verschluesselt
  im Job, nie in Vorgang.eingabe; der Worker loescht den Job danach.
- Ein reiner Public Key ist kein Geheimnis und wird direkt im Web
  angelegt, mit verstaendlicher Meldung bei einem Duplikat.
- Das Web liest nie das Chiffrat: ob ein Private Key gespeichert ist,
  verraet masterKeyVersion (per CHECK an das Chiffrat gekoppelt). Ein
  struktureller Test verlangt fuer jede zeilenliefernde Abfrage auf
  Schluessel ausserhalb des Workers ein festes select.
- Migration: Vorgangsarten fuer Erzeugen, Import und (fuer #014) das
  Loeschen des Private Keys; CHECKs fuer den Slug (landet in
  Dateinamen) und "geloescht nur ohne Chiffrat".
- Platzhalter PublicAusrollenAktion/PrivateAusrollenAktion fuer #016/#017.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 10:29:52 +02:00
co-authored by Claude Opus 5.5
parent f0833aa1db
commit 263143134f
33 changed files with 3322 additions and 287 deletions
@@ -0,0 +1,19 @@
-- Schluessel erzeugen, importieren, Private Key loeschen (#012, #014).
--
-- Neue Vorgangsarten: Erzeugen und Import eines Paars laufen im Worker, weil nur er den
-- Master-Key hat. Das Loeschen eines gespeicherten Private Keys laeuft ebenfalls dort, damit
-- die Grenze „nur src/worker/geheimnisse.ts fasst das Chiffrat an“ einfach bleibt.
ALTER TYPE "VorgangsArt" ADD VALUE 'SCHLUESSEL_ERZEUGEN';
ALTER TYPE "VorgangsArt" ADD VALUE 'SCHLUESSEL_IMPORTIEREN';
ALTER TYPE "VorgangsArt" ADD VALUE 'PRIVATE_KEY_LOESCHEN';
-- Der Slug landet in Dateinamen (<slug>.pub, ~/.ssh/id_<slug>) und Kommentaren. Was hier
-- nicht passt, kommt nie bis in einen Pfad oder ein Shell-Skript.
ALTER TABLE "Schluessel" ADD CONSTRAINT "schluessel_slug_gueltig" CHECK (
"slug" ~ '^[a-z0-9-]{1,64}$'
);
-- „Private Key geloescht“ und ein gespeichertes Chiffrat schliessen sich aus (#014).
ALTER TABLE "Schluessel" ADD CONSTRAINT "schluessel_geloescht_ohne_private_key" CHECK (
"privatGeloeschtAm" IS NULL OR "privatVerschluesselt" IS NULL
);