Commit Graph
100 Commits
Author SHA1 Message Date
florian.wildeandClaude Opus 5.5 b4804390c4 fix(protokoll): IDs ohne Zweierschritte vergeben (#032)
Spalten-Default (BIGSERIAL) und Trigger protokoll_verketten zogen je
eine Nummer aus der Sequenz, jede Zeile verbrauchte zwei IDs. Die
Nummer aus dem Default darf der Trigger aber nicht einfach
uebernehmen, denn sie wird vor der Advisory-Sperre gezogen: Eine parallele
Transaktion mit groesserer ID koennte zuerst verketten, und die Kette
liesse sich in ID-Reihenfolge nicht mehr pruefen.

Deshalb zieht nur noch der Trigger unter der Sperre; der Default ist
ein Platzhalter 0 (Prisma: @default(0)), damit create ohne ID
funktioniert. Die Sequenz bleibt der Spalte zugeordnet. Bestehende
Zeilen werden nicht umnummeriert, weil die ID in die Hash-Eingabe
eingeht.

Tests: drei Eintraege nacheinander ergeben n, n+1, n+2; eine
mitgegebene ID wird ueberschrieben; 20 parallele Eintraege belegen
genau 20 aufeinanderfolgende IDs bei intakter Kette.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:19:28 +02:00
florian.wilde 75077f9101 Merge branch 'phase/030-gitea-abgleich-2' 2026-10-12 17:13:18 +02:00
florian.wildeandClaude Opus 5.5 87b733cc9f chore(gitea): Issues nach Gitea, Basic-Auth fuer Konto-Passwort (#030)
In den Git-Zugangsdaten steht das Konto-Passwort, kein API-Token. Gitea
nimmt es nur per Basic-Auth an; das Skript erkennt das jetzt selbst.
Alle 30 Issues mit Labels und Meilensteinen uebertragen. Neu: #031
Rotation des Deployment-Keys und #032 Nacharbeiten fuer 1.0.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:13:15 +02:00
florian.wilde 9759a91e34 Merge branch 'phase/release-0.5.0' 2026-10-12 17:08:19 +02:00
florian.wildeandClaude Opus 5.5 a531857e1f chore(release): 0.5.0 - Phase 5 abgeschlossen
Uebersicht, Audit-Log, Benachrichtigungen, Handbuch und Betrieb.
CHANGELOG, DEVLOG und README nachgetragen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:08:17 +02:00
florian.wildeandClaude Opus 5.5 ea6e44fced test(anmeldung): Namen gezielt im Benutzerkasten pruefen (#026)
Seit die Uebersicht echte letzte Aktionen zeigt, steht der Name des
Mock-Admins mehrfach auf der Seite.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:08:06 +02:00
florian.wildeandClaude Opus 5.5 034a10b0b1 fix(protokoll): Texte fuer die Aktionen der Benachrichtigungen (#027)
Der Test, der jede Protokoll-Aktion im Code auf einen lesbaren Text
prueft, schlug nach dem Merge von #028 an - genau dafuer gibt es ihn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 17:02:54 +02:00
florian.wilde c8f4d8a386 Merge branch 'phase/029-handbuch-abschluss' 2026-10-12 16:56:53 +02:00
florian.wildeandClaude Opus 5.5 d9e218a6d3 docs(anleitung): Kapitel Einstellungen und Benachrichtigungen (#029)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:56:52 +02:00
florian.wilde ca05d6a58e Merge branch 'phase/029-handbuch'
# Conflicts:
#	src/worker/index.ts
2026-10-12 16:56:22 +02:00
florian.wilde b99d19ec8c Merge branch 'phase/028-benachrichtigungen'
# Conflicts:
#	docker-compose.yml
#	scripts/screenshots.ts
2026-10-12 16:55:57 +02:00
florian.wildeandClaude Opus 5.5 3e19b11a80 test(einstellungen): E2E, Screenshots und Issue-Abschluss (#028)
Der E2E-Lauf belegt im gebauten Stand, was die Unit- und DB-Tests nur
einzeln zeigen: ein Admin speichert SMTP, die Testnachricht kommt ueber
den Worker in Mailpit an, das Passwort kehrt nie in die Seite zurueck,
und ein Leser sieht keine Aendern-Knoepfe. Die Screenshots (1440/390 px)
halten das Layout der neuen Seite fuer die Anleitung fest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:54:56 +02:00
florian.wildeandClaude Opus 5.5 8f5b31c0cb feat(benachrichtigung): E-Mail und Webhook bei Drift und Ablauf (#028)
Drift, ablaufende und abgelaufene Schluessel, unerreichbare Hosts und
geaenderte Host-Keys sollen auffallen, ohne dass jemand taeglich in die
Oberflaeche schaut. Versand per SMTP (Microsoft 365, STARTTLS) und per
Webhook mit HMAC-Signatur, eingestellt unter /einstellungen.

Die Anlaesse liest der Worker aus der Datenbank, statt sie im Abgleich
auszuloesen: der Abgleich-Ablauf bleibt unberuehrt, und ein verpasster
Lauf holt sich der naechste. Ein Versandprotokoll (Zustellung) mit
Anlass-Fingerabdruck verhindert Doppelmeldungen und haelt Fehlschlaege
fuer begrenzte Wiederholungen fest. Taeglich eine Zusammenfassung,
geaenderte Host-Keys im Fuenf-Minuten-Takt sofort.

Versand und Testnachricht laufen im Worker, damit der Webprozess keinen
Weg zu Mailserver oder Webhook-Ziel braucht. Passwort und Geheimnis
liegen nur verschluesselt in der Datenbank; die Seite zeigt "gesetzt".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:54:43 +02:00
florian.wildeandClaude Opus 5.5 7721a9f764 docs(anleitung): Kapitel Audit-Log und Syslog-Variablen (#029)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:49:28 +02:00
florian.wilde 461cab1389 Merge branch 'main' into phase/029-handbuch 2026-10-12 16:49:27 +02:00
florian.wilde fb830dffa7 Merge branch 'phase/026-aktionstexte' 2026-10-12 16:49:11 +02:00
florian.wildeandClaude Opus 5.5 ad371b785a feat(uebersicht): lesbare Aktionstexte aus dem Audit-Log (#026)
Die letzten Aktionen nutzen dieselben Texte wie das Audit-Log (#027)
statt technischer Namen wie authorized_keys.geschrieben.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:49:07 +02:00
florian.wilde 2a45d4cc46 Merge branch 'phase/027-protokoll-ansicht' 2026-10-12 16:48:27 +02:00
florian.wildeandClaude Opus 5.5 0154343fd2 feat(protokoll): Audit-Log mit Filtern, Kettenpruefung und CSV (#027)
Das Protokoll war bisher nur in der Datenbank lesbar. Die Ansicht zeigt es
jeder Rolle mit lesbaren Aktionstexten, aufklappbarem Diff, Details und
Hashes; ein Test haelt fest, dass jede Aktion im Code einen Text hat.

Geblaettert wird ueber die ID statt OFFSET, weil das Protokoll waehrend
des Lesens waechst. Die Kettenpruefung liest jede Zeile und laeuft deshalb
nur auf Knopfdruck, mit 5 Minuten Zwischenspeicher; ein Bruch wird selbst
protokolliert. Der CSV-Export streamt in Stapeln, entschaerft Formeln und
steht selbst im Protokoll. protokollieren() reicht jeden Eintrag nach dem
Schreiben an Syslog weiter, ohne Details und Diff.

Kein Weg aendert oder loescht Eintraege; ein struktureller Test sichert
das ab, zusaetzlich zum Trigger.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:47:44 +02:00
florian.wildeandClaude Opus 5.5 7bb2ca6a51 feat(protokoll): Syslog-Sender nach RFC 5424 ohne Abhaengigkeit (#027)
Die Kopie beim Syslog-Server ist der vom sshkv-Host unabhaengige Beleg:
wer die Datenbank uebernimmt, kann die Hash-Kette neu rechnen, aber nicht
zurueckholen, was schon beim Syslog liegt. Eigene kleine Implementierung
mit node:dgram/node:net statt einer Bibliothek, weil das Format
ueberschaubar ist und jede Abhaengigkeit im Sicherheitskern zaehlt.

Strukturierte Daten werden nach RFC 5424 escaped und begrenzt (2048 Byte),
Severity folgt dem Ergebnis, Facility standardmaessig authpriv. Der Sender
wirft nie und meldet Fehler hoechstens einmal je Minute, damit ein
ausgefallener Syslog-Server weder Aktionen verhindert noch das Log flutet.
Web und Worker bekommen die Variablen, weil beide protokollieren.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:47:30 +02:00
florian.wildeandClaude Opus 5.5 3b28f73c95 docs(betrieb): Handbuch, Bedrohungsanalyse, geprobte Sicherung (#029)
Handbuch mit Bildern fuer alle Ablaeufe, darunter der Kern-
Anwendungsfall und der Deployment-Zugang fuer Claude Code.
Betriebsdoku zu Installation, authentik und Reverse Proxy sowie eine
Bedrohungsanalyse mit Notfallplan fuer den Fall, dass sshkv selbst
kompromittiert wird.

Sicherung und Wiederherstellung als Skripte, auf einem Wegwerf-Stapel
geprobt. Dabei fielen zwei Fehler auf: das Schema der Warteschlange
liess sich nicht zurueckspielen, und ein Abbruch ohne Transaktion
loeschte es halb. Jetzt nur Schema public, in einer Transaktion, mit
Pruefwert des Master-Keys. Der Worker startet nicht, wenn der
Master-Key nicht zur Datenbank passt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:46:38 +02:00
florian.wilde f791816e85 Merge branch 'phase/026-uebersicht' 2026-10-12 16:33:14 +02:00
florian.wildeandClaude Opus 5.5 ca61ca5b41 feat(uebersicht): echte Zahlen statt Beispieldaten (#026)
Kennzahlen, Hosts mit Abweichung, bald ablaufende Schluessel und letzte
Aktionen kommen aus der Datenbank; die Beispieldaten des Prototyps sind
entfernt. Befunde werden je Host und Benutzer in fester Reihenfolge
zusammengefasst, abgelaufene und noch ausgerollte Schluessel stehen in
Koralle. Anmeldungen bleiben dem Audit-Log vorbehalten, sonst
verdraengten sie auf der Uebersicht alles andere.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:33:11 +02:00
florian.wilde 83bd9e0922 Merge branch 'phase/release-0.4.0' 2026-10-12 13:40:34 +02:00
florian.wildeandClaude Opus 5.5 18f72f9d03 chore(release): 0.4.0 - Phase 4 abgeschlossen
Abgleich und Drift-Erkennung, Import, Rotation, Widerruf und
Zuweisungsmatrix. CHANGELOG und DEVLOG nachgetragen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:40:32 +02:00
florian.wilde 6407a2815d Merge branch 'phase/023-024' (Rotation, Widerruf) 2026-10-12 13:31:47 +02:00
florian.wildeandClaude Opus 5.5 f6ed66fc7e feat(rotation): gefuehrte Rotation mit Test vor dem Widerruf (#023)
Einen Schlüssel zu tauschen soll keinen Ausfall riskieren: Der Nachfolger
übernimmt alle aktiven Zuweisungen, wird ausgerollt und per
Verbindungstest geprüft – erst wenn alle Tests seit dem letzten Ausrollen
grün sind, entfernt der Widerruf aus #024 den alten Schlüssel, der dann
als ROTIERT endet.

Private Keys bekommen einen neuen Pfad (-rJJJJMMTT) bei gleichem Alias.
Damit ssh den neuen nimmt, schreibt privat.ts je Alias nur noch einen
config-Eintrag; die jüngste Zuweisung gewinnt. Der Worker setzt
vorgaengerId schon beim Erzeugen, so ist die Kette auch nach einem
Abbruch vollständig und die Schrittfolge wiederaufnehmbar.

Personen-Keys ohne Private Key in sshkv bekommen nur einen Hinweis:
dafür braucht es einen neuen Public Key von der Person.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:30:39 +02:00
florian.wildeandClaude Opus 5.5 a2d4f8428a fix(widerruf): gesperrte Auswahl im Massenwiderruf erkennbar (#024)
Deployment-Key, widerrufene und rotierte Schlüssel sind nicht wählbar,
ihre Kästchen sahen aber genauso aus wie die wählbaren. Jetzt sind sie
sichtbar abgeblendet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:30:04 +02:00
florian.wildeandClaude Opus 5.5 c6361ff3c6 feat(widerruf): Widerruf und Massenwiderruf mit Dry-Run (#024)
Ein Schlüssel soll nach Projektende mit einem Klick von allen Hosts
verschwinden – aber nie als „widerrufen“ gelten, solange er irgendwo noch
liegt. Deshalb ein Zwischenzustand (widerrufEingeleitetAm): Verwendungen
werden zum Entfernen vorgemerkt, der bestehende Dry-Run mit Hash-Sperre
zeigt und schreibt die Änderungen, und erst wenn keine Verwendung mehr
offen ist, setzt der Abschluss WIDERRUFEN (bzw. ROTIERT für #023).
Scheitert ein Host, bleibt der Schlüssel im Zwischenzustand und die Seite
nennt die offenen Ziele.

Zielkonten lassen sich optional mit entfernen, aber erst nach den
Schlüsseln und nie, wenn ein anderer aktiver Schlüssel sie noch nutzt.
Massenwiderruf (Admin, frische Bestätigung) nutzt einen gemeinsamen
Dry-Run und protokolliert je Schlüssel. Abgelaufene Schlüssel erscheinen
im Filter „abgelaufen“ und bieten den Widerruf an – entfernt wird nie
automatisch.

Migration 20261012130000_widerruf_eingeleitet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:14:01 +02:00
florian.wilde 34f5b1f6df Merge branch 'phase/025-zuweisungsmatrix' 2026-10-12 13:06:33 +02:00
florian.wilde fd00ef6de8 Merge branch 'phase/030-gitea-abgleich' 2026-10-12 13:06:32 +02:00
florian.wildeandClaude Opus 5.5 8fadb84923 chore(gitea): Abgleich der Issue-Dateien nach Gitea vorbereiten (#030)
Skript aus ProxLedger uebernommen: legt Labels, Meilensteine und
Issues an bzw. aktualisiert sie, Token aus GITEA_TOKEN oder den
Git-Zugangsdaten. Ohne Zugang laeuft nur ein Trockenlauf - der Gitea-
Zugang fehlt noch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:06:28 +02:00
florian.wilde 656dcb3b4c Merge branch 'phase/021-abgleich' 2026-10-12 13:05:24 +02:00
florian.wildeandClaude Opus 5.5 cbeb0235f2 test(abgleich): Abgleich und Uebernahme im Browser (#021, #022)
Gebauter Stand mit Worker: fremder Key auf dem Testziel, Abgleich
zeigt ihn als unbekannt, Uebernahme in den Block mit Dry-Run, danach
keine offenen Abweichungen. Der Test raeumt einen Benutzer deploy aus
frueheren Laeufen weg - genau den hat der Abgleich zu Recht als von
Hand geaenderten Block gemeldet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:05:20 +02:00
florian.wildeandClaude Opus 5.5 a47cae4082 feat(zuweisungen): Matrix Schluessel x Host mit CSV-Export (#025)
Bisher liess sich nur je Schluessel oder je Host nachsehen, wo ein Key
verwaltet ist. Die Matrix zeigt das auf einen Blick, loest
Hostgruppen-Zuweisungen je Mitglied auf (P vs. P·G) und macht nicht
synchrone Zustaende ohne Farbe erkennbar.

Die Matrix plant nur: Zuweisen und Entfernen laufen ueber die
vorhandenen Aktionen, ausgerollt wird weiter per Dry-Run auf der
Schluesselseite. Der Aufbau ist eine reine Funktion in src/lib, damit
Seite und CSV-Export garantiert dasselbe zeigen und die
Gruppenaufloesung ohne Datenbank testbar ist. Der CSV-Export entschaerft
Formelbeginne, weil Schluesselnamen frei waehlbar sind und Excel sie
sonst ausfuehren wuerde.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:05:12 +02:00
florian.wildeandClaude Opus 5.5 e518e75022 feat(abgleich): Drift-Erkennung, Zeitplan und Uebernahme (#021, #022)
Ein Abgleich liest je Host alle authorized_keys in einem SSH-Aufruf,
als jeweiliger Benutzer, und vergleicht sie mit dem Soll: fehlende,
von Hand geaenderte und unbekannte Keys, falsche Rechte, beschaedigte
Bloecke und veraenderte Private Keys. Befunde werden geoeffnet und
wieder geschlossen, Zustaende von Verwendungen und Host nachgezogen.
Auf dem Host aendert der Abgleich nichts.

Der Zeitplan liegt in pg-boss und feuert auch bei mehreren Workern
nur einmal. Auf der Hostseite stehen Abweichungen und der Ist-Zustand
je Benutzer; unbekannte Keys lassen sich uebernehmen - nur bekannt
machen oder mit Dry-Run in den verwalteten Block holen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 13:02:11 +02:00
florian.wilde 2dad8e5631 Merge branch 'phase/release-0.3.0' 2026-10-12 11:25:17 +02:00
florian.wildeandClaude Opus 5.5 2c658b1223 chore(release): 0.3.0 - Phase 3 abgeschlossen
Host-zu-Host-Assistent, Zielkonten mit sudoers, Vorlagen. CHANGELOG
und DEVLOG nachgetragen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:25:16 +02:00
florian.wilde 00cff56500 Merge branch 'phase/018-020-assistent' 2026-10-12 11:19:33 +02:00
florian.wilde 685b76657d Merge branch 'phase/019-vorlagen-oberflaeche' 2026-10-12 11:19:33 +02:00
florian.wildeandClaude Opus 5.5 b80f14c5d7 feat(assistent): Host-zu-Host-Assistent mit Vorlagen (#018, #020)
Ein Durchgang fuer den Kern-Anwendungsfall: Vorlage und Zweck, Host A,
Host B mit Zielbenutzer, Zusammenfassung, dann Schluessel erzeugen,
Benutzer anlegen, zuweisen, Dry-Run, ausrollen und Verbindung testen.
Die Vorlage Deployment-Zugang fuellt deploy, Ablaufdatum, from= und
Alias vor; bei root als Ziel warnt der Assistent ausdruecklich.

Die Ausfuehrung ist wiederaufnehmbar: wird mittendrin eine frische
Bestaetigung fuer das Zielkonto noetig, geschieht sie in einem neuen
Tab, und nichts wird doppelt angelegt. Zielkonten laufen als eigene
Vorgaenge (neue Migration) und merken sich, ob sshkv den Benutzer
angelegt hat - nur dann loescht ein Widerruf ihn spaeter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:19:28 +02:00
florian.wildeandClaude Opus 5.5 458f62289a test(vorlagen): Service-, E2E-Tests und Screenshots (#019)
Der Service war nur ueber die reinen Pruefregeln abgedeckt. Die
DB-Tests halten fest, was die Oberflaeche voraussetzt: doppelte Namen
melden sich am Feld name, Aenderungen protokollieren genau die
geaenderten Felder, eingebaute Vorlagen lassen sich nicht archivieren.
Der E2E-Ablauf prueft Feldfehler, Vorschau und Rechte im gebauten
Stand; die Screenshots dienen der Layout-Abnahme bei 390 px.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:16:42 +02:00
florian.wildeandClaude Opus 5.5 5c42f8dd8d feat(vorlagen): Liste, Formular und Live-Vorschau (#019)
Die Vorlagen-Logik aus #019 war bisher nur per Seed und Service
erreichbar. Admins koennen eigene Vorlagen jetzt anlegen, eingebaute
anpassen und eigene archivieren; jede Rolle sieht die Liste, weil
Operatoren die Vorlagen im Assistenten wiedererkennen sollen.

Die Vorschau nutzt im Browser dieselben reinen Funktionen wie der
Server (platzhalterErsetzen, optionenPruefen, sudoersInhalt). So sieht
der Admin vor dem Speichern, was in authorized_keys, ~/.ssh/config und
sudoers landen wuerde; verbindlich prueft weiterhin der Server. Voller
sudo-Zugang bekommt einen deutlichen Hinweis, weil er root gleichkommt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:16:31 +02:00
florian.wilde bdf12b05e7 Merge branch 'phase/release-0.2.0' 2026-10-12 11:05:59 +02:00
florian.wildeandClaude Opus 5.5 0a58f6ce32 chore(release): 0.2.0 - Phase 2 abgeschlossen
Schluessel, Personen, Private Key anzeigen, authorized_keys-Parser und
Ausrollen von Public Keys; vorgezogen das Ausrollen von Private Keys
mit ssh-config und known_hosts. CHANGELOG und DEVLOG nachgetragen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:05:57 +02:00
florian.wilde 1dc39671c2 Merge branch 'phase/016-017-schluesselseite' 2026-10-12 11:05:07 +02:00
florian.wildeandClaude Opus 5.5 12603eb145 feat(schluessel): Zuweisen und Ausrollen auf der Schluesselseite (#017)
Auf der Seite eines Schluessels: Public Key einem Host oder einer
Gruppe zuweisen, Private Key (nur Admin) auf einen Quell-Host legen -
mit ~/.ssh/config-Eintrag und dem Host-Key des Ziels in known_hosts -,
Dry-Run fuer alle Ziele, anwenden und die Verbindung von A nach B
testen. Der Kern-Anwendungsfall laeuft damit im Browser durch (E2E).

Der Dry-Run nimmt Private-Key-Ziele nur fuer Admins auf: sonst koennte
ein Operator einen vorbereiteten Private Key ausrollen lassen. Die
Vorschau zeigt je Datei die Aktion; der Inhalt eines Private Keys
erreicht den Browser nie.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:05:03 +02:00
florian.wilde 68136e8f07 Merge branch 'phase/019-vorlagen' 2026-10-12 10:54:31 +02:00
florian.wilde 58e50f14c2 Merge branch 'phase/017-private-key-ausrollen'
# Conflicts:
#	src/worker/handler.ts
2026-10-12 10:54:28 +02:00
florian.wilde 3f72a1c7bf Merge branch 'phase/018-benutzer-sudoers' 2026-10-12 10:54:12 +02:00
florian.wilde 445afe3a9b Merge branch 'phase/012-014' (Schluessel, Personen, Private Key)
# Conflicts:
#	src/components/vorgang/useVorgang.ts
#	src/worker/handler.ts
2026-10-12 10:54:09 +02:00
florian.wildeandClaude Opus 5.5 3e78ed55e4 feat(vorlagen): Platzhalter, Pruefung und Service fuer Vorlagen (#019)
Vorlagen werden beim Speichern so geprueft, wie sie spaeter benutzt
werden: mit probeweise aufgeloesten Platzhaltern. Ein unbekannter
Platzhalter ist ein Fehler, kein leerer Text - sonst stuende from=""
in authorized_keys. Werte mit Anfuehrungszeichen, Kommas oder Leerraum
koennen keine weiteren Optionen einschleusen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:52:30 +02:00
florian.wildeandClaude Opus 5.5 2b6f826ce0 docs(anleitung): Screenshots Schluessel, Personen, Self-Service (#014)
Neue Aufnahmen fuer Schluesselliste, Neuer Schluessel, Schluesseldetail
(echter Demo-Key statt Prototyp), Personen und Meine Schluessel; die
uebrigen neu, weil die Navigation jetzt "Meine Schluessel" zeigt.
Aufgenommen gegen den gebauten Stand mit SSHKV_SEED_DEMO=1, 390 px ohne
seitliches Scrollen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:51:17 +02:00
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
florian.wildeandClaude Opus 5.5 61a52d4538 feat(ausrollen): Verbindungstest von Host A nach Host B (#020)
Auf A als Zielbenutzer ssh mit BatchMode und StrictHostKeyChecking:
keine Rueckfrage, keine stille Annahme eines Host-Keys. Der Test
beweist damit genau das, was ein Cronjob auf A spaeter braucht. Die
letzte Meldung von ssh landet in Vorgang und Protokoll; ein Erfolg
setzt die letzte Verwendung des Schluessels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:50:43 +02:00
florian.wildeandClaude Opus 5.5 edc38d0c27 feat(ausrollen): Private Key mit ssh-config und known_hosts (#017)
Der Kern-Anwendungsfall im Worker: Private Key auf Host A, Public Key
auf Host B, dazu ein Block in ~/.ssh/config von A und der vertraute
Host-Key von B in known_hosts - danach meldet sich A per Alias bei B
an, ohne Rueckfrage zum Host-Key (Integrationstest gegen zwei echte
sshd).

Gleiches Muster wie bei Public Keys: Plan je Datei mit Hashes, dann
genau dieser Plan. Der Inhalt eines Private Keys steht nie im Plan
oder Protokoll, eine fremde Datei am Zielpfad wird nie ueberschrieben,
und von einem alten Private Key bleibt keine Sicherungskopie liegen.
Der config-Block steht am Anfang, weil ssh den ersten Treffer nimmt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:49:28 +02:00
florian.wildeandClaude Opus 5.5 b5b43f41f6 feat(personen): Personen und Self-Service Meine Schluessel (#013)
Personen lassen sich anlegen, bearbeiten und archivieren; ihr Detail
zeigt ihre Schluessel. Unter "Meine Schluessel" hinterlegt jede Rolle
eigene Public Keys (Freigabe Frage 4), wirksam erst nach Zuweisung
durch einen Operator oder Admin.

Warum so:
- Die eigene Person entsteht beim ersten Aufruf oder wird verknuepft:
  per benutzerId, sonst per E-Mail - aber nur, wenn der OIDC-Anbieter
  die Adresse bestaetigt hat. Dafuer merkt sich Benutzer jetzt
  emailBestaetigt (Migration); sonst koennte jemand mit selbst
  gewaehlter Adresse die Keys einer fremden Person sehen.
- Die Self-Service-Services nehmen nur den angemeldeten Benutzer und
  leiten die Person selbst ab; fremde Schluessel-IDs gelten als nicht
  vorhanden.
- Entfernen nur fuer eigene, selbst hinterlegte Keys ohne Verwendung,
  bedingt geloescht, damit eine gleichzeitige Zuweisung nicht per
  Cascade verschwindet.
- Archivieren statt Loeschen: wer bis wann welchen Key hatte, bleibt
  nachvollziehbar.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:38:55 +02:00
florian.wildeandClaude Opus 5.5 7b6323a6a4 feat(zielkonten): Benutzer anlegen und sudoers pflegen (#018)
Zielbenutzer wie deploy legt sshkv bei Bedarf an, mit ~/.ssh und
optional einer sudoers-Datei: keine Rechte, bestimmte Befehle oder
voll. Die Datei entsteht unter einem Namen, den sudo ignoriert, und
wird erst nach visudo -c aktiv. Eine fremde Datei gleichen Namens
ueberschreibt sshkv nie, Systemkonten wie root oder sshkv fasst es
nicht an.

Befehle muessen absolute Pfade sein; Zeichen mit Sonderbedeutung in
sudoers sind ausgeschlossen, damit die Liste so wirkt, wie sie aussieht.

Vorgang und Oberflaeche folgen mit #019/#020.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:38:22 +02:00
florian.wilde c2b8004ade Merge branch 'phase/016-public-key-ausrollen' 2026-10-12 10:36:22 +02:00
florian.wildeandClaude Opus 5.5 263143134f 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>
2026-10-12 10:29:52 +02:00
florian.wildeandClaude Opus 5.5 8056b8a0c9 test(ausrollen): Dry-Run und Ausrollen im Browser (#016)
Gebauter Stand mit Worker gegen das Testziel: Host einrichten, Dry-Run
zeigt das Diff, auf dem Host aendert sich noch nichts, nach dem
Anwenden steht der Key mit seinen Einschraenkungen im Block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:21:54 +02:00
florian.wildeandClaude Opus 5.5 30f271b23a feat(hosts): Schluessel auf dem Host anzeigen und ausrollen (#016)
Die Hostseite listet, welche Public und Private Keys sshkv dort
verwaltet - direkt oder ueber eine Hostgruppe - mit Zustand als Punkt
und Text. Ein Dry-Run je Host zeigt das Diff aller Benutzer dieses
Hosts und rollt nach Bestaetigung aus.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:19:27 +02:00
florian.wildeandClaude Opus 5.5 f66d87ad11 feat(ausrollen): Public Keys mit Trockenlauf und Backup ausrollen (#016)
Zuweisungen von Public Keys an Hosts oder Hostgruppen, ein Trockenlauf
mit Diff je Ziel und das Ausrollen genau dieses Plans. Hat sich die
Datei oder das Soll seit dem Trockenlauf geaendert, bricht das Ziel
ab: es wird nie etwas anderes geschrieben, als jemand gesehen hat.

Auf dem Host geschieht alles als Zielbenutzer, mit Sicherung, Temp-
Datei, mv und 0700/0600; der Hash wird dort erneut geprueft. Der
Deployment-Key gehoert immer zum Soll des Anmeldebenutzers.

Integrationstests gegen echtes sshd, die Oberflaechenbausteine folgen
mit der Schluesselseite aus #012.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:17:35 +02:00
florian.wilde 1358055e40 Merge branch 'phase/015-authorized-keys-parser' 2026-10-12 10:10:58 +02:00
florian.wildeandClaude Opus 5.5 19edd20d72 feat(ssh): authorized_keys lesen und verwalteten Block setzen (#015)
Reine Funktionen fuer Dry-Run und Ausrollen. Alles ausserhalb des
Blocks bleibt bytegenau stehen, auch CRLF, BOM und kaputte Zeilen;
der Block entsteht deterministisch aus dem Soll. Ein beschaedigter
Block wird nie automatisch ueberschrieben, weil genau dort fremde
Zeilen verschwinden koennten.

Verwaltete Keys erkennt sshkv am Fingerprint, nicht am Kommentar. Der
Deployment-Key kann nicht aus dem Block fallen (Aussperrschutz).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:10:54 +02:00
florian.wilde f0833aa1db Merge branch 'phase/release-0.1.0' 2026-10-12 10:02:46 +02:00
florian.wildeandClaude Opus 5.5 db0d095032 chore(release): 0.1.0 - Phase 1 abgeschlossen
Grundgeruest, Anmeldung, Hosts mit Host-Key-Pruefung und Bootstrap.
README mit Installation und Ablauf fuer neue Hosts, CHANGELOG und
DEVLOG nachgetragen. Offen im Meilenstein bleibt nur #030 (Gitea),
das auf die Zugangsdaten wartet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:02:44 +02:00
florian.wilde d44185bcbd Merge branch 'phase/009-010-oberflaeche' 2026-10-12 10:00:47 +02:00
florian.wildeandClaude Opus 5.5 4584669962 feat(hosts): Host-Key und Bootstrap in der Oberflaeche (#009, #010)
Auf der Hostseite: Host-Key abrufen, Fingerprint vergleichen und
vertrauen oder ablehnen. Weicht ein Key vom vertrauten ab, erscheint
eine deutliche Warnung, und Annehmen geht nur mit Haekchen und frischer
Bestaetigung. Danach Bootstrap per Einmal-Passwort oder per Befehl und
Verbindungspruefung, jeweils mit Fortschrittsanzeige.

Der sshd-Befund empfiehlt prohibit-password, wenn root sich per
Passwort anmelden darf - nur als Hinweis, geaendert wird nichts.

E2E im gebauten Stand mit Worker gegen ein echtes sshd.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 10:00:43 +02:00
florian.wilde c87c7b4e43 Merge branch 'phase/010-bootstrap' 2026-10-12 09:52:00 +02:00
florian.wilde 69f9037e41 Merge branch 'phase/009-host-key-pruefung' 2026-10-12 09:51:59 +02:00
florian.wilde 941b38b4ea Merge branch 'phase/008-hosts-gruppen' 2026-10-12 09:51:58 +02:00
florian.wildeandClaude Opus 5.5 83f3c2893b fix(deploy): Prisma im Worker buendeln, Worker-Benutzer waehlbar (#007)
Im Docker-Stapel startete der Worker nicht: pnpm legt @prisma/client
im Standalone-Ordner nicht dort ab, wo die ESM-Aufloesung sucht. Der
Client wird jetzt mitgebuendelt.

Docker-Secrets ohne Swarm tragen die Rechte der Host-Datei. Wer den
Master-Key nicht auf UID 1001 umschreiben kann, setzt stattdessen
SSHKV_WORKER_USER auf den Eigentuemer der Datei.

Gesamter Stapel geprueft: migrate, web und worker gesund.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:51:56 +02:00
florian.wildeandClaude Opus 5.5 c8a415f1ca chore(hosts): Demo-Daten, Screenshots und Issue-Notizen (#008)
SSHKV_SEED_DEMO=1 legt sechs Demo-Hosts, drei Gruppen und einen Kunden an,
damit Entwicklung, Screenshots und Handbuch ohne echte Server auskommen.
Nur mit der Variable, damit eine echte Instanz nie Phantom-Hosts bekommt;
Upsert nach Name haelt den Seed idempotent.

Das Screenshot-Skript kennt jetzt Seiten mit wechselnder ID (folgen: erster
passender Link) und nimmt Hostliste und Hostdetail auf.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:50:53 +02:00
florian.wildeandClaude Opus 5.5 90a41dc55d feat(hosts): Seiten fuer Hosts und Hostgruppen (#008)
Ersetzt die Platzhalter: Hostliste mit Suche und Gruppenfilter per GET
(teilbare Links), Formulare fuer Hosts und Gruppen, Hostdetail als Geruest
und Gruppendetail mit Mitgliederauswahl. Bearbeiten-Knoepfe entscheidet der
Server per darf(); jede Seite prueft die Sitzung selbst.

Die Detailseite rendert HostKeyAktionen (#009) und BootstrapAktionen (#010)
als feste Platzhalter mit fertigen Props, damit die Folge-Issues nur ihre
eigene Datei ersetzen. Formulare senden per onSubmit, weil React ein
<form action> nach jeder Antwort zuruecksetzt und Eingaben bei Feldfehlern
sonst verloren gingen. Unter md zeigt die Hostliste Karten statt Tabelle,
damit Status und Host-Key auf 390 px ohne Wischen sichtbar bleiben.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:50:34 +02:00
florian.wildeandClaude Opus 5.5 4f9d5d9680 feat(hosts): Services, Lesemodelle und Aktionen fuer Hosts (#008)
Hosts und Hostgruppen brauchen eine Schreibschicht, bevor Host-Key-Pruefung
(#009) und Bootstrap (#010) darauf aufsetzen. Die Regeln (Name, Adresse wie
der DB-CHECK, Port, Tags, Anmeldeart) liegen framework-frei in src/lib/hosts.ts,
damit Action (Feldmeldungen) und Service (Schutz fuer Seed und Assistenten)
dieselben pruefen.

Archivieren statt Loeschen haelt Protokollbezuege lesbar; dabei fallen die
Gruppenmitgliedschaften weg, damit Zuweisungen an eine Gruppe nie einen
archivierten Host treffen. Jede Aenderung landet mit den geaenderten Feldern
im Protokoll, Eindeutigkeitsverletzungen kommen als deutsche HostFehler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:42:56 +02:00
florian.wildeandClaude Opus 5.5 17879f4c80 feat(bootstrap): Deployment-Key per Einmal-Passwort oder Befehl (#010)
Ein einziges POSIX-Skript fuer beide Wege: der Worker fuehrt es nach
einer einmaligen Passwortanmeldung aus, und genau dasselbe Skript
steht als Befehl zum Kopieren bereit. So gibt es keine zwei Wahrheiten
darueber, was ein Bootstrap auf dem Host aendert.

Das Passwort reist nur verschluesselt im Job und steht nie in Vorgang,
Protokoll oder Log. Dateien im Home des Zielbenutzers liest und
schreibt das Skript als dieser Benutzer: ein Symlink auf /etc/shadow
fuehrt zum Abbruch statt zur Kopie (Integrationstest). sudoers wird
erst nach visudo -c aktiv. Am Ende beweist eine Anmeldung per
Deployment-Key, dass sshkv ohne Passwort weiterarbeiten kann.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:42:45 +02:00
florian.wildeandClaude Opus 5.5 353b751df2 feat(ssh): strikte Host-Key-Pruefung und TOFU im Worker (#009)
Jede Verbindung prueft den Host-Key gegen die vertrauten Keys des
Hosts und bietet nur deren Algorithmen an - sonst koennte der Server
einen Key eines anderen Typs zeigen, den nie jemand gesehen hat. Ein
abweichender Key bricht vor dem Senden von Zugangsdaten ab, wird als
ausstehend festgehalten und oeffnet eine Abweichung.

Abrufen bricht die Verbindung im hostVerifier ab: der Mensch
entscheidet, nie der Code. Einen vertrauten Key zu ersetzen verlangt
ein ausdrueckliches ersetzen und eine frische Bestaetigung.

Integrationstest gegen ein echtes sshd inklusive Host-Key-Wechsel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:37:09 +02:00
florian.wilde ad97c8e9f1 Merge branch 'phase/006-geheimnisse-worker' 2026-10-12 09:33:22 +02:00
florian.wildeandClaude Opus 5.5 9b807c13ae feat(worker): Private Keys im Worker versiegeln, Deployment-Key (#006)
Der zweite Teil von #006 bindet den Kern an Datenbank und Worker. Nur
src/worker/geheimnisse.ts liest Chiffrat und Data-Key; ein struktureller
Test haelt fest, dass der Webprozess nichts von dort importiert und so
nie an den Master-Key kommt.

Die Zusatzdaten binden jedes Chiffrat an ID und Fingerprint seines
Schluessels: kopiert man es in eine andere Zeile, laesst es sich dort
nicht oeffnen. Der Deployment-Key entsteht beim ersten Start; ein Test
prueft, dass ein pg_dump keinen Klartext enthaelt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:33:18 +02:00
florian.wilde 855d77cf17 Merge branch 'phase/005-protokoll' 2026-10-12 09:30:55 +02:00
florian.wildeandClaude Opus 5.5 364c514b04 feat(protokoll): Eintraege vor dem Schreiben schwaerzen (#005)
Details und Diff laufen durch schwaerzen(), damit kein Private Key und
kein Passwort ins unveraenderliche Protokoll geraet - was dort einmal
steht, laesst sich per Trigger nicht mehr entfernen.

Der Wortstamm privat ist dafuer zu breit: er schwaerzte auch Pfade und
Verwendungszaehler, die das Protokoll gerade zeigen soll. Jetzt greifen
nur Felder, die den Schluessel selbst tragen, und jeder PEM-Block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:30:51 +02:00
florian.wilde b194054d30 Merge branch 'phase/004-anmeldung-rollen' 2026-10-12 09:28:56 +02:00
florian.wildeandClaude Opus 5.5 d1ba822a4a feat(auth): Anmeldeseiten, TOTP-Einrichtung und Bestaetigung (#004)
Anmeldung, Einrichtung des zweiten Faktors fuer den Notfall-Admin,
erneute Bestaetigung vor kritischen Aktionen und die Seite fuer
fehlende Rechte. Die Huelle zeigt jetzt den echten Benutzer, und jede
Seite prueft die Sitzung selbst; ein struktureller Test haelt das fest.

Die Ablaeufe sind im gebauten Stand gegen den Mock-OIDC geprueft
(pnpm test:e2e): Admin, Konto ohne Gruppe, Notfall-Admin mit TOTP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:28:51 +02:00
florian.wilde 4d0044b380 Merge branch 'phase/002-design-layout' 2026-10-12 09:21:46 +02:00
florian.wilde a2bdd9f205 Merge branch 'phase/007-worker-vorgaenge' 2026-10-12 09:21:41 +02:00
florian.wilde 63a378df8f Merge branch 'phase/006-geheimnisse-schluesselformat' (Kern) 2026-10-12 09:21:41 +02:00
florian.wildeandClaude Opus 5.5 4a23930b54 feat(worker): Worker mit pg-boss, Vorgaenge und Docker-Betrieb (#007)
Die Oberflaeche legt einen Vorgang an und schickt nur dessen ID in die
Warteschlange; der Worker fuehrt ihn aus und haelt Status, Ergebnis
oder Fehler fest. So bleiben lange SSH-Vorgaenge aus dem Webprozess
heraus, und der Fortschritt ist ohne Job-Interna sichtbar.

Kein automatisches Wiederholen: ein halb ausgefuehrter Schreibvorgang
auf einem Host gehoert vor einen Menschen. Fehlertexte aus fremden
Bibliotheken erreichen die Oberflaeche nie.

Dockerfile und Compose fuer den Betrieb: Master-Key als Secret nur im
Worker, Datenbank in einem internen Netz.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:21:41 +02:00
florian.wildeandClaude Opus 5.5 4af07e9553 docs(anleitung): Screenshot-Skript und Bilder des Prototyps (#002)
scripts/screenshots.ts nimmt Uebersicht und Schluesseldetail bei
1440x900 und 390x844 auf und meldet nebenbei horizontalen Ueberlauf,
damit es fuer die Anleitung (#029) und als schneller Layout-Test taugt.
Issue #002 ist damit erledigt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:21:02 +02:00
florian.wildeandClaude Opus 5.5 445f5c5ebd feat(ui): Prototyp Uebersicht und Schluesseldetail (#002)
Klickbarer Prototyp mit Beispieldaten aus src/beispiel/, sichtbar als
Beispiel gekennzeichnet. Er zeigt das verbindliche Design an echten
Bildschirmen, bevor die Lesemodelle (#012, #021, #026) existieren; die
Seiten tauschen spaeter nur die Datenquelle. Die bisherige Startseite
entfaellt zugunsten der Route-Gruppe (app).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:20:34 +02:00
florian.wildeandClaude Opus 5.5 419e8ff8bd feat(ui): Huelle mit Seitenleiste und Platzhalterseiten (#002)
Die Huelle bekommt Benutzer und optional den aktiven Pfad als Props und
kennt weder Sitzung noch Datenbank; ohne Pfad ermittelt die Navigation
ihn per usePathname(), weil ein Layout den Pfad nicht kennt. Die
Benutzerermittlung steckt in huellenBenutzerErmitteln(), damit #004 nur
diese Funktion ersetzen muss.

Unter 1024 px klappt die Leiste ein; geschlossen ist sie invisible und
damit auch nicht per Tab erreichbar. Alle Navigationsziele haben eine
Platzhalterseite, damit nichts auf eine 404 fuehrt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:20:30 +02:00
florian.wildeandClaude Opus 5.5 da0607522e feat(ui): Marke und Grundkomponenten (#002)
Bildmarke als Inline-SVG und Wortmarke, dazu Karte, Button, Badge,
Statuspunkt, Tabelle, Kennzahl, Diff-Box, Breadcrumb, Seitenkopf,
Leerzustand und MonoKopieren. Die Bausteine kapseln die Designregeln
(44 px Klickflaeche, Status nie nur ueber Farbe, Tabellen scrollen im
eigenen Container), damit spaetere Seiten sie nicht neu erfinden.
Nur MonoKopieren ist Client-Komponente, weil sie die Zwischenablage
braucht.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:20:26 +02:00
florian.wildeandClaude Opus 5.5 88b5697cc9 feat(ui): Design-Tokens, Schriften und Datumsformate (#002)
Die Farben des Tresor-Designs stehen als CSS-Variablen in :root und
werden per @theme inline an Tailwind gereicht, damit Inline-SVGs und
Utility-Klassen dieselbe Quelle nutzen. Schriften kommen selbst gehostet
aus @fontsource, nur in den benutzten Gewichten und im Latin-Ausschnitt.

src/lib/format.ts rechnet fest in Europe/Berlin: der Server laeuft im
Container auf UTC, die Anzeige soll aber ueberall dieselbe Ortszeit
zeigen. relativ() nimmt den Bezugspunkt als Parameter, damit statische
Seiten und Tests deterministisch bleiben.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:20:22 +02:00
florian.wilde bfa0d574aa Merge branch 'phase/004-anmeldung-rollen' (Logik) 2026-10-12 09:17:57 +02:00
florian.wilde 792bdd54ce Merge branch 'phase/011-test-ssh-container' 2026-10-12 09:17:48 +02:00
florian.wildeandClaude Opus 5.5 6cdc49df49 docs: Stand und Entscheidungen zu #006 festhalten (#006)
Der framework-freie Kern ist fertig; Deployment-Key, Master-Key im Worker
und der Nachweis am Datenbank-Dump haengen an Datenbank und Worker und
kommen mit #007. Die Entscheidungen (16 bcrypt-Runden, Fehlercodes,
RSA-Mindestlaenge abschaltbar fuers Beobachten, Umschlag-Format,
Dateirechte des Master-Keys) stehen im Issue, damit die Anbindung nicht
raten muss.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:17:46 +02:00
florian.wildeandClaude Opus 5.5 5ef90310e1 feat(auth): Anmeldung per OIDC und Notfall-Admin mit TOTP (#004)
Sitzung, OIDC-Fluss und Rueckruf aus Laufzettel uebernommen und auf
sshkv angepasst. Abweichungen mit Absicht:

- Ohne passende sshkv-Gruppe gibt es keinen Zugang, auch keinen
  Lesezugriff: schon die Hostliste verraet, wer wohin darf.
- Notfall-Admin nur mit TOTP (Freigabe Frage 5). Ein richtiges
  Passwort fuehrt beim ersten Mal nur zur Einrichtung. Jeder Code gilt
  einmal, fuenf Fehlversuche sperren 15 Minuten.
- Kritische Rechte verlangen eine Bestaetigung, die hoechstens fuenf
  Minuten alt ist. Bei OIDC mit prompt=login, max_age=0 und Pruefung
  von auth_time, sonst zaehlte eine laufende SSO-Sitzung als Beweis.
- Sitzungen laufen acht statt zwoelf Stunden.

Die Seiten folgen, sobald das Design-System (#002) steht.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:17:44 +02:00
florian.wildeandClaude Opus 5.5 d359b7d4c3 feat: Geheimnisse vor Log und Protokoll schwaerzen (#006)
Private Keys, Passwoerter und Tokens duerfen nie in Log oder Protokoll
landen (CLAUDE.md). Ein Netz ueber Feldnamen allein reicht nicht: Ein Key
kann in einer Fehlermeldung oder einem Stacktrace stecken, ohne dass das
Feld danach heisst. Deshalb schwaerzt `schwaerzen` sowohl Felder mit
verdaechtigen Namen (irgendwo im Namen, nicht nur am Anfang) als auch
jeden PEM-Private-Key in beliebigen Zeichenketten, auch abgeschnittene.

Der Helfer ist framework-frei und wird mit dem Logger und dem Protokoll
eingebaut.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:17:34 +02:00
florian.wildeandClaude Opus 5.5 893b840f06 feat: Envelope Encryption und Master-Key aus Datei (#006)
Private Keys liegen spaeter verschluesselt in PostgreSQL. Ein eigener DEK
je Geheimnis erlaubt eine Master-Key-Rotation, ohne die Chiffrate selbst
anzufassen; das AAD (Schluessel-ID und Fingerprint) bindet jedes Chiffrat
an seinen Datensatz, sodass vertauschte Zeilen beim Oeffnen auffallen
statt einen fremden Key zu liefern. Die Master-Key-Version steckt im AAD
des DEK.

masterKeyLaden akzeptiert roh, Base64 und Hex, weil `openssl rand` der
naheliegende Weg zum Anlegen ist, und bricht ab, wenn Gruppe oder Welt
die Datei lesen duerfen – ein lesbarer Master-Key oeffnet alle Private
Keys. SSHKV_MASTER_KEY_UNSICHER_ERLAUBT=1 erlaubt das bewusst.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:16:56 +02:00
florian.wildeandClaude Opus 5.5 6b20566676 feat: OpenSSH-Schluessel erzeugen, schreiben und lesen (#006)
Private Keys sollen nie als Datei auf dem sshkv-Server liegen, auch nicht
kurz. ssh-keygen braucht aber genau das. Deshalb erzeugt Node crypto die
Schluessel (ed25519, RSA-4096), und ein eigener Serializer schreibt das
Format openssh-key-v1 – unverschluesselt oder mit aes256-ctr und
bcrypt_pbkdf (16 Runden, 16 Byte Salt), Checkint und Padding wie OpenSSH.

Der Parser liest auch von ssh-keygen erzeugte Dateien, prueft, dass
Private und Public Key zusammengehoeren, und meldet fehlende oder falsche
Passphrasen ueber eigene Codes. Fehlermeldungen sind feste Saetze ohne
Eingabe darin, damit kein Schluesselmaterial in Logs geraet. RSA unter
2048 Bit wird beim Import abgelehnt; wer nur beobachtet, kann die Grenze
ausschalten.

Die Tests pruefen beide Richtungen gegen das echte ssh-keygen (-y, -lf).

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