Bisher ließ sich der Instanz-Key nur von Hand auf jedem Host tauschen. Die Rotation läuft jetzt in fünf Schritten: Nachfolger erzeugen, per Dry-Run neben den alten verteilen, Anmeldung je Host prüfen, umstellen, alten Key aus allen Blöcken entfernen und seinen Private Key verwerfen. Der Nachfolger steht während der Rotation in Block und Pflichtliste, damit kein Host ohne funktionierenden Key dasteht. Umgestellt wird nur nach erfolgreicher Prüfung oder bewusstem Übergehen eines Hosts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
70 lines
2.4 KiB
TypeScript
70 lines
2.4 KiB
TypeScript
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
import { randomBytes } from "node:crypto";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { prisma } from "@/lib/db";
|
|
import { verschluesseln } from "@/lib/geheimnisse";
|
|
import { vorgangAusfuehren } from "@/worker/ausfuehren";
|
|
import { masterKeyVergessen } from "@/worker/geheimnisse";
|
|
import { HANDLER } from "@/worker/handler";
|
|
import { rootPasswort, type Ziel } from "./ziele";
|
|
|
|
/** Gemeinsame Vorbereitung fuer Integrationstests: Master-Key, Host mit Bootstrap (#016). */
|
|
|
|
export function masterKeyAnlegen(): () => void {
|
|
const verzeichnis = mkdtempSync(path.join(tmpdir(), "sshkv-master-"));
|
|
const datei = path.join(verzeichnis, "master.key");
|
|
writeFileSync(datei, randomBytes(32), { mode: 0o600 });
|
|
process.env["SSHKV_MASTER_KEY_FILE"] = datei;
|
|
masterKeyVergessen();
|
|
return () => rmSync(verzeichnis, { recursive: true, force: true });
|
|
}
|
|
|
|
export async function vorgangLaufen(
|
|
art:
|
|
| "HOSTKEY_ABRUFEN"
|
|
| "BOOTSTRAP"
|
|
| "TROCKENLAUF"
|
|
| "AUSROLLEN"
|
|
| "VERBINDUNGSTEST"
|
|
| "ZIELKONTO_ANLEGEN"
|
|
| "ZIELKONTO_ENTFERNEN"
|
|
| "ABGLEICH"
|
|
| "ROTATION",
|
|
daten: {
|
|
hostId?: string;
|
|
eingabe?: object;
|
|
geheim?: string;
|
|
angefordertVonId?: string;
|
|
},
|
|
) {
|
|
const vorgang = await prisma.vorgang.create({
|
|
data: {
|
|
art,
|
|
hostId: daten.hostId ?? null,
|
|
eingabe: daten.eingabe ?? {},
|
|
angefordertVonId: daten.angefordertVonId ?? null,
|
|
},
|
|
});
|
|
await vorgangAusfuehren(vorgang.id, HANDLER, daten.geheim);
|
|
return prisma.vorgang.findUniqueOrThrow({ where: { id: vorgang.id } });
|
|
}
|
|
|
|
/** Host anlegen, Host-Key vertrauen, Bootstrap per root-Passwort. */
|
|
export async function eingerichteterHost(
|
|
ziel: Ziel,
|
|
name: string,
|
|
anmeldeart: "DIENSTBENUTZER" | "ROOT" = "DIENSTBENUTZER",
|
|
) {
|
|
const host = await prisma.host.create({ data: { name, adresse: ziel.host, port: ziel.port, anmeldeart } });
|
|
await vorgangLaufen("HOSTKEY_ABRUFEN", { hostId: host.id });
|
|
await prisma.hostSchluessel.updateMany({ where: { hostId: host.id }, data: { status: "VERTRAUT" } });
|
|
const bootstrap = await vorgangLaufen("BOOTSTRAP", {
|
|
hostId: host.id,
|
|
eingabe: { modus: "passwort", benutzer: "root" },
|
|
geheim: verschluesseln(rootPasswort()),
|
|
});
|
|
if (bootstrap.status !== "ERLEDIGT") throw new Error(`Bootstrap fehlgeschlagen: ${bootstrap.fehler}`);
|
|
return prisma.host.findUniqueOrThrow({ where: { id: host.id } });
|
|
}
|