Ein Abgleich liest je Host alle authorized_keys in einem SSH-Aufruf, als jeweiliger Benutzer, und vergleicht sie mit dem Soll: fehlende, von Hand geaenderte und unbekannte Keys, falsche Rechte, beschaedigte Bloecke und veraenderte Private Keys. Befunde werden geoeffnet und wieder geschlossen, Zustaende von Verwendungen und Host nachgezogen. Auf dem Host aendert der Abgleich nichts. Der Zeitplan liegt in pg-boss und feuert auch bei mehreren Workern nur einmal. Auf der Hostseite stehen Abweichungen und der Ist-Zustand je Benutzer; unbekannte Keys lassen sich uebernehmen - nur bekannt machen oder mit Dry-Run in den verwalteten Block holen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
69 lines
2.4 KiB
TypeScript
69 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",
|
|
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 } });
|
|
}
|