Files
sshkeyvault/docs/issues/014-private-key-anzeigen.md
T
florian.wildeandClaude Opus 5.5 0bb62450f3 feat(schluessel): Private Key anzeigen, herunterladen, loeschen (#014)
Admins koennen einen gespeicherten Private Key nach frischer
Bestaetigung einmalig anzeigen oder als Datei <slug> herunterladen und
ihn endgueltig aus SSHKeyVault loeschen.

Warum so:
- Der Web-Prozess hat keinen Master-Key. Er bittet den Worker per
  Vorgang, den Key zu oeffnen; der legt ihn mit SSHKV_APP_SECRET
  verschluesselt in Vorgang.ergebnis ab. Das Web holt ihn genau einmal
  ab: ein einziges UPDATE unter Zeilensperre liest und ueberschreibt.
- Nicht abgeholte Keys verfallen nach zwei Minuten: Worker-Takt alle
  30 s, Aufraeumen vor jeder Abholung, und das Web gibt aeltere nicht
  mehr heraus. Die Status-Schnittstelle zeigt nur "bereit".
- Download nur per POST (ein GET-Link oder Vorabruf wuerde die
  Abholung verbrauchen), no-store, Herkunft per Sec-Fetch-Site, weil
  no-referrer bei Formular-POSTs Origin: null sendet.
- Loeschen laeuft als Worker-Vorgang, damit nur geheimnisse.ts die
  Chiffrat-Felder anfasst; eigenes Recht privateKeyLoeschen mit
  Bestaetigung. Deployment-Keys gibt sshkv weder heraus noch loescht es
  sie.
- Protokoll: angefordert, angezeigt, heruntergeladen, geloescht - mit
  Fingerprint, nie mit Key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:51:04 +02:00

3.1 KiB
Raw Blame History

#014 Private Key anzeigen und herunterladen

Status: erledigt Meilenstein: v0.2.0 Labels: sicherheit Geschätzt: 2 h Tatsächlich: 0 h 10 min

Ziel

Nur Admin, nur nach Re-Auth, immer protokolliert; Entschlüsselung im Worker.

Aufgaben

  • Einmal-Abholung
  • Option „Private Key nach Ausrollen löschen“

Akzeptanzkriterien

  • Kein Caching, kein Logging

Abhängigkeiten

#012, #004

Notizen / Entscheidungen

  • Ablauf: Web legt einen Vorgang PRIVATE_KEY_ABHOLEN an (Protokoll privatekey.angefordert) → der Worker öffnet den Umschlag und legt den Key mit SSHKV_APP_SECRET verschlüsselt in Vorgang.ergebnis ab ({ abholung: "v1.…" }) → das Web holt ihn genau einmal ab: ein einziges UPDATE … FROM (SELECT … FOR UPDATE) RETURNING liest das alte Ergebnis und setzt { abgeholt: true }; fünf gleichzeitige Abholungen ergeben genau eine (Test). Abholen darf nur, wer angefordert hat. Protokoll privatekey.angezeigt / privatekey.heruntergeladen mit Fingerprint, nie mit Key.
  • Die Status-Schnittstelle /api/vorgaenge/[id] zeigt für eine bereitliegende Abholung nur { bereit: true }, nie das Chiffrat.
  • Verfall nach 2 Minuten (ABHOLFRIST_MS in src/lib/schluessel.ts), dreifach abgesichert: der Worker räumt alle 30 s und vor jeder neuen Abholung auf (abholungenAufraeumen, ein einziges UPDATE → { verfallen: true }), und das Web gibt eine ältere Abholung nicht mehr heraus (überschreibt sie aber trotzdem). Ein Worker-Takt statt pg-boss-Zeitplan: dessen Raster ist eine Minute.
  • Anzeige im Dialog (Monospace, Kopier-Knopf, „Wird nicht erneut angezeigt“); der Key lebt nur im Zustand des Dialogs und wird beim Schließen verworfen. Download als Datei <slug> über POST /api/private-key/[vorgangId] (Formular, kein GET – ein Link oder Vorabruf würde die einmalige Abholung verbrauchen), Content-Disposition: attachment, Cache-Control: no-store. Der Route Handler prüft selbst Recht, Bestätigung und Herkunft: wegen Referrer-Policy: no-referrer senden Browser bei Formular-POSTs Origin: null, maßgeblich ist deshalb Sec-Fetch-Site: same-origin (oder ein passender Origin).
  • Re-Auth: Fehlt die frische Bestätigung, leitet die Oberfläche nach /bestaetigen?ziel=/schluessel/<id>?privatekey=1 und öffnet danach den Dialog wieder. Der Mock-OIDC liefert auth_time nur, wenn man es in die Claims schreibt – die E2E tut das.
  • Löschen („Private Key aus SSHKeyVault löschen“): neues Recht privateKeyLoeschen (nur Admin, mit Bestätigung), ausgeführt als Worker-Vorgang PRIVATE_KEY_LOESCHEN (privateKeyVerwerfen in src/worker/geheimnisse.ts), damit nur diese Datei die Chiffrat-Felder anfasst – auch zum Leeren. Setzt Chiffrat, DEK und Version auf null und privatGeloeschtAm; Protokoll privatekey.geloescht. CHECK schluessel_geloescht_ohne_private_key (Migration aus #012).
  • Deployment-Keys gibt sshkv weder heraus noch löscht es deren Private Key: der Key ist root auf allen Hosts, und ohne ihn käme der Worker nirgends mehr hin. Im Notfall wird rotiert.