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>
61 lines
2.3 KiB
TypeScript
61 lines
2.3 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",
|
|
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 } });
|
|
}
|