# DEVLOG – SSHKeyVault ## Zeitübersicht **Gesamt (Wandzeit):** 3 h 40 min **Summe der Issue-Zeiten:** 11 h 03 min – Sub-Agenten arbeiteten parallel an #002, #006, #008, #011, #012–#014, #019 (Oberfläche), #023–#025. | 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 | | #030 Gitea-Abgleich (vorbereitet) | 0 h 05 min | | Meilenstein | Zeit | |---|---| | v0.1.0 | 1 h 25 min (Wandzeit) | **abgeschlossen** | v0.2.0 | 1 h 00 min (Wandzeit) | **abgeschlossen** | v0.3.0 | 0 h 30 min (Wandzeit) | **abgeschlossen** | v0.4.0 | 0 h 45 min (Wandzeit) | **abgeschlossen** --- ## 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).