Files
sshkeyvault/src/lib/rotation.ts
T
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

38 lines
1.6 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.
/**
* Namens- und Pfadregeln der gefuehrten Rotation (#023). Rein, damit testbar.
*/
const NAME_SUFFIX = /\s*\(rotiert \d{2}\.\d{2}\.\d{4}\)$/;
const PFAD_SUFFIX = /-r\d{8}(?:-\d+)?$/;
/** TT.MM.JJJJ und JJJJMMTT nach dem Kalender in Berlin. */
export function rotationsDatum(jetzt: Date = new Date()): { anzeige: string; kompakt: string } {
const [jahr, monat, tag] = new Intl.DateTimeFormat("en-CA", { timeZone: "Europe/Berlin" })
.format(jetzt)
.split("-");
return { anzeige: `${tag}.${monat}.${jahr}`, kompakt: `${jahr}${monat}${tag}` };
}
/**
* „Backup A → B (rotiert 12.10.2026)“ – ein frueheres Rotations-Suffix wird ersetzt, damit
* die Namen bei jeder Rotation nicht laenger werden.
*/
export function rotationsName(name: string, datum: string, maxLaenge = 200): string {
const suffix = ` (rotiert ${datum})`;
const basis = name.replace(NAME_SUFFIX, "").trim();
return `${basis.slice(0, Math.max(1, maxLaenge - suffix.length))}${suffix}`;
}
/**
* Neuer Pfad fuer den Private Key des Nachfolgers: gleicher Name mit `-rJJJJMMTT`. Ein alter
* Suffix wird ersetzt; ist der Pfad (auch durch eine fruehere, entfernte Zuweisung) belegt,
* kommt eine laufende Nummer dazu. Zwei Dateien nebeneinander sind gewollt: bis der Nachfolger
* getestet ist, bleibt der alte Schluessel benutzbar.
*/
export function rotationsPfad(pfad: string, datumKompakt: string, belegt: ReadonlySet<string>): string {
const basis = pfad.replace(PFAD_SUFFIX, "");
let kandidat = `${basis}-r${datumKompakt}`;
for (let nummer = 2; belegt.has(kandidat); nummer += 1) kandidat = `${basis}-r${datumKompakt}-${nummer}`;
return kandidat;
}