Files
sshkeyvault/tests/integration/hilfen/host.ts
T
florian.wildeandClaude Opus 5.5 b80f14c5d7 feat(assistent): Host-zu-Host-Assistent mit Vorlagen (#018, #020)
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>
2026-10-12 11:19:28 +02:00

68 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",
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 } });
}