Files
sshkeyvault/src/lib/protokoll-aktionen.ts
T
florian.wildeandClaude Opus 5.5 4219701b29 feat(anmeldung): lokale Benutzerkonten mit Pflicht-TOTP (#035)
Ohne authentik gab es nur den Notfall-Admin – ein geteiltes Konto ohne
Rollen und ohne erkennbare Akteure im Audit-Log. Admins legen jetzt
lokale Konten an; sshkv erzeugt ein Einmal-Passwort, das bei der ersten
Anmeldung ersetzt wird, danach ist TOTP Pflicht.

Rollen, Sperren (beendet Sitzungen sofort) und Zurücksetzen unter
Einstellungen → Benutzer, nur für Admins mit frischer Bestätigung.
SSHKV_LOKALE_KONTEN=0 schaltet lokale Konten ab; der Notfall-Admin
bleibt.

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

162 lines
8.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Lesbare Beschriftung der technischen Protokoll-Aktionen (#027).
*
* Das Protokoll speichert stabile Schluessel wie `authorized_keys.geschrieben`, damit sich
* Auswertungen (CSV, Syslog, SIEM) nicht an Oberflaechentexten aufhaengen. Die Ansicht
* uebersetzt sie hier. `tests/protokoll-aktionen.test.ts` sucht jede `aktion: "…"` im
* Quellcode und verlangt einen Eintrag – eine neue Aktion ohne Text faellt so sofort auf.
*
* `aendert`: Die Aktion veraendert etwas (Datei geschrieben, Schluessel erzeugt …) und wird bei
* Erfolg in --ok gezeigt. Lesende oder rein informative Aktionen (Anmeldung, Abruf, Export)
* bleiben neutral, damit das Grün die Stellen markiert, an denen sich etwas getan hat.
*/
export const AKTIONSGRUPPEN = {
anmeldung: "Anmeldung",
benutzer: "Benutzer",
hostkey: "Host-Key",
ausrollen: "Ausrollen",
schluessel: "Schlüssel",
widerruf: "Widerruf/Rotation",
zielkonto: "Zielkonto",
abgleich: "Abgleich",
hosts: "Hosts",
stammdaten: "Personen und Vorlagen",
protokoll: "Protokoll",
benachrichtigung: "Benachrichtigung",
} as const;
export type Aktionsgruppe = keyof typeof AKTIONSGRUPPEN;
type Beschreibung = { readonly text: string; readonly gruppe: Aktionsgruppe; readonly aendert: boolean };
const a = (text: string, gruppe: Aktionsgruppe, aendert = true): Beschreibung => ({ text, gruppe, aendert });
export const AKTIONEN: Readonly<Record<string, Beschreibung>> = {
// Benachrichtigungen (#028)
"benachrichtigung.gesendet": a("Benachrichtigung gesendet", "benachrichtigung", false),
"benachrichtigung.fehlgeschlagen": a("Benachrichtigung fehlgeschlagen", "benachrichtigung", false),
"einstellungen.benachrichtigungen.geaendert": a("Benachrichtigungen geändert", "benachrichtigung"),
// Ereignisname im Webhook (#028); taucht als Text auf, wenn ein Host-Key-Alarm protokolliert wird.
"hostkey.geaendert": a("Host-Key geändert (Alarm)", "hostkey", false),
// Anmeldung und Bestaetigung (#004)
"anmeldung.oidc": a("Anmeldung (OIDC)", "anmeldung", false),
"anmeldung.oidc.erstmals": a("Erste Anmeldung (OIDC)", "anmeldung"),
"anmeldung.verweigert": a("Anmeldung verweigert", "anmeldung", false),
"anmeldung.notfall": a("Anmeldung Notfall-Admin", "anmeldung", false),
"anmeldung.lokal": a("Anmeldung (lokales Konto)", "anmeldung", false),
"anmeldung.lokal.fehlgeschlagen": a("Anmeldung (lokal) fehlgeschlagen", "anmeldung", false),
"anmeldung.lokal.gesperrt": a("Anmeldung (lokal) gesperrt – zu viele Fehlversuche", "anmeldung", false),
"anmeldung.lokal.totp_falsch": a("Anmeldung (lokal): falscher TOTP-Code", "anmeldung", false),
"konto.passwort_gesetzt": a("Eigenes Passwort gesetzt", "benutzer"),
"konto.totp_eingerichtet": a("TOTP eingerichtet", "anmeldung"),
"benutzer.angelegt": a("Lokales Konto angelegt", "benutzer"),
"benutzer.rolle_geaendert": a("Rolle geändert", "benutzer"),
"benutzer.gesperrt": a("Konto gesperrt", "benutzer"),
"benutzer.entsperrt": a("Konto entsperrt", "benutzer"),
"benutzer.zurueckgesetzt": a("Konto zurückgesetzt (Passwort und TOTP)", "benutzer"),
"anmeldung.notfall.fehlgeschlagen": a("Notfall-Anmeldung fehlgeschlagen", "anmeldung", false),
"anmeldung.notfall.gesperrt": a("Notfall-Anmeldung gesperrt", "anmeldung", false),
"anmeldung.notfall.totp_falsch": a("Notfall-Anmeldung: TOTP falsch", "anmeldung", false),
"bestaetigung.oidc": a("Anmeldung bestätigt (Re-Auth)", "anmeldung", false),
"notfall.totp_eingerichtet": a("TOTP für Notfall-Admin eingerichtet", "anmeldung"),
// Host-Keys (#009)
"hostkey.abgerufen": a("Host-Key abgerufen", "hostkey", false),
"hostkey.bestaetigt": a("Host-Key bestätigt", "hostkey"),
"hostkey.ersetzt": a("Host-Key ersetzt", "hostkey"),
"hostkey.abgelehnt": a("Host-Key abgelehnt", "hostkey"),
"hostkey.abweichung": a("Host-Key weicht ab – Verbindung abgebrochen", "hostkey", false),
// Ausrollen (#016, #017)
"authorized_keys.geschrieben": a("authorized_keys geschrieben", "ausrollen"),
"privatekey.ausgerollt": a("Private Key ausgerollt", "ausrollen"),
"verwendung.public.angelegt": a("Public Key zugewiesen", "ausrollen"),
"verwendung.public.entfernen": a("Public-Key-Zuweisung zum Entfernen vorgemerkt", "ausrollen"),
"verwendung.private.angelegt": a("Private Key zugewiesen", "ausrollen"),
"verwendung.private.entfernen": a("Private-Key-Zuweisung zum Entfernen vorgemerkt", "ausrollen"),
// Schluessel (#012, #013, #014)
"schluessel.erzeugt": a("Schlüssel erzeugt", "schluessel"),
"schluessel.importiert": a("Schlüssel importiert", "schluessel"),
"schluessel.uebernommen": a("Schlüssel vom Host übernommen", "schluessel"),
"schluessel.selbst_hinterlegt": a("Eigenen Public Key hinterlegt", "schluessel"),
"schluessel.selbst_entfernt": a("Eigenen Public Key entfernt", "schluessel"),
"schluessel.geaendert": a("Schlüssel geändert", "schluessel"),
"privatekey.angefordert": a("Private Key angefordert", "schluessel", false),
"privatekey.angezeigt": a("Private Key angezeigt", "schluessel", false),
"privatekey.heruntergeladen": a("Private Key heruntergeladen", "schluessel", false),
"privatekey.geloescht": a("Private Key gelöscht", "schluessel"),
"deploymentkey.erzeugt": a("Deployment-Key erzeugt", "schluessel"),
"deploymentkey.geprueft": a("Neuer Deployment-Key geprüft", "widerruf", false),
"deploymentkey.umgestellt": a("Deployment-Key umgestellt", "widerruf"),
"deploymentkey.abgeschlossen": a("Rotation des Deployment-Keys abgeschlossen", "widerruf"),
// Widerruf und Rotation (#023, #024)
"schluessel.widerruf.eingeleitet": a("Widerruf eingeleitet", "widerruf"),
"schluessel.widerrufen": a("Schlüssel widerrufen", "widerruf"),
"schluessel.rotiert": a("Schlüssel rotiert", "widerruf"),
"schluessel.rotation.uebernommen": a("Rotation: Zuweisungen übernommen", "widerruf"),
"schluessel.rotation.abgebrochen": a("Rotation abgebrochen", "widerruf"),
// Zielkonten (#018)
"zielkonto.angelegt": a("Zielkonto angelegt", "zielkonto"),
"zielkonto.geaendert": a("Zielkonto geändert", "zielkonto"),
"zielkonto.entfernt": a("Zielkonto entfernt", "zielkonto"),
// Abgleich und Uebernahme (#021, #022)
abgleich: a("Abgleich durchgeführt", "abgleich", false),
"uebernahme.verwalten": a("Fremden Key übernommen und verwaltet", "abgleich"),
"uebernahme.bekannt": a("Fremden Key als bekannt aufgenommen", "abgleich"),
// Hosts, Gruppen, Kunden (#008, #010)
"host.angelegt": a("Host angelegt", "hosts"),
"host.geaendert": a("Host geändert", "hosts"),
"host.archiviert": a("Host archiviert", "hosts"),
"host.bootstrap.angefordert": a("Einrichtung angefordert", "hosts", false),
"host.bootstrap": a("Host eingerichtet (Bootstrap)", "hosts"),
"host.bootstrap.geprueft": a("Einrichtung geprüft", "hosts"),
"hostgruppe.angelegt": a("Hostgruppe angelegt", "hosts"),
"hostgruppe.geaendert": a("Hostgruppe geändert", "hosts"),
"kunde.angelegt": a("Kunde angelegt", "hosts"),
verbindungstest: a("Verbindung geprüft", "hosts", false),
// Personen und Vorlagen (#013, #019)
"person.angelegt": a("Person angelegt", "stammdaten"),
"person.geaendert": a("Person geändert", "stammdaten"),
"person.archiviert": a("Person archiviert", "stammdaten"),
"person.verknuepft": a("Person mit Konto verknüpft", "stammdaten"),
"vorlage.angelegt": a("Vorlage angelegt", "stammdaten"),
"vorlage.geaendert": a("Vorlage geändert", "stammdaten"),
"vorlage.archiviert": a("Vorlage archiviert", "stammdaten"),
// Protokoll selbst (#027)
"protokoll.exportiert": a("Protokoll exportiert (CSV)", "protokoll", false),
"protokoll.kette_beschaedigt": a("Hash-Kette beschädigt gefunden", "protokoll", false),
};
/** Text zur Aktion; unbekannte (etwa aus aelteren Versionen) bleiben technisch lesbar. */
export function aktionText(aktion: string): string {
return AKTIONEN[aktion]?.text ?? aktion;
}
/** Alle bekannten Aktionen einer Gruppe – Grundlage fuer den Filter `aktion IN (…)`. */
export function aktionenDerGruppe(gruppe: Aktionsgruppe): string[] {
return Object.entries(AKTIONEN)
.filter(([, b]) => b.gruppe === gruppe)
.map(([aktion]) => aktion);
}
export type AktionsTon = "ok" | "gefahr" | "neutral";
/** Farbe in der Liste: Fehlschlag und Verweigerung immer --danger, Aenderungen --ok. */
export function aktionTon(aktion: string, ergebnis: "ERFOLG" | "FEHLER" | "VERWEIGERT"): AktionsTon {
if (ergebnis !== "ERFOLG") return "gefahr";
return AKTIONEN[aktion]?.aendert ? "ok" : "neutral";
}
export function istAktionsgruppe(wert: string): wert is Aktionsgruppe {
return Object.hasOwn(AKTIONSGRUPPEN, wert);
}