Files
florian.wildeandClaude Opus 5.5 7e4968f2ea docs(devlog): Sitzung 8 mit #035 und #036 (#036)
Zeiten und Entscheidungen der Sitzung festhalten.

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

13 KiB
Raw Permalink Blame History

DEVLOG – SSHKeyVault

Zeitübersicht

Gesamt (Wandzeit): 6 h 40 min
Summe der Issue-Zeiten: 16 h 38 min – Sub-Agenten arbeiteten parallel an #002, #006, #008, #011, #012–#014, #019 (Oberfläche), #023–#025, #027, #028 und #032.

Issue Zeit
Planung (Phase 0) 0 h 20 min
#001 Projekt-Setup 0 h 15 min
#002 Design-System und Layout 0 h 18 min
#003 Datenmodell und Migrationen 0 h 10 min
#004 Anmeldung, Rollen und Bestätigung 0 h 45 min
#005 Revisionssicheres Protokoll 0 h 10 min
#006 Verschlüsselte Ablage und Schlüsselformat 0 h 25 min
#007 Worker und Vorgänge 0 h 15 min
#008 Hosts und Hostgruppen 0 h 30 min
#009 Host-Key-Prüfung 0 h 25 min
#010 Bootstrap 0 h 35 min
#011 Test-SSH-Container 0 h 15 min
#012 Schlüssel erzeugen, importieren, exportieren 0 h 25 min
#013 Personen und Self-Service 0 h 10 min
#014 Private Key anzeigen 0 h 10 min
#015 authorized_keys-Parser 0 h 15 min
#016 Public Key ausrollen 0 h 40 min
#017 Private Key ausrollen 0 h 45 min
#018 Zielbenutzer und sudoers 0 h 30 min
#019 Vorlagen 0 h 20 min
#020 Host-zu-Host-Assistent 0 h 50 min
#021 Abgleich und Drift-Erkennung 0 h 45 min
#022 Import bestehender Schlüssel 0 h 20 min
#023 Geführte Rotation 0 h 20 min
#024 Widerruf und Massenwiderruf 0 h 25 min
#025 Zuweisungsmatrix 0 h 30 min
#026 Übersicht 0 h 25 min
#027 Protokollansicht, CSV, Syslog 0 h 20 min
#028 Benachrichtigungen 0 h 30 min
#029 Handbuch und Betriebsdokumentation 0 h 50 min
#030 Gitea-Abgleich 0 h 05 min
#031 Rotation des Deployment-Keys 1 h 10 min
#032 Nacharbeiten Phase 4/5 0 h 45 min
#033 Erneut rotieren nach Abbruch 0 h 20 min
#034 Abnahme-Checkliste 0 h 10 min
#035 Lokale Benutzerkonten 0 h 35 min
#036 Deployment-Key-Rotation abbrechen 0 h 20 min
Meilenstein Zeit
v0.1.0 1 h 25 min (Wandzeit)
v0.2.0 1 h 00 min (Wandzeit)
v0.3.0 0 h 30 min (Wandzeit)
v0.4.0 0 h 45 min (Wandzeit)
v0.5.0 0 h 45 min (Wandzeit)
v1.0.0 2 h 15 min (Wandzeit)

2026-10-12 · Sitzung 1 · 08:45–10:10

Issue: Planung, #001–#011 Version: 0.1.0

Erledigt

  • Auftrag gelesen, Architektur, Datenmodell, Sicherheitskonzept und Phasenplan entworfen (docs/PLAN.md)
  • Sieben Rückfragen gestellt und beantwortet, Plan freigegeben
  • Konventionen aus Laufzettel und ProxLedger übernommen; Git-Repository mit lokaler Identität angelegt
  • 30 Issues für Phase 1–5 als Dateien angelegt (Gitea folgt mit #030)
  • #001 Grundgerüst; #003 Datenmodell mit Datenbankregeln und Protokoll-Hash-Kette als Trigger
  • #002, #006 (Kern), #011 parallel von Sub-Agenten in eigenen Worktrees, danach gemergt
  • #004 Anmeldung: OIDC gegen Mock-Anbieter, Notfall-Admin mit TOTP, Re-Auth; E2E im gebauten Stand
  • #005 Protokoll-Service mit Schwärzen; #006 Teil 2: Versiegeln im Worker, Deployment-Key
  • #007 Worker mit pg-boss; vollständiger Docker-Stapel geprüft (migrate, web, worker gesund)
  • #008 Hosts und Hostgruppen (Sub-Agent)
  • #009 strikte Host-Key-Prüfung und TOFU, #010 Bootstrap – Integrationstests gegen echtes sshd, E2E im Browser bis zur Verbindungsprüfung
  • Stand: 287 Unit-/DB-Tests, 17 Integrationstests, 5 E2E-Abläufe grün

Entscheidungen

  • Kein Auth.js: v5 steht dauerhaft auf Beta – wie in Laufzettel openid-client + jose.
  • Web und Worker getrennt, Master-Key nur im Worker: Eine Lücke im Webprozess allein entschlüsselt keine Private Keys.
  • Schlüsselerzeugung ohne ssh-keygen: Node crypto + eigener OpenSSH-Serializer, damit Private Keys nie als Datei auf dem sshkv-Server liegen. ssh-keygen prüft in den Tests die Kompatibilität.
  • Dienstbenutzer sshkv mit sudo als Standard, root pro Host wählbar.
  • Ohne sshkv-Gruppe kein Zugang – auch kein Lesezugriff.
  • Protokoll-Hash im Datenbank-Trigger: Niemand kann eine Zeile ohne Verkettung einfügen; die ID wird erst unter Sperre vergeben, damit die Kette auch bei parallelem Schreiben hält.
  • Ein Bootstrap-Skript für Passwort- und Copy-Paste-Weg, Dateizugriffe als Zielbenutzer (Symlink-Schutz).
  • Eigene Testdatenbank je parallelem Lauf: Sub-Agent und Hauptsitzung leerten sich gegenseitig die Tabellen (in CLAUDE.md festgehalten).
  • Prisma-Client in den Worker gebündelt: Im Standalone-Abbild fand die ESM-Auflösung @prisma/client sonst nicht (erst im echten Docker-Stapel aufgefallen).
  • Worker-Benutzer in Compose wählbar (SSHKV_WORKER_USER), weil Docker-Secrets ohne Swarm die Rechte der Host-Datei tragen.

2026-10-12 · Sitzung 2 · 10:07–11:07

Issue: #012–#020 Version: 0.2.0

Erledigt

  • #012–#014 (Schlüssel, Personen/Self-Service, Private Key anzeigen) von einem Sub-Agenten in eigenem Worktree, danach gemergt; Konflikte in useVorgang und der Handler-Tabelle zusammengeführt
  • #015 authorized_keys-Parser und Merge mit 26 Randfall-Tests
  • #016 Public Keys ausrollen: Soll, Trockenlauf, Ausrollen, Hostseite, Schlüsselseite – Integrationstests und E2E
  • #017 Private Key mit ~/.ssh/config und known_hosts – Integrationstest „A meldet sich bei B an“, E2E für den ganzen Kern-Anwendungsfall
  • Vorgezogen: #018 Zielkonto-Skripte (Integrationstests), #019 Vorlagen-Logik, #020 Verbindungstest
  • Stand: 410 Unit-/DB-Tests, 30 Integrationstests, 12 E2E-Abläufe grün

Entscheidungen

  • Phase 3 teilweise vorgezogen, weil Florian „weiter“ sagte, während der Agent noch an Phase 2 arbeitete – nur Teile ohne Schemaänderung, damit die Migrationen des Agenten nicht kollidieren.
  • Trockenlauf und Ausrollen als zwei Vorgänge mit Hash-Sperre: Es wird nie etwas anderes geschrieben, als jemand gesehen hat.
  • Private-Key-Ziele im Dry-Run nur für Admins: Sonst hätte ein Operator einen vorbereiteten Private Key ausrollen lassen können (Freigabe Frage 3).
  • Keine Sicherungskopie alter Private Keys auf dem Host; config-Block am Dateianfang, weil ssh den ersten Treffer nimmt.
  • Ein Hook useVorgang für alle: zwei parallel entstandene Fassungen zusammengeführt, beide Schnittstellen bleiben.

2026-10-12 · Sitzung 3 · 11:06–11:36

Issue: #018–#020 Version: 0.3.0

Erledigt

  • #019 Vorlagen-Oberfläche (Liste, Formular, Live-Vorschau) von einem Sub-Agenten, gemergt
  • #018 Zielkonten als Vorgänge mit Migration, Herkunft gemerkt; Integrationstest anlegen → entfernen
  • #020 Host-zu-Host-Assistent mit Vorlagen, wiederaufnehmbarer Ausführung und Verbindungstest; E2E mit „Deployment-Zugang“
  • Stand: 423 Unit-/DB-Tests, 31 Integrationstests, 16 E2E-Abläufe grün

Entscheidungen

  • Migration auf Ortszeit-Reihenfolge umbenannt (20261012110000): Prisma vergibt UTC-Zeitstempel, die Migrationen des Agenten trugen Ortszeit – sonst stünde die neue vor älteren.
  • Bestätigung im neuen Tab: Der Assistent hält seine Eingaben im Speicher; eine Umleitung nach /bestaetigen hätte sie gelöscht.
  • Ausführung des Assistenten als Kette im Klick statt als Hook-Zustandsmaschine – einfacher, und erledigte Schritte lassen sich beim Fortsetzen überspringen.
  • Prozesse stoppen nur über ein Skript, das Programm und Arbeitsverzeichnis prüft: Zweimal hatte ein zu breites Suchmuster die eigene Shell getroffen.

2026-10-12 · Sitzung 4 · 12:50–13:35

Issue: #021–#025, #030 Version: 0.4.0

Erledigt

  • Zweites, getrenntes Paar SSH-Testziele (sshkv-test-b, Ports 2231/2232, Mock-OIDC 8091) für den Rotation/Widerruf-Agenten, damit Integrationstests sich nicht gegenseitig zurücksetzen
  • #021 Abgleich mit reiner Befundlogik, Worker-Ablauf, Zeitplan; #022 Übernahme mit neuem Feld ausserhalbEntfernen – Integrationstests und E2E
  • #023/#024 Rotation und Widerruf (Sub-Agent) mit Zwischenzustand widerrufEingeleitetAm; #025 Zuweisungsmatrix (Sub-Agent)
  • #030 Gitea-Skript bereitgelegt (Trockenlauf läuft)
  • Stand: 470 Unit-/DB-Tests, 38 Integrationstests, 21 E2E-Abläufe grün

Entscheidungen

  • Abgleich ändert nichts auf dem Host: Er meldet nur; Korrekturen laufen über Dry-Run und Ausrollen, damit ein Mensch sie sieht.
  • Bekannte Schlüssel außerhalb des Blocks sind kein Befund – nur unbekannte. Sonst meldete jeder bewusst stehen gelassene Admin-Key dauerhaft Drift.
  • Widerruf mit Zwischenzustand: Ein Schlüssel wird erst WIDERRUFEN, wenn er überall entfernt ist; ein nicht erreichbarer Host lässt ihn erkennbar „unvollständig“.
  • Rotation entfernt den alten Schlüssel erst nach erfolgreichen Verbindungstests; doppelte Aliase in ~/.ssh/config: die jüngste Zuweisung gewinnt.
  • Migrationszeitstempel in Ortszeit-Reihenfolge auch bei parallel entstandenen Migrationen (…130000 Agent, …131000 Hauptsitzung).

2026-10-12 · Sitzung 5 · 16:27–17:12

Issue: #026–#029 Version: 0.5.0

Erledigt

  • #026 Übersicht aus der Datenbank; lesbare Aktionstexte aus #027
  • #027 Audit-Log-Ansicht, Kettenprüfung, CSV, Syslog (Sub-Agent)
  • #028 Benachrichtigungen und Einstellungsseite (Sub-Agent, eigener Mailpit und eigene Testziele)
  • #029 Handbuch (12 Kapitel mit Bildern), Installation, Bedrohungsanalyse, Sicherung; Sicherung/Wiederherstellung geprobt
  • Merge-Konflikte (Compose, Screenshot-Skript, Worker-Einstieg) zusammengeführt; der strukturelle Test für Aktionstexte fand vier neue Aktionen ohne Text
  • Stand: 858 Unit-/DB-Tests, 43 Integrationstests, 24 E2E-Abläufe grün

Entscheidungen

  • Wiederherstellung wirklich geprobt statt nur beschrieben – sie fand zwei Fehler, die im Ernstfall die Wiederherstellung verhindert hätten.
  • Master-Key nicht in die Sicherung, nur ein Prüfwert; Worker verweigert den Start mit falschem Master-Key.
  • Benachrichtigungen lesen ihre Anlässe aus der Datenbank statt sich in den Abgleich einzuhängen; Doppelmeldungen verhindert ein Versandprotokoll mit Fingerabdruck je Anlass.
  • Keine 1.0.0 ohne Abnahme: 0.5.0 schließt die fünf Phasen; authentik, Gitea und ein Lauf auf echten Hosts stehen noch aus.

2026-10-12 · Sitzung 6 · 17:12–18:07

Issue: #030–#032 Version: unveröffentlicht (auf dem Weg zu 1.0.0)

Erledigt

  • Remote-Repo in Gitea: main und Tags v0.1.0–v0.5.0 gepusht, Issues abgeglichen (Basic-Auth, weil die hinterlegten Zugangsdaten ein Passwort sind)
  • #031 Geführte Rotation des Deployment-Keys: Service, Worker-Schritte, Seite mit Schrittleiste, Integrationstest gegen beide Testziele, E2E
  • #032 Nacharbeiten (Sub-Agent): lückenlose Protokoll-IDs, Rotation abbrechen, Anker „Zum Dry-Run“, Massenwiderruf-E2E – der fand, dass der Massenwiderruf UUIDs mit Bindestrich abwies
  • Stand: 876 Unit-/DB-Tests, 46 Integrationstests, 27 E2E-Abläufe grün

Entscheidungen

  • Der Nachfolger des Deployment-Keys ist ein eigener Schlüssel, der während der Rotation über sollFuer() in Block und Pflichtliste steht – so verteilt ihn der normale Dry-Run, und der Aussperrschutz deckt beide Keys.
  • Umstellen erst nach Prüfung auf allen Hosts; ein Host lässt sich nur bewusst und protokolliert übergehen.
  • Rotation abbrechen nutzt den vorhandenen Widerruf statt eines neuen Zustands (#032).
  • Protokoll-ID nur noch aus dem Trigger unter der Advisory-Sperre – keine Lücken mehr durch verworfene Sequenzwerte.

2026-10-12 · Sitzung 7 · 19:48–20:18

Issue: #033, #034 Version: unveröffentlicht (auf dem Weg zu 1.0.0)

Erledigt

  • #033 Nach einer abgebrochenen Rotation lässt sich derselbe Schlüssel erneut rotieren (1:n-Relation, partieller Index)
  • #034 Abnahme-Checkliste docs/betrieb/abnahme.md für authentik, echte Hosts und Betrieb
  • Stand: 877 Unit-/DB-Tests, 46 Integrationstests grün; E2E Rotation, Widerruf, Schlüssel grün

Entscheidungen

  • Je Vorgänger höchstens ein offener Nachfolger statt genau einem: abgebrochene bleiben in der Historie, maßgeblich ist der jüngste.
  • Neustart erst nach abgeschlossenem Abbruch – sonst stünden zwei neue Schlüssel gleichzeitig auf den Hosts.
  • Bestätigung „Rotation abgebrochen“ bleibt sichtbar über dem Neustart, statt kommentarlos auf Schritt 1 zu springen (fand der E2E-Test).

2026-10-12 · Sitzung 8 · 22:05–22:55

Issue: #035, #036 Version: unveröffentlicht (auf dem Weg zu 1.0.0)

Erledigt

  • #035 Lokale Benutzerkonten mit Pflicht-TOTP: Einmal-Passwort, eigenes Passwort bei der ersten Anmeldung, Benutzerverwaltung unter Einstellungen, Schalter SSHKV_LOKALE_KONTEN
  • #036 Rotation des Deployment-Keys vor dem Umstellen abbrechen
  • Stand: 911 Unit-/DB-Tests, 47 Integrationstests, 29 E2E-Abläufe grün

Entscheidungen

  • TOTP auch für lokale Konten Pflicht; lokale Konten dauerhaft, aber abschaltbar (Florian).
  • Admins wählen keine Passwörter für andere – sshkv erzeugt ein Einmal-Passwort, das genau einmal angezeigt wird.
  • SSHKV_LOKALE_KONTEN muss in docker-compose.yml stehen: Compose reicht nur ausdrücklich genannte Variablen an den Web-Container weiter – sonst hätte der Schalter im Betrieb nicht gewirkt.
  • Testreste machen Produktlücken sichtbar: Eine hängengebliebene Rotation auf verwaisten Testhosts zeigte, dass es keinen Weg zurück gab (#036). Die E2E-Tests räumen solche Reste jetzt selbst ab.