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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
Die Bildmarke liegt als SVG in drei Varianten (dunkel, hell, mono) vor.
Rasterdateien erzeugt scripts/icons.mjs mit sharp und sie werden
eingecheckt, damit Build und Laufzeitabbild kein sharp brauchen. Unter
32 px dient eine reduzierte Marke ohne Zahlenring und Bolzen, weil die
Details dort zu Rauschen verschwimmen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Konfiguration aus Laufzettel uebernommen, damit alle Projekte gleich
gebaut und geprueft werden. Entwicklungsdatenbank auf 5436, weil 5433
und 5434 von ProxLedger und Laufzettel belegt sind.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>