From f66d87ad11e1ece604ea5610574a65f3ec43ffc9 Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Mon, 12 Oct 2026 10:17:35 +0200 Subject: [PATCH 1/3] feat(ausrollen): Public Keys mit Trockenlauf und Backup ausrollen (#016) 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 --- src/components/ausrollen/AusrollenPanel.tsx | 133 +++++++++ .../ausrollen/PublicZuweisenFormular.tsx | 77 +++++ src/components/vorgang/useVorgang.ts | 53 ++++ src/server/actions/verwendungen.ts | 96 +++++++ src/server/queries/ziele.ts | 22 ++ src/server/services/soll.ts | 131 +++++++++ src/server/services/verwendungen.ts | 149 ++++++++++ src/worker/ablaeufe/ausrollen.ts | 267 ++++++++++++++++++ src/worker/handler.ts | 5 +- src/worker/ssh/authorized-keys-datei.ts | 141 +++++++++ src/worker/ssh/verbindung.ts | 13 +- tests/integration/ausrollen.test.ts | 195 +++++++++++++ tests/integration/hilfen/host.ts | 60 ++++ 13 files changed, 1339 insertions(+), 3 deletions(-) create mode 100644 src/components/ausrollen/AusrollenPanel.tsx create mode 100644 src/components/ausrollen/PublicZuweisenFormular.tsx create mode 100644 src/components/vorgang/useVorgang.ts create mode 100644 src/server/actions/verwendungen.ts create mode 100644 src/server/queries/ziele.ts create mode 100644 src/server/services/soll.ts create mode 100644 src/server/services/verwendungen.ts create mode 100644 src/worker/ablaeufe/ausrollen.ts create mode 100644 src/worker/ssh/authorized-keys-datei.ts create mode 100644 tests/integration/ausrollen.test.ts create mode 100644 tests/integration/hilfen/host.ts diff --git a/src/components/ausrollen/AusrollenPanel.tsx b/src/components/ausrollen/AusrollenPanel.tsx new file mode 100644 index 0000000..fc4a82b --- /dev/null +++ b/src/components/ausrollen/AusrollenPanel.tsx @@ -0,0 +1,133 @@ +"use client"; + +import { Play, ScanSearch } from "lucide-react"; +import { useRouter } from "next/navigation"; +import { useEffect, useState, useTransition } from "react"; +import { Button } from "@/components/ui/Button"; +import { DiffBox } from "@/components/ui/DiffBox"; +import { laeuftNoch, useVorgang } from "@/components/vorgang/useVorgang"; +import { ausrollenAktion, trockenlaufAktion } from "@/server/actions/verwendungen"; +import type { DiffZeile } from "@/lib/ssh/zeilen-diff"; + +/** + * Dry-Run-Vorschau und Ausfuehren (#016). Was angezeigt wird, ist genau das, was der Worker + * schreibt: der Plan traegt die Hashes, und das Ausrollen bricht ab, wenn sich seitdem auf + * dem Host oder an den Zuweisungen etwas geaendert hat. + */ +type PlanZiel = { + hostName: string; + zielbenutzer: string; + pfad?: string; + geaendert: boolean; + diff: DiffZeile[]; + hinweise: string[]; + fehler?: string; +}; + +export function AusrollenPanel({ schluesselId, offen }: { schluesselId: string; offen: number }) { + const router = useRouter(); + const [trockenlaufId, setTrockenlaufId] = useState(); + const [ausrollenId, setAusrollenId] = useState(); + const [fehler, setFehler] = useState(); + const [startet, starten] = useTransition(); + const trockenlauf = useVorgang(trockenlaufId); + const ausrollen = useVorgang(ausrollenId); + + useEffect(() => { + if (ausrollen.stand && !laeuftNoch(ausrollen.stand)) router.refresh(); + }, [ausrollen.stand, router]); + + const plan = (trockenlauf.stand?.plan as { ziele?: PlanZiel[] } | null)?.ziele; + const aenderungen = plan?.filter((z) => z.geaendert && !z.fehler).length ?? 0; + const laeuft = startet || laeuftNoch(trockenlauf.stand) || laeuftNoch(ausrollen.stand); + const fertig = ausrollen.stand?.status === "ERLEDIGT"; + + return ( +
+
+ + {offen > 0 && !plan && ( + {offen} Zuweisung(en) noch nicht ausgerollt. + )} +
+ + {(fehler || trockenlauf.fehler || trockenlauf.stand?.status === "FEHLGESCHLAGEN") && ( +

+ {fehler ?? trockenlauf.fehler ?? trockenlauf.stand?.fehler} +

+ )} + + {plan?.map((ziel) => ( +
+ {ziel.fehler ? ( +

+ {ziel.zielbenutzer}@{ziel.hostName}: {ziel.fehler} +

+ ) : ( + + )} + {ziel.hinweise.map((h) => ( +

+ ▲ {h} +

+ ))} +
+ ))} + + {plan && aenderungen > 0 && !fertig && ( +
+ +
+ + +
+
+ )} + {plan && aenderungen === 0 && ( +

Alles ist bereits auf dem Soll-Stand.

+ )} + {ausrollen.stand?.status === "FEHLGESCHLAGEN" && ( +

+ {ausrollen.stand.fehler} +

+ )} + {fertig &&

Ausgerollt.

} +
+ ); +} diff --git a/src/components/ausrollen/PublicZuweisenFormular.tsx b/src/components/ausrollen/PublicZuweisenFormular.tsx new file mode 100644 index 0000000..2ff5080 --- /dev/null +++ b/src/components/ausrollen/PublicZuweisenFormular.tsx @@ -0,0 +1,77 @@ +"use client"; + +import { Button, Feld, Formularfehler } from "@/components/ui"; +import { Auswahlliste } from "@/components/ui/Feld"; +import { useFormularAktion } from "@/components/hosts/useFormularAktion"; +import { publicZuweisenAktion } from "@/server/actions/verwendungen"; +import type { ZielAuswahl } from "@/server/queries/ziele"; + +/** + * Public Key einem Host oder einer Hostgruppe zuweisen (#016). Die Zuweisung allein aendert + * noch nichts auf dem Host – erst Trockenlauf und Ausrollen. + */ +export function PublicZuweisenFormular({ + schluesselId, + auswahl, + beiErfolg, +}: { + schluesselId: string; + auswahl: ZielAuswahl; + beiErfolg?: () => void; +}) { + const { zustand, absenden, laeuft } = useFormularAktion(async (vorher, formular) => { + const ergebnis = await publicZuweisenAktion(vorher, formular); + if (!ergebnis.fehler && !ergebnis.feldfehler) beiErfolg?.(); + return ergebnis; + }); + return ( +
+ + + + + + {auswahl.hosts.map((h) => ( + + ))} + + {auswahl.gruppen.length > 0 && ( + + {auswahl.gruppen.map((g) => ( + + ))} + + )} + + {zustand.feldfehler?.["ziel"] && ( +

{zustand.feldfehler["ziel"]}

+ )} + + + + + ); +} diff --git a/src/components/vorgang/useVorgang.ts b/src/components/vorgang/useVorgang.ts new file mode 100644 index 0000000..6560463 --- /dev/null +++ b/src/components/vorgang/useVorgang.ts @@ -0,0 +1,53 @@ +"use client"; + +import { useEffect, useState } from "react"; + +/** Was /api/vorgaenge/[id] liefert (#009, #016). */ +export type VorgangsStand = { + readonly id: string; + readonly status: string; + readonly fehler: string | null; + readonly plan: unknown; + readonly ergebnis: unknown; +}; + +const LAUFEND = new Set(["WARTEND", "LAEUFT"]); + +export function laeuftNoch(stand: VorgangsStand | undefined): boolean { + return stand !== undefined && LAUFEND.has(stand.status); +} + +/** Fragt einen Vorgang jede Sekunde ab, bis er fertig ist. */ +export function useVorgang(vorgangId: string | undefined): { stand?: VorgangsStand; fehler?: string } { + // Der Stand merkt sich seine Vorgangs-ID: so zeigt ein neuer Vorgang nie den alten Stand, + // ohne dass der Effekt synchron zuruecksetzen muss. + const [stand, setStand] = useState(); + const [fehler, setFehler] = useState<{ id: string; text: string }>(); + + useEffect(() => { + if (!vorgangId) return; + let abgebrochen = false; + void (async () => { + while (!abgebrochen) { + const antwort = await fetch(`/api/vorgaenge/${vorgangId}`, { cache: "no-store" }); + if (!antwort.ok) { + setFehler({ id: vorgangId, text: "Status des Vorgangs nicht abrufbar." }); + return; + } + const neu = (await antwort.json()) as VorgangsStand; + if (abgebrochen) return; + setStand(neu); + if (!LAUFEND.has(neu.status)) return; + await new Promise((weiter) => setTimeout(weiter, 1000)); + } + })(); + return () => { + abgebrochen = true; + }; + }, [vorgangId]); + + return { + ...(stand && stand.id === vorgangId ? { stand } : {}), + ...(fehler && fehler.id === vorgangId ? { fehler: fehler.text } : {}), + }; +} diff --git a/src/server/actions/verwendungen.ts b/src/server/actions/verwendungen.ts new file mode 100644 index 0000000..a88ac82 --- /dev/null +++ b/src/server/actions/verwendungen.ts @@ -0,0 +1,96 @@ +"use server"; + +import { revalidatePath } from "next/cache"; +import { z } from "@/lib/validierung"; +import { BestaetigungFehler, RechteFehler, rechtErforderlich } from "@/server/auth/sitzungspflicht"; +import { akteurVon, textfeld, type FormularZustand } from "@/server/actions/formular"; +import { + VerwendungsFehler, + ausrollenStarten, + publicVerwendungAnlegen, + publicVerwendungEntfernen, + trockenlaufStarten, +} from "@/server/services/verwendungen"; + +/** Public-Key-Zuweisungen und Ausrollen (#016). Duenn: Recht, Eingabe, Service. */ + +export type VorgangsAntwort = { readonly vorgangId?: string; readonly fehler?: string }; + +function alsAntwort(fehler: unknown): { fehler: string } { + if ( + fehler instanceof VerwendungsFehler || + fehler instanceof RechteFehler || + fehler instanceof BestaetigungFehler + ) { + return { fehler: fehler.message }; + } + throw fehler; +} + +export async function publicZuweisenAktion( + _vorher: FormularZustand, + formular: FormData, +): Promise { + try { + const { benutzer } = await rechtErforderlich("publicKeyAusrollen"); + const schluesselId = z.string().min(1).max(64).parse(textfeld(formular, "schluesselId")); + // „host:“ oder „gruppe:“ aus einer einzigen Auswahlliste. + const ziel = textfeld(formular, "ziel") ?? ""; + const [art, id] = ziel.split(":"); + if (!id || (art !== "host" && art !== "gruppe")) + return { feldfehler: { ziel: "Bitte ein Ziel wählen." } }; + await publicVerwendungAnlegen( + { + schluesselId, + hostId: art === "host" ? id : null, + hostgruppeId: art === "gruppe" ? id : null, + zielbenutzer: (textfeld(formular, "zielbenutzer") ?? "").trim(), + optionen: textfeld(formular, "optionen") ?? null, + }, + akteurVon(benutzer), + ); + revalidatePath(`/schluessel/${schluesselId}`); + return {}; + } catch (fehler) { + return alsAntwort(fehler); + } +} + +export async function publicEntfernenAktion( + verwendungId: string, + schluesselId: string, +): Promise<{ fehler?: string }> { + try { + const { benutzer } = await rechtErforderlich("publicKeyAusrollen"); + await publicVerwendungEntfernen(z.string().min(1).max(64).parse(verwendungId), akteurVon(benutzer)); + revalidatePath(`/schluessel/${schluesselId}`); + return {}; + } catch (fehler) { + return alsAntwort(fehler); + } +} + +export async function trockenlaufAktion(schluesselId: string): Promise { + try { + const { benutzer } = await rechtErforderlich("publicKeyAusrollen"); + return { + vorgangId: await trockenlaufStarten( + { schluesselId: z.string().min(1).max(64).parse(schluesselId) }, + akteurVon(benutzer), + ), + }; + } catch (fehler) { + return alsAntwort(fehler); + } +} + +export async function ausrollenAktion(trockenlaufId: string): Promise { + try { + const { benutzer } = await rechtErforderlich("publicKeyAusrollen"); + return { + vorgangId: await ausrollenStarten(z.string().min(1).max(64).parse(trockenlaufId), akteurVon(benutzer)), + }; + } catch (fehler) { + return alsAntwort(fehler); + } +} diff --git a/src/server/queries/ziele.ts b/src/server/queries/ziele.ts new file mode 100644 index 0000000..a30b48b --- /dev/null +++ b/src/server/queries/ziele.ts @@ -0,0 +1,22 @@ +import { prisma } from "@/lib/db"; + +/** Auswahl fuer „Public Key ausrollen auf …“ (#016): Hosts mit Bootstrap und Hostgruppen. */ +export async function zielAuswahl() { + const [hosts, gruppen] = await Promise.all([ + prisma.host.findMany({ + where: { archiviertAm: null }, + select: { id: true, name: true, bootstrapAm: true }, + orderBy: { name: "asc" }, + }), + prisma.hostgruppe.findMany({ + select: { id: true, name: true, _count: { select: { mitglieder: true } } }, + orderBy: { name: "asc" }, + }), + ]); + return { + hosts: hosts.map((h) => ({ id: h.id, name: h.name, bereit: h.bootstrapAm !== null })), + gruppen: gruppen.map((g) => ({ id: g.id, name: g.name, mitglieder: g._count.mitglieder })), + }; +} + +export type ZielAuswahl = Awaited>; diff --git a/src/server/services/soll.ts b/src/server/services/soll.ts new file mode 100644 index 0000000..7ca83ba --- /dev/null +++ b/src/server/services/soll.ts @@ -0,0 +1,131 @@ +import type { Host } from "@/generated/prisma/client"; +import { prisma } from "@/lib/db"; +import type { SollEintrag } from "@/lib/ssh/authorized-keys"; + +/** + * Der Soll-Zustand einer authorized_keys (#016): welche Public Keys gehoeren in den + * verwalteten Block eines Benutzers auf einem Host – direkt zugewiesen oder ueber eine + * Hostgruppe. Laeuft im Worker und im Web (Vorschau), deshalb hier und nicht in src/worker. + */ + +/** Dienstbenutzer auf den Hosts (Freigabe Frage 1); gleiche Regel wie im Worker. */ +export function anmeldebenutzerVon(host: Pick): string { + return host.anmeldeart === "ROOT" ? "root" : "sshkv"; +} + +/** Zustaende, in denen eine Verwendung zum Soll gehoert. */ +const IM_SOLL = ["GEPLANT", "AUSGEROLLT", "ABWEICHUNG", "FEHLGESCHLAGEN"] as const; + +export type Soll = { + readonly eintraege: SollEintrag[]; + /** Fingerprints, die nie aus dem Block fallen duerfen (Deployment-Key). */ + readonly pflicht: string[]; + /** Verwendungen, die in diesem Soll stecken oder entfernt werden – fuer den Zustand danach. */ + readonly verwendungIds: { readonly drin: string[]; readonly raus: string[] }; +}; + +async function deploymentKeyFuer(host: Pick) { + if (host.deploymentSchluesselId) { + return prisma.schluessel.findUnique({ + where: { id: host.deploymentSchluesselId }, + select: { publicKey: true, fingerprint: true }, + }); + } + const gruppe = await prisma.hostgruppeMitglied.findFirst({ + where: { hostId: host.id, hostgruppe: { deploymentSchluesselId: { not: null } } }, + select: { + hostgruppe: { select: { deploymentSchluessel: { select: { publicKey: true, fingerprint: true } } } }, + }, + }); + if (gruppe?.hostgruppe.deploymentSchluessel) return gruppe.hostgruppe.deploymentSchluessel; + return prisma.schluessel.findFirst({ + where: { kategorie: "DEPLOYMENT", instanzStandard: true, status: "AKTIV" }, + select: { publicKey: true, fingerprint: true }, + }); +} + +export async function sollFuer(hostId: string, zielbenutzer: string): Promise { + const host = await prisma.host.findUniqueOrThrow({ + where: { id: hostId }, + select: { id: true, anmeldeart: true, deploymentSchluesselId: true }, + }); + const verwendungen = await prisma.publicVerwendung.findMany({ + where: { + zielbenutzer, + OR: [{ hostId }, { hostgruppe: { mitglieder: { some: { hostId } } } }], + zustand: { not: "ENTFERNT" }, + }, + select: { + id: true, + optionen: true, + zustand: true, + angelegtAm: true, + schluessel: { select: { publicKey: true, fingerprint: true, status: true } }, + }, + // Deterministische Reihenfolge im Block: aelteste Zuweisung zuerst. + orderBy: [{ angelegtAm: "asc" }, { id: "asc" }], + }); + + const eintraege: SollEintrag[] = []; + const pflicht: string[] = []; + if (zielbenutzer === anmeldebenutzerVon(host)) { + const deployment = await deploymentKeyFuer(host); + if (deployment) { + eintraege.push({ publicKey: deployment.publicKey, fingerprint: deployment.fingerprint }); + pflicht.push(deployment.fingerprint); + } + } + + const drin: string[] = []; + const raus: string[] = []; + for (const verwendung of verwendungen) { + const aktiv = verwendung.schluessel.status === "AKTIV" || verwendung.schluessel.status === "ROTIERT"; + if ((IM_SOLL as readonly string[]).includes(verwendung.zustand) && aktiv) { + eintraege.push({ + publicKey: verwendung.schluessel.publicKey, + fingerprint: verwendung.schluessel.fingerprint, + optionen: verwendung.optionen, + }); + drin.push(verwendung.id); + } else { + raus.push(verwendung.id); + } + } + return { eintraege, pflicht, verwendungIds: { drin, raus } }; +} + +export type Ziel = { readonly hostId: string; readonly zielbenutzer: string }; + +/** Alle (Host, Benutzer)-Paare, die eine Public-Key-Verwendung betrifft – Gruppen aufgeloest. */ +export async function zieleVonVerwendungen(where: { + schluesselId?: string; + ids?: string[]; +}): Promise { + const verwendungen = await prisma.publicVerwendung.findMany({ + where: { + ...(where.schluesselId ? { schluesselId: where.schluesselId } : {}), + ...(where.ids ? { id: { in: where.ids } } : {}), + zustand: { not: "ENTFERNT" }, + }, + select: { + zielbenutzer: true, + hostId: true, + hostgruppe: { + select: { mitglieder: { select: { hostId: true, host: { select: { archiviertAm: true } } } } }, + }, + }, + }); + const ziele = new Map(); + for (const verwendung of verwendungen) { + const hostIds = verwendung.hostId + ? [verwendung.hostId] + : (verwendung.hostgruppe?.mitglieder.filter((m) => !m.host.archiviertAm).map((m) => m.hostId) ?? []); + for (const hostId of hostIds) { + ziele.set(`${hostId}\u0000${verwendung.zielbenutzer}`, { + hostId, + zielbenutzer: verwendung.zielbenutzer, + }); + } + } + return [...ziele.values()]; +} diff --git a/src/server/services/verwendungen.ts b/src/server/services/verwendungen.ts new file mode 100644 index 0000000..6e5e466 --- /dev/null +++ b/src/server/services/verwendungen.ts @@ -0,0 +1,149 @@ +import { Prisma } from "@/generated/prisma/client"; +import { prisma } from "@/lib/db"; +import { BENUTZERNAME_MUSTER } from "@/lib/shell"; +import { OptionenFehler, optionenPruefen } from "@/lib/ssh/optionen"; +import { protokollieren } from "@/server/services/protokoll"; +import { zieleVonVerwendungen, type Ziel } from "@/server/services/soll"; +import { vorgangEinreihen } from "@/server/vorgaenge/vorgaenge"; + +/** + * Public-Key-Zuweisungen (#016): anlegen, zum Entfernen vormerken, Trockenlauf und + * Ausfuehren anstossen. Geschrieben wird nur im Worker. + */ +export class VerwendungsFehler extends Error { + constructor(nachricht: string) { + super(nachricht); + this.name = "VerwendungsFehler"; + } +} + +type Akteur = { readonly id: string; readonly name: string }; + +export type PublicZuweisung = { + readonly schluesselId: string; + readonly hostId?: string | null; + readonly hostgruppeId?: string | null; + readonly zielbenutzer: string; + readonly optionen?: string | null; +}; + +export async function publicVerwendungAnlegen(eingabe: PublicZuweisung, akteur: Akteur): Promise { + if (!BENUTZERNAME_MUSTER.test(eingabe.zielbenutzer)) { + throw new VerwendungsFehler( + "Ungültiger Zielbenutzer (Kleinbuchstaben, Ziffern, _ und -, höchstens 32 Zeichen).", + ); + } + if (Boolean(eingabe.hostId) === Boolean(eingabe.hostgruppeId)) { + throw new VerwendungsFehler("Bitte genau einen Host oder eine Hostgruppe wählen."); + } + let optionen: string | null = null; + try { + optionen = optionenPruefen(eingabe.optionen ?? "") || null; + } catch (fehler) { + if (fehler instanceof OptionenFehler) throw new VerwendungsFehler(fehler.message); + throw fehler; + } + const schluessel = await prisma.schluessel.findUnique({ + where: { id: eingabe.schluesselId }, + select: { status: true, fingerprint: true, kategorie: true }, + }); + if (!schluessel) throw new VerwendungsFehler("Diesen Schlüssel gibt es nicht."); + if (schluessel.status !== "AKTIV") + throw new VerwendungsFehler("Nur aktive Schlüssel lassen sich zuweisen."); + if (schluessel.kategorie === "DEPLOYMENT") { + throw new VerwendungsFehler("Der Deployment-Key wird von sshkv selbst verwaltet."); + } + const ziel = eingabe.hostId + ? await prisma.host.findUnique({ + where: { id: eingabe.hostId }, + select: { name: true, archiviertAm: true }, + }) + : await prisma.hostgruppe.findUnique({ where: { id: eingabe.hostgruppeId! }, select: { name: true } }); + if (!ziel || ("archiviertAm" in ziel && ziel.archiviertAm)) + throw new VerwendungsFehler("Das Ziel gibt es nicht (mehr)."); + + try { + const verwendung = await prisma.publicVerwendung.create({ + data: { + schluesselId: eingabe.schluesselId, + hostId: eingabe.hostId ?? null, + hostgruppeId: eingabe.hostgruppeId ?? null, + zielbenutzer: eingabe.zielbenutzer, + optionen, + angelegtVonId: akteur.id, + }, + }); + await protokollieren({ + akteur, + aktion: "verwendung.public.angelegt", + ergebnis: "ERFOLG", + hostName: eingabe.hostId ? ziel.name : null, + zielbenutzer: eingabe.zielbenutzer, + fingerprint: schluessel.fingerprint, + details: { hostgruppe: eingabe.hostgruppeId ? ziel.name : null, optionen }, + }); + return verwendung.id; + } catch (fehler) { + if (fehler instanceof Prisma.PrismaClientKnownRequestError && fehler.code === "P2002") { + throw new VerwendungsFehler("Dieser Schlüssel ist dort für diesen Benutzer bereits zugewiesen."); + } + throw fehler; + } +} + +/** Merkt eine Verwendung zum Entfernen vor; wirksam mit dem naechsten Ausrollen. */ +export async function publicVerwendungEntfernen(id: string, akteur: Akteur): Promise { + const verwendung = await prisma.publicVerwendung.findUnique({ + where: { id }, + include: { schluessel: { select: { fingerprint: true } }, host: { select: { name: true } } }, + }); + if (!verwendung || verwendung.zustand === "ENTFERNT") + throw new VerwendungsFehler("Diese Zuweisung gibt es nicht."); + // Nie ausgerollt: einfach weg, auf dem Host steht noch nichts. + const nieAusgerollt = verwendung.zustand === "GEPLANT" && !verwendung.zuletztAngewendetAm; + await prisma.publicVerwendung.update({ + where: { id }, + data: { zustand: nieAusgerollt ? "ENTFERNT" : "ENTFERNEN" }, + }); + await protokollieren({ + akteur, + aktion: "verwendung.public.entfernen", + ergebnis: "ERFOLG", + hostName: verwendung.host?.name ?? null, + zielbenutzer: verwendung.zielbenutzer, + fingerprint: verwendung.schluessel.fingerprint, + }); +} + +/** Trockenlauf fuer die Ziele eines Schluessels oder fuer ausgewaehlte Verwendungen. */ +export async function trockenlaufStarten( + auswahl: { schluesselId?: string; verwendungIds?: string[]; ziele?: Ziel[] }, + akteur: Akteur, +): Promise { + const ziele = + auswahl.ziele ?? + (await zieleVonVerwendungen({ + ...(auswahl.schluesselId ? { schluesselId: auswahl.schluesselId } : {}), + ...(auswahl.verwendungIds ? { ids: auswahl.verwendungIds } : {}), + })); + if (ziele.length === 0) throw new VerwendungsFehler("Es gibt nichts auszurollen."); + return vorgangEinreihen({ + art: "TROCKENLAUF", + angefordertVonId: akteur.id, + schluesselId: auswahl.schluesselId ?? null, + eingabe: { ziele }, + }); +} + +export async function ausrollenStarten(trockenlaufId: string, akteur: Akteur): Promise { + const trockenlauf = await prisma.vorgang.findUnique({ where: { id: trockenlaufId } }); + if (!trockenlauf || trockenlauf.art !== "TROCKENLAUF" || trockenlauf.status !== "ERLEDIGT") { + throw new VerwendungsFehler("Erst den Trockenlauf abwarten."); + } + return vorgangEinreihen({ + art: "AUSROLLEN", + angefordertVonId: akteur.id, + schluesselId: trockenlauf.schluesselId, + eingabe: { trockenlaufId }, + }); +} diff --git a/src/worker/ablaeufe/ausrollen.ts b/src/worker/ablaeufe/ausrollen.ts new file mode 100644 index 0000000..e1ed08f --- /dev/null +++ b/src/worker/ablaeufe/ausrollen.ts @@ -0,0 +1,267 @@ +import { createHash } from "node:crypto"; +import type { Client } from "ssh2"; +import type { Host } from "@/generated/prisma/client"; +import { prisma } from "@/lib/db"; +import { BlockFehler, blockSetzen } from "@/lib/ssh/authorized-keys"; +import { diffVerdichten, type DiffZeile } from "@/lib/ssh/zeilen-diff"; +import { sollFuer, type Ziel } from "@/server/services/soll"; +import { protokollieren } from "@/server/services/protokoll"; +import { VorgangsFehler, type Handler } from "@/worker/ausfuehren"; +import { authorizedKeysHolen, authorizedKeysSchreiben, inhaltHash } from "@/worker/ssh/authorized-keys-datei"; +import { hostVerbinden } from "@/worker/ssh/host"; + +/** + * Public Keys ausrollen (#016) in zwei Schritten: + * + * 1. TROCKENLAUF: je Ziel (Host, Benutzer) die Datei lesen, den Block aus dem Soll setzen und + * Diff, Hinweise und beide Hashes festhalten. Auf dem Host aendert sich nichts. + * 2. AUSROLLEN: genau diesen Plan ausfuehren. Hat sich die Datei oder das Soll seitdem + * geaendert, bricht das Ziel ab (optimistische Sperre ueber die Hashes) – der Mensch + * soll nie etwas anderes schreiben, als er gesehen hat. + */ + +/** So lange darf ein Trockenlauf alt sein, bevor er ausgefuehrt wird. */ +export const TROCKENLAUF_GUELTIG_MS = 15 * 60 * 1000; + +export type PlanZiel = { + readonly hostId: string; + readonly hostName: string; + readonly zielbenutzer: string; + readonly pfad?: string; + readonly vorher?: string; + readonly nachher?: string; + readonly geaendert: boolean; + readonly diff: DiffZeile[]; + readonly hinweise: string[]; + readonly fehler?: string; +}; + +export type Plan = { readonly ziele: PlanZiel[] }; + +function hashVon(text: string): string { + return createHash("sha256").update(Buffer.from(text, "utf8")).digest("hex"); +} + +function fehlerText(fehler: unknown): string { + if (fehler instanceof VorgangsFehler || fehler instanceof BlockFehler) return fehler.message; + return "Interner Fehler – Details im Worker-Log."; +} + +function zieleLesen(eingabe: unknown): Ziel[] { + const ziele = (eingabe as { ziele?: unknown } | null)?.ziele; + if (!Array.isArray(ziele) || ziele.length === 0) + throw new VorgangsFehler("Der Trockenlauf nennt keine Ziele."); + return ziele.map((z) => { + const ziel = z as Partial; + if (typeof ziel.hostId !== "string" || typeof ziel.zielbenutzer !== "string") { + throw new VorgangsFehler("Ungültiges Ziel im Trockenlauf."); + } + return { hostId: ziel.hostId, zielbenutzer: ziel.zielbenutzer }; + }); +} + +/** Ziele nach Host gruppieren: je Host genau eine SSH-Verbindung. */ +async function jeHost( + ziele: readonly Ziel[], + arbeit: (host: Host, verbindung: Client, benutzer: string) => Promise, + beiFehler: (host: Pick, benutzer: string, fehler: unknown) => T, +): Promise { + const nachHost = new Map(); + for (const ziel of ziele) + nachHost.set(ziel.hostId, [...(nachHost.get(ziel.hostId) ?? []), ziel.zielbenutzer]); + const ergebnisse: T[] = []; + for (const [hostId, benutzerListe] of nachHost) { + const host = await prisma.host.findUnique({ where: { id: hostId } }); + if (!host || host.archiviertAm) { + for (const benutzer of benutzerListe) { + ergebnisse.push( + beiFehler( + { id: hostId, name: host?.name ?? "?" }, + benutzer, + new VorgangsFehler("Host ist archiviert oder fehlt."), + ), + ); + } + continue; + } + let verbindung: Client; + try { + verbindung = await hostVerbinden(host); + } catch (fehler) { + for (const benutzer of benutzerListe) ergebnisse.push(beiFehler(host, benutzer, fehler)); + continue; + } + try { + for (const benutzer of benutzerListe) { + try { + ergebnisse.push(await arbeit(host, verbindung, benutzer)); + } catch (fehler) { + ergebnisse.push(beiFehler(host, benutzer, fehler)); + } + } + } finally { + verbindung.end(); + } + } + return ergebnisse; +} + +export const trockenlaufHandler: Handler = async ({ vorgang }) => { + const ziele = zieleLesen(vorgang.eingabe); + const planZiele = await jeHost( + ziele, + async (host, verbindung, benutzer) => { + const stand = await authorizedKeysHolen(verbindung, host, benutzer); + const soll = await sollFuer(host.id, benutzer); + const ergebnis = blockSetzen(stand.inhalt, soll.eintraege, soll.pflicht); + return { + hostId: host.id, + hostName: host.name, + zielbenutzer: benutzer, + pfad: stand.pfad, + vorher: stand.sha256, + nachher: hashVon(ergebnis.inhalt), + geaendert: ergebnis.geaendert || !stand.vorhanden, + diff: diffVerdichten(ergebnis.diff), + hinweise: ergebnis.hinweise, + }; + }, + (host, benutzer, fehler) => ({ + hostId: host.id, + hostName: host.name, + zielbenutzer: benutzer, + geaendert: false, + diff: [], + hinweise: [], + fehler: fehlerText(fehler), + }), + ); + return { plan: { ziele: planZiele }, ergebnis: { ziele: planZiele.length } }; +}; + +export const ausrollenHandler: Handler = async ({ vorgang }) => { + const trockenlaufId = (vorgang.eingabe as { trockenlaufId?: unknown } | null)?.trockenlaufId; + if (typeof trockenlaufId !== "string") throw new VorgangsFehler("Kein Trockenlauf angegeben."); + const trockenlauf = await prisma.vorgang.findUnique({ where: { id: trockenlaufId } }); + if ( + !trockenlauf || + trockenlauf.art !== "TROCKENLAUF" || + trockenlauf.status !== "ERLEDIGT" || + !trockenlauf.plan + ) { + throw new VorgangsFehler("Der Trockenlauf ist nicht abgeschlossen."); + } + if (Date.now() - (trockenlauf.beendetAm ?? trockenlauf.angelegtAm).getTime() > TROCKENLAUF_GUELTIG_MS) { + throw new VorgangsFehler("Der Trockenlauf ist älter als 15 Minuten. Bitte neu starten."); + } + const bereitsAusgefuehrt = await prisma.vorgang.findFirst({ + where: { + art: "AUSROLLEN", + id: { not: vorgang.id }, + status: { in: ["LAEUFT", "ERLEDIGT"] }, + eingabe: { equals: { trockenlaufId } }, + }, + }); + if (bereitsAusgefuehrt) throw new VorgangsFehler("Dieser Trockenlauf wurde bereits ausgeführt."); + + const plan = trockenlauf.plan as unknown as Plan; + const auszufuehren = plan.ziele.filter((ziel) => ziel.geaendert && !ziel.fehler); + const akteur = vorgang.angefordertVonId + ? await prisma.benutzer.findUnique({ + where: { id: vorgang.angefordertVonId }, + select: { id: true, name: true }, + }) + : null; + + type Ausgang = { + readonly hostId: string; + readonly zielbenutzer: string; + readonly erfolg: boolean; + readonly fehler?: string; + }; + const geplant = new Map(auszufuehren.map((z) => [`${z.hostId}\u0000${z.zielbenutzer}`, z])); + + const ausgaenge = await jeHost( + auszufuehren, + async (host, verbindung, benutzer) => { + const ziel = geplant.get(`${host.id}\u0000${benutzer}`)!; + const stand = await authorizedKeysHolen(verbindung, host, benutzer); + if (stand.sha256 !== ziel.vorher) { + throw new VorgangsFehler( + `authorized_keys von ${benutzer}@${host.name} wurde seit dem Trockenlauf geändert. Bitte neuen Trockenlauf starten.`, + ); + } + const soll = await sollFuer(host.id, benutzer); + const ergebnis = blockSetzen(stand.inhalt, soll.eintraege, soll.pflicht); + if (hashVon(ergebnis.inhalt) !== ziel.nachher) { + throw new VorgangsFehler( + `Die Zuweisungen für ${benutzer}@${host.name} haben sich seit dem Trockenlauf geändert. Bitte neuen Trockenlauf starten.`, + ); + } + await authorizedKeysSchreiben(verbindung, host, benutzer, ergebnis.inhalt, stand.sha256); + await protokollieren({ + akteur: akteur ?? "System", + aktion: "authorized_keys.geschrieben", + ergebnis: "ERFOLG", + hostName: host.name, + zielbenutzer: benutzer, + diff: ergebnis.diff + .filter((z) => z.art !== "kontext") + .map((z) => `${z.art === "hinzu" ? "+" : "-"} ${z.text}`) + .join("\n"), + details: { + vorher: stand.sha256, + nachher: inhaltHash(Buffer.from(ergebnis.inhalt, "utf8")), + trockenlaufId, + }, + }); + return { hostId: host.id, zielbenutzer: benutzer, erfolg: true }; + }, + (host, benutzer, fehler) => ({ + hostId: host.id, + zielbenutzer: benutzer, + erfolg: false, + fehler: fehlerText(fehler), + }), + ); + + // Zustand der Verwendungen nachziehen – auch fuer Ziele ohne Aenderung, die schon stimmten. + const fehlgeschlagen = new Set( + ausgaenge.filter((a) => !a.erfolg).map((a) => `${a.hostId}\u0000${a.zielbenutzer}`), + ); + // Fehlschlaege zuletzt: eine Gruppen-Verwendung, die auf einem Host scheitert, soll + // FEHLGESCHLAGEN bleiben, auch wenn ein anderer Host danach erfolgreich war. + const reihenfolge = plan.ziele + .filter((z) => !z.fehler) + .sort( + (a, b) => + Number(fehlgeschlagen.has(`${a.hostId}\u0000${a.zielbenutzer}`)) - + Number(fehlgeschlagen.has(`${b.hostId}\u0000${b.zielbenutzer}`)), + ); + for (const ziel of reihenfolge) { + const schluessel = `${ziel.hostId}\u0000${ziel.zielbenutzer}`; + const soll = await sollFuer(ziel.hostId, ziel.zielbenutzer); + const jetzt = new Date(); + if (fehlgeschlagen.has(schluessel)) { + await prisma.publicVerwendung.updateMany({ + where: { id: { in: soll.verwendungIds.drin } }, + data: { zustand: "FEHLGESCHLAGEN" }, + }); + continue; + } + await prisma.publicVerwendung.updateMany({ + where: { id: { in: soll.verwendungIds.drin } }, + data: { zustand: "AUSGEROLLT", zuletztAngewendetAm: jetzt }, + }); + await prisma.publicVerwendung.updateMany({ + where: { id: { in: soll.verwendungIds.raus }, zustand: "ENTFERNEN" }, + data: { zustand: "ENTFERNT", zuletztAngewendetAm: jetzt }, + }); + } + + const fehler = ausgaenge.filter((a) => !a.erfolg); + if (fehler.length > 0 && fehler.length === ausgaenge.length) { + throw new VorgangsFehler(fehler.map((f) => f.fehler).join(" ")); + } + return { ergebnis: { geschrieben: ausgaenge.filter((a) => a.erfolg).length, fehler } }; +}; diff --git a/src/worker/handler.ts b/src/worker/handler.ts index 0596c06..dae834e 100644 --- a/src/worker/handler.ts +++ b/src/worker/handler.ts @@ -1,12 +1,15 @@ import type { HandlerTabelle } from "@/worker/ausfuehren"; +import { ausrollenHandler, trockenlaufHandler } from "@/worker/ablaeufe/ausrollen"; import { bootstrapHandler } from "@/worker/ablaeufe/bootstrap"; import { erreichbarkeitHandler, hostKeyAbrufenHandler } from "@/worker/ablaeufe/hostkey"; /** - * Welche Vorgangsart wer ausfuehrt. Waechst mit den Issues: Ausrollen (#016), Abgleich (#021) … + * Welche Vorgangsart wer ausfuehrt. Waechst mit den Issues: Abgleich (#021) … */ export const HANDLER: HandlerTabelle = { HOSTKEY_ABRUFEN: hostKeyAbrufenHandler, ERREICHBARKEIT: erreichbarkeitHandler, BOOTSTRAP: bootstrapHandler, + TROCKENLAUF: trockenlaufHandler, + AUSROLLEN: ausrollenHandler, }; diff --git a/src/worker/ssh/authorized-keys-datei.ts b/src/worker/ssh/authorized-keys-datei.ts new file mode 100644 index 0000000..db2226d --- /dev/null +++ b/src/worker/ssh/authorized-keys-datei.ts @@ -0,0 +1,141 @@ +import { createHash } from "node:crypto"; +import type { Client } from "ssh2"; +import type { Host } from "@/generated/prisma/client"; +import { BENUTZERNAME_MUSTER, shellQuote } from "@/lib/shell"; +import { VorgangsFehler } from "@/worker/ausfuehren"; +import { befehlAusfuehren } from "@/worker/ssh/verbindung"; + +/** + * authorized_keys eines Benutzers auf einem Host lesen und schreiben (#016). + * + * Wie beim Bootstrap: alles im Home des Zielbenutzers geschieht **als dieser Benutzer** + * (`runuser`), damit ein Symlink root nie eine fremde Datei lesen oder schreiben laesst. + * Der Hash der Datei wird auf dem Host berechnet und beim Schreiben dort erneut geprueft – + * hat sich die Datei seit dem Trockenlauf geaendert, bricht der Vorgang ab. + */ + +/** Hash fuer „Datei gibt es nicht“. */ +export const KEINE_DATEI = "keine-datei"; +/** So viele Sicherungen bleiben je Datei liegen. */ +const SICHERUNGEN_BEHALTEN = 5; + +const MARKE_FEHLT = "SSHKV-BENUTZER-FEHLT"; +const MARKE_GEAENDERT = "SSHKV-GEAENDERT"; +const MARKE_OK = "SSHKV-GESCHRIEBEN"; + +export type DateiStand = { + readonly pfad: string; + readonly vorhanden: boolean; + readonly sha256: string; + readonly inhalt: string; +}; + +function praefix(host: Pick): string { + return host.anmeldeart === "ROOT" ? "" : "sudo -n "; +} + +function benutzerPruefen(benutzer: string): void { + if (!BENUTZERNAME_MUSTER.test(benutzer)) throw new VorgangsFehler(`Ungültiger Zielbenutzer „${benutzer}“.`); +} + +export function inhaltHash(inhalt: Buffer | null): string { + return inhalt === null ? KEINE_DATEI : createHash("sha256").update(inhalt).digest("hex"); +} + +/** + * Liest die Datei. Ausgabeformat des Skripts: erste Zeile ` `, danach die Datei + * unveraendert – so kommen auch Bytes an, die kein Text sind. + */ +export async function authorizedKeysHolen( + verbindung: Client, + host: Host, + benutzer: string, +): Promise { + benutzerPruefen(benutzer); + const skript = `set -eu +B=${shellQuote(benutzer)} +getent passwd "$B" >/dev/null || { echo ${MARKE_FEHLT}; exit 3; } +H=$(getent passwd "$B" | cut -d: -f6) +D="$H/.ssh/authorized_keys" +if runuser -u "$B" -- test -e "$D"; then echo "$H ja"; runuser -u "$B" -- cat "$D"; else echo "$H nein"; fi`; + const ergebnis = await befehlAusfuehren( + verbindung, + `${praefix(host)}sh -c ${shellQuote(skript)}`, + undefined, + true, + ); + const roh = ergebnis.rohStdout ?? Buffer.alloc(0); + if (roh.toString("utf8").startsWith(MARKE_FEHLT)) { + throw new VorgangsFehler(`Den Benutzer „${benutzer}“ gibt es auf ${host.name} nicht.`); + } + if (ergebnis.code !== 0) { + throw new VorgangsFehler(`authorized_keys von ${benutzer}@${host.name} ließ sich nicht lesen.`); + } + const zeilenende = roh.indexOf(0x0a); + const kopf = roh.subarray(0, zeilenende).toString("utf8"); + const vorhanden = kopf.endsWith(" ja"); + const home = kopf.replace(/ (ja|nein)$/, ""); + const inhalt = roh.subarray(zeilenende + 1); + // Nur gueltiges UTF-8 wird bearbeitet: sonst gingen fremde Bytes beim Zurueckschreiben + // verloren, und „bytegenau“ waere gebrochen. + if (!Buffer.from(inhalt.toString("utf8"), "utf8").equals(inhalt)) { + throw new VorgangsFehler( + `authorized_keys von ${benutzer}@${host.name} enthält ungültiges UTF-8. Bitte von Hand prüfen.`, + ); + } + return { + pfad: `${home}/.ssh/authorized_keys`, + vorhanden, + sha256: inhaltHash(vorhanden ? inhalt : null), + inhalt: inhalt.toString("utf8"), + }; +} + +/** + * Schreibt die Datei, wenn ihr Hash auf dem Host noch `erwartet` ist: Sicherung, Temp-Datei, + * Rechte, `mv`. Alte Sicherungen ueber SICHERUNGEN_BEHALTEN hinaus werden entfernt. + */ +export async function authorizedKeysSchreiben( + verbindung: Client, + host: Host, + benutzer: string, + inhalt: string, + erwartet: string, +): Promise<{ sicherung: boolean }> { + benutzerPruefen(benutzer); + if (!/^([0-9a-f]{64}|keine-datei)$/.test(erwartet)) throw new VorgangsFehler("Ungültiger Datei-Hash."); + const skript = `set -eu +umask 077 +B=${shellQuote(benutzer)} +ERWARTET=${shellQuote(erwartet)} +getent passwd "$B" >/dev/null || { echo ${MARKE_FEHLT}; exit 3; } +H=$(getent passwd "$B" | cut -d: -f6) +als() { runuser -u "$B" -- "$@"; } +als mkdir -p "$H/.ssh" +als chmod 0700 "$H/.ssh" +D="$H/.ssh/authorized_keys" +if als test -e "$D"; then IST=$(als cat "$D" | sha256sum | cut -d' ' -f1); else IST=${KEINE_DATEI}; fi +[ "$IST" = "$ERWARTET" ] || { echo ${MARKE_GEAENDERT}; exit 4; } +if als test -e "$D"; then + als cp -p "$D" "$D.sshkv.bak.$(date +%Y%m%d-%H%M%S)" + als sh -c 'ls -1t "$1".sshkv.bak.* 2>/dev/null | tail -n +${SICHERUNGEN_BEHALTEN + 1} | while read -r alt; do rm -f -- "$alt"; done' sh "$D" +fi +N=$(als mktemp "$H/.ssh/.sshkv.tmp.XXXXXX") +als tee "$N" >/dev/null +als chmod 0600 "$N" +als mv "$N" "$D" +echo ${MARKE_OK}`; + const ergebnis = await befehlAusfuehren(verbindung, `${praefix(host)}sh -c ${shellQuote(skript)}`, inhalt); + if (ergebnis.stdout.includes(MARKE_GEAENDERT)) { + throw new VorgangsFehler( + `authorized_keys von ${benutzer}@${host.name} wurde seit dem Trockenlauf geändert. Bitte neuen Trockenlauf starten.`, + ); + } + if (ergebnis.stdout.includes(MARKE_FEHLT)) { + throw new VorgangsFehler(`Den Benutzer „${benutzer}“ gibt es auf ${host.name} nicht.`); + } + if (ergebnis.code !== 0 || !ergebnis.stdout.includes(MARKE_OK)) { + throw new VorgangsFehler(`authorized_keys von ${benutzer}@${host.name} ließ sich nicht schreiben.`); + } + return { sicherung: erwartet !== KEINE_DATEI }; +} diff --git a/src/worker/ssh/verbindung.ts b/src/worker/ssh/verbindung.ts index 8f1f972..c00ebf9 100644 --- a/src/worker/ssh/verbindung.ts +++ b/src/worker/ssh/verbindung.ts @@ -189,7 +189,13 @@ function verbindungsFehler(fehler: unknown, ziel: Ziel): VorgangsFehler { return new VorgangsFehler(`SSH-Verbindung zu ${ziel.adresse} fehlgeschlagen.`, { cause: fehler }); } -export type Befehlsergebnis = { readonly stdout: string; readonly stderr: string; readonly code: number }; +export type Befehlsergebnis = { + readonly stdout: string; + readonly stderr: string; + readonly code: number; + /** Nur mit `roh = true`: stdout als Bytes, fuer Dateien, die exakt ankommen muessen. */ + readonly rohStdout?: Buffer; +}; /** * Fuehrt einen Befehl aus. `stdin` wird geschrieben und geschlossen – so reisen Dateiinhalte @@ -199,6 +205,7 @@ export function befehlAusfuehren( client: Client, befehl: string, stdin?: string | Buffer, + roh = false, ): Promise { return new Promise((erfuellen, ablehnen) => { client.exec(befehl, (fehler, strom) => { @@ -227,8 +234,10 @@ export function befehlAusfuehren( if (laenge > AUSGABE_HOECHSTENS) { return ablehnen(new VorgangsFehler("Der Host lieferte mehr Ausgabe als erwartet.")); } + const stdout = Buffer.concat(ausgabe); erfuellen({ - stdout: Buffer.concat(ausgabe).toString("utf8"), + ...(roh ? { rohStdout: stdout } : {}), + stdout: stdout.toString("utf8"), stderr: Buffer.concat(fehlerAusgabe).toString("utf8"), code: code ?? -1, }); diff --git a/tests/integration/ausrollen.test.ts b/tests/integration/ausrollen.test.ts new file mode 100644 index 0000000..47e714c --- /dev/null +++ b/tests/integration/ausrollen.test.ts @@ -0,0 +1,195 @@ +import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import { prisma } from "@/lib/db"; +import { schluesselpaarErzeugen } from "@/lib/ssh/openssh"; +import { publicVerwendungAnlegen, publicVerwendungEntfernen } from "@/server/services/verwendungen"; +import { deploymentKeySicherstellen } from "@/worker/geheimnisse"; +import type { Plan } from "@/worker/ablaeufe/ausrollen"; +import { datenbankLeeren } from "../hilfen/db"; +import { eingerichteterHost, masterKeyAnlegen, vorgangLaufen } from "./hilfen/host"; +import { befehlAlsRoot, zielA, zielBereit } from "./hilfen/ziele"; + +/** Public Keys ausrollen gegen ein echtes sshd (#016). */ + +const AKTEUR = { id: "test", name: "Test" }; +let aufraeumen: () => void; + +beforeAll(async () => { + await zielBereit(zielA); + aufraeumen = masterKeyAnlegen(); +}); +afterAll(() => aufraeumen()); + +beforeEach(async () => { + await datenbankLeeren(); + await deploymentKeySicherstellen(); + await befehlAlsRoot( + zielA, + "userdel -r sshkv 2>/dev/null; rm -f /etc/sudoers.d/sshkv /root/.ssh/authorized_keys* /home/test/.ssh/authorized_keys*; true", + ); +}); + +async function schluesselAnlegen(name: string) { + const paar = await schluesselpaarErzeugen("ED25519", { kommentar: `sshkv:${name}` }); + return prisma.schluessel.create({ + data: { + name, + slug: name, + typ: "ED25519", + kategorie: "DIENST", + publicKey: paar.publicKeyZeile, + fingerprint: paar.fingerprint, + kommentar: `sshkv:${name}`, + }, + }); +} + +async function trockenlauf(schluesselId: string) { + const vorgang = await vorgangLaufen("TROCKENLAUF", { + eingabe: { + ziele: ( + await prisma.publicVerwendung.findMany({ + where: { schluesselId }, + include: { hostgruppe: { include: { mitglieder: true } } }, + }) + ).flatMap((v) => + v.hostId + ? [{ hostId: v.hostId, zielbenutzer: v.zielbenutzer }] + : (v.hostgruppe?.mitglieder.map((m) => ({ hostId: m.hostId, zielbenutzer: v.zielbenutzer })) ?? []), + ), + }, + }); + expect(vorgang.status).toBe("ERLEDIGT"); + return { id: vorgang.id, plan: vorgang.plan as unknown as Plan }; +} + +const datei = async (pfad: string) => (await befehlAlsRoot(zielA, `cat ${pfad} 2>/dev/null || true`)).stdout; + +describe("Public Key ausrollen", () => { + it("zeigt im Trockenlauf die Aenderung, schreibt erst beim Ausrollen", async () => { + const host = await eingerichteterHost(zielA, "ziel-a"); + await befehlAlsRoot(zielA, "mkdir -p /root/.ssh && printf '# von Hand\\n' > /root/.ssh/authorized_keys"); + const schluessel = await schluesselAnlegen("backup"); + const verwendungId = await publicVerwendungAnlegen( + { + schluesselId: schluessel.id, + hostId: host.id, + zielbenutzer: "root", + optionen: 'restrict,from="10.0.0.12"', + }, + AKTEUR, + ); + + const { id, plan } = await trockenlauf(schluessel.id); + expect(plan.ziele).toHaveLength(1); + expect(plan.ziele[0]).toMatchObject({ + zielbenutzer: "root", + geaendert: true, + pfad: "/root/.ssh/authorized_keys", + }); + expect(plan.ziele[0]!.diff).toContainEqual({ + art: "hinzu", + text: `restrict,from="10.0.0.12" ${schluessel.publicKey}`, + }); + expect(await datei("/root/.ssh/authorized_keys")).toBe("# von Hand\n"); + + const ausrollen = await vorgangLaufen("AUSROLLEN", { eingabe: { trockenlaufId: id } }); + expect(ausrollen.fehler).toBeNull(); + const inhalt = await datei("/root/.ssh/authorized_keys"); + expect(inhalt).toBe( + `# von Hand\n# BEGIN SSHKV MANAGED – nicht manuell bearbeiten\nrestrict,from="10.0.0.12" ${schluessel.publicKey}\n# END SSHKV MANAGED\n`, + ); + expect((await prisma.publicVerwendung.findUniqueOrThrow({ where: { id: verwendungId } })).zustand).toBe( + "AUSGEROLLT", + ); + expect((await befehlAlsRoot(zielA, "ls /root/.ssh | grep -c sshkv.bak")).stdout.trim()).toBe("1"); + expect((await befehlAlsRoot(zielA, "stat -c %a /root/.ssh/authorized_keys")).stdout.trim()).toBe("600"); + const eintrag = await prisma.protokoll.findFirst({ where: { aktion: "authorized_keys.geschrieben" } }); + expect(eintrag?.diff).toContain("+ restrict"); + + // Derselbe Trockenlauf nicht zweimal. + const nochmal = await vorgangLaufen("AUSROLLEN", { eingabe: { trockenlaufId: id } }); + expect(nochmal.fehler).toMatch(/bereits ausgeführt/); + }); + + it("bricht ab, wenn die Datei seit dem Trockenlauf geaendert wurde", async () => { + const host = await eingerichteterHost(zielA, "ziel-a"); + const schluessel = await schluesselAnlegen("backup"); + await publicVerwendungAnlegen( + { schluesselId: schluessel.id, hostId: host.id, zielbenutzer: "root" }, + AKTEUR, + ); + const { id } = await trockenlauf(schluessel.id); + await befehlAlsRoot(zielA, "mkdir -p /root/.ssh && echo '# dazwischen' >> /root/.ssh/authorized_keys"); + const ausrollen = await vorgangLaufen("AUSROLLEN", { eingabe: { trockenlaufId: id } }); + expect(ausrollen.status).toBe("FEHLGESCHLAGEN"); + expect(ausrollen.fehler).toMatch(/seit dem Trockenlauf geändert/); + expect(await datei("/root/.ssh/authorized_keys")).toBe("# dazwischen\n"); + }); + + it("bricht ab, wenn sich die Zuweisungen seit dem Trockenlauf geaendert haben", async () => { + const host = await eingerichteterHost(zielA, "ziel-a"); + const a = await schluesselAnlegen("a"); + const b = await schluesselAnlegen("b"); + await publicVerwendungAnlegen({ schluesselId: a.id, hostId: host.id, zielbenutzer: "root" }, AKTEUR); + const { id } = await trockenlauf(a.id); + await publicVerwendungAnlegen({ schluesselId: b.id, hostId: host.id, zielbenutzer: "root" }, AKTEUR); + const ausrollen = await vorgangLaufen("AUSROLLEN", { eingabe: { trockenlaufId: id } }); + expect(ausrollen.fehler).toMatch(/Zuweisungen .* geändert/); + }); + + it("entfernt einen Schluessel wieder, und der Deployment-Key bleibt", async () => { + const host = await eingerichteterHost(zielA, "ziel-a"); + const schluessel = await schluesselAnlegen("weg"); + const id = await publicVerwendungAnlegen( + { schluesselId: schluessel.id, hostId: host.id, zielbenutzer: "sshkv" }, + AKTEUR, + ); + const erster = await trockenlauf(schluessel.id); + // Im Block des Dienstbenutzers steht der Deployment-Key schon; nur der neue kommt hinzu. + expect(erster.plan.ziele[0]!.diff.filter((z) => z.art !== "kontext")).toEqual([ + { art: "hinzu", text: schluessel.publicKey }, + ]); + await vorgangLaufen("AUSROLLEN", { eingabe: { trockenlaufId: erster.id } }); + expect(await datei("/home/sshkv/.ssh/authorized_keys")).toContain(schluessel.publicKey); + + await publicVerwendungEntfernen(id, AKTEUR); + const zweiter = await trockenlauf(schluessel.id); + expect(zweiter.plan.ziele[0]!.diff).toContainEqual({ art: "entfernt", text: schluessel.publicKey }); + await vorgangLaufen("AUSROLLEN", { eingabe: { trockenlaufId: zweiter.id } }); + const inhalt = await datei("/home/sshkv/.ssh/authorized_keys"); + expect(inhalt).not.toContain(schluessel.publicKey); + expect(inhalt).toContain("sshkv-deployment@"); + expect((await prisma.publicVerwendung.findUniqueOrThrow({ where: { id } })).zustand).toBe("ENTFERNT"); + }); + + it("rollt ueber eine Hostgruppe fuer einen gewoehnlichen Benutzer aus", async () => { + const host = await eingerichteterHost(zielA, "ziel-a"); + const gruppe = await prisma.hostgruppe.create({ + data: { name: "Test", mitglieder: { create: { hostId: host.id } } }, + }); + const schluessel = await schluesselAnlegen("gruppe"); + await publicVerwendungAnlegen( + { schluesselId: schluessel.id, hostgruppeId: gruppe.id, zielbenutzer: "test" }, + AKTEUR, + ); + const { id } = await trockenlauf(schluessel.id); + await vorgangLaufen("AUSROLLEN", { eingabe: { trockenlaufId: id } }); + expect(await datei("/home/test/.ssh/authorized_keys")).toContain(schluessel.publicKey); + const besitz = await befehlAlsRoot( + zielA, + "stat -c '%U %a' /home/test/.ssh /home/test/.ssh/authorized_keys", + ); + expect(besitz.stdout).toBe("test 700\ntest 600\n"); + }); + + it("meldet einen fehlenden Benutzer im Trockenlauf je Ziel", async () => { + const host = await eingerichteterHost(zielA, "ziel-a"); + const schluessel = await schluesselAnlegen("niemand"); + await publicVerwendungAnlegen( + { schluesselId: schluessel.id, hostId: host.id, zielbenutzer: "gibtsnicht" }, + AKTEUR, + ); + const { plan } = await trockenlauf(schluessel.id); + expect(plan.ziele[0]!.fehler).toMatch(/gibt es auf ziel-a nicht/); + }); +}); diff --git a/tests/integration/hilfen/host.ts b/tests/integration/hilfen/host.ts new file mode 100644 index 0000000..455352b --- /dev/null +++ b/tests/integration/hilfen/host.ts @@ -0,0 +1,60 @@ +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 } }); +} From 30f271b23a1e7c834f9e1e19bac7b3e0d85e0f26 Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Mon, 12 Oct 2026 10:19:27 +0200 Subject: [PATCH 2/3] feat(hosts): Schluessel auf dem Host anzeigen und ausrollen (#016) Die Hostseite listet, welche Public und Private Keys sshkv dort verwaltet - direkt oder ueber eine Hostgruppe - mit Zustand als Punkt und Text. Ein Dry-Run je Host zeigt das Diff aller Benutzer dieses Hosts und rollt nach Bestaetigung aus. Co-Authored-By: Claude Opus 5.5 --- src/app/(app)/hosts/[id]/page.tsx | 70 +++++++++++++++++-- src/components/ausrollen/AusrollenPanel.tsx | 6 +- .../ausrollen/VerwendungsZustand.tsx | 28 ++++++++ src/server/actions/verwendungen.ts | 9 ++- src/server/queries/ziele.ts | 33 +++++++++ src/server/services/soll.ts | 13 +++- src/server/services/verwendungen.ts | 5 +- 7 files changed, 151 insertions(+), 13 deletions(-) create mode 100644 src/components/ausrollen/VerwendungsZustand.tsx diff --git a/src/app/(app)/hosts/[id]/page.tsx b/src/app/(app)/hosts/[id]/page.tsx index 90fa9f8..8ddf26d 100644 --- a/src/app/(app)/hosts/[id]/page.tsx +++ b/src/app/(app)/hosts/[id]/page.tsx @@ -18,6 +18,9 @@ import { sitzungErforderlich } from "@/server/auth/sitzungspflicht"; import { hostDetail, type HostDetail } from "@/server/queries/hosts"; import { BootstrapAktionen } from "./BootstrapAktionen"; import { sshdHinweise } from "@/lib/sshd-hinweise"; +import { AusrollenPanel } from "@/components/ausrollen/AusrollenPanel"; +import { VerwendungsZustand } from "@/components/ausrollen/VerwendungsZustand"; +import { verwendungenAufHost } from "@/server/queries/ziele"; import { HostKeyAktionen } from "./HostKeyAktionen"; import { HostKeyEntscheidung } from "./HostKeyEntscheidung"; import { bootstrapBefehlFuer } from "@/server/queries/bootstrap"; @@ -95,6 +98,8 @@ export default async function HostSeite({ params }: { params: Promise<{ id: stri const archiviert = host.archiviertAm !== null; const verwalten = darf(benutzer.rolle, "hostsVerwalten") && !archiviert; const darfEntscheiden = darf(benutzer.rolle, "hostKeyEntscheiden") && !archiviert; + const darfAusrollen = darf(benutzer.rolle, "publicKeyAusrollen") && !archiviert; + const verwendungen = await verwendungenAufHost(host.id); const jetzt = new Date(); return ( @@ -217,7 +222,7 @@ export default async function HostSeite({ params }: { params: Promise<{ id: stri {host.zielkonten.length > 0 && (

@@ -230,11 +235,64 @@ export default async function HostSeite({ params }: { params: Promise<{ id: stri ))}

)} - } - titel="Folgt mit #016" - text="Hier erscheinen die Public und Private Keys, die sshkv auf diesem Host verwaltet." - /> + {verwendungen.oeffentlich.length === 0 && verwendungen.privat.length === 0 ? ( + } + titel="Noch keine Schlüssel zugewiesen" + text="Zuweisen geht über die Seite eines Schlüssels („+ Ausrollen“)." + /> + ) : ( +
    + {verwendungen.oeffentlich.map((v) => ( +
  • + + + {v.schluessel.name} + + + Public · {v.zielbenutzer}@{host.name} + {v.hostgruppe ? ` · über ${v.hostgruppe.name}` : ""} + {v.optionen ? ` · ${v.optionen}` : ""} + + + +
  • + ))} + {verwendungen.privat.map((v) => ( +
  • + + + {v.schluessel.name} + + + Private · {v.zielbenutzer}@{host.name}:{v.pfad} + + + +
  • + ))} +
+ )} + {darfAusrollen && verwendungen.oeffentlich.length > 0 && host.bootstrapAm && ( +
+ v.zustand !== "AUSGEROLLT").length} + /> +
+ )}
{verwalten && ( diff --git a/src/components/ausrollen/AusrollenPanel.tsx b/src/components/ausrollen/AusrollenPanel.tsx index fc4a82b..96d236e 100644 --- a/src/components/ausrollen/AusrollenPanel.tsx +++ b/src/components/ausrollen/AusrollenPanel.tsx @@ -6,7 +6,7 @@ import { useEffect, useState, useTransition } from "react"; import { Button } from "@/components/ui/Button"; import { DiffBox } from "@/components/ui/DiffBox"; import { laeuftNoch, useVorgang } from "@/components/vorgang/useVorgang"; -import { ausrollenAktion, trockenlaufAktion } from "@/server/actions/verwendungen"; +import { ausrollenAktion, trockenlaufAktion, type AusrollAuswahl } from "@/server/actions/verwendungen"; import type { DiffZeile } from "@/lib/ssh/zeilen-diff"; /** @@ -24,7 +24,7 @@ type PlanZiel = { fehler?: string; }; -export function AusrollenPanel({ schluesselId, offen }: { schluesselId: string; offen: number }) { +export function AusrollenPanel({ auswahl, offen }: { auswahl: AusrollAuswahl; offen: number }) { const router = useRouter(); const [trockenlaufId, setTrockenlaufId] = useState(); const [ausrollenId, setAusrollenId] = useState(); @@ -52,7 +52,7 @@ export function AusrollenPanel({ schluesselId, offen }: { schluesselId: string; starten(async () => { setFehler(undefined); setAusrollenId(undefined); - const antwort = await trockenlaufAktion(schluesselId); + const antwort = await trockenlaufAktion(auswahl); if (antwort.fehler) setFehler(antwort.fehler); else setTrockenlaufId(antwort.vorgangId); }) diff --git a/src/components/ausrollen/VerwendungsZustand.tsx b/src/components/ausrollen/VerwendungsZustand.tsx new file mode 100644 index 0000000..16d6b10 --- /dev/null +++ b/src/components/ausrollen/VerwendungsZustand.tsx @@ -0,0 +1,28 @@ +import type { VerwendungsZustand as Zustand } from "@/generated/prisma/enums"; +import { clsx } from "clsx"; + +/** Zustand einer Verwendung – immer Punkt und Text, nie nur Farbe (#016). */ +const ANZEIGE: Record = { + GEPLANT: { text: "noch nicht ausgerollt", klasse: "text-accent", punkt: "○" }, + AUSGEROLLT: { text: "synchron", klasse: "text-ok", punkt: "●" }, + ABWEICHUNG: { text: "Drift", klasse: "text-danger", punkt: "●" }, + ENTFERNEN: { text: "wird entfernt", klasse: "text-accent", punkt: "○" }, + ENTFERNT: { text: "entfernt", klasse: "text-muted", punkt: "○" }, + FEHLGESCHLAGEN: { text: "fehlgeschlagen", klasse: "text-danger", punkt: "●" }, +}; + +export function VerwendungsZustand({ zustand, className }: { zustand: Zustand; className?: string }) { + const anzeige = ANZEIGE[zustand]; + return ( + + {anzeige.punkt} + {anzeige.text} + + ); +} diff --git a/src/server/actions/verwendungen.ts b/src/server/actions/verwendungen.ts index a88ac82..0a4e944 100644 --- a/src/server/actions/verwendungen.ts +++ b/src/server/actions/verwendungen.ts @@ -70,12 +70,17 @@ export async function publicEntfernenAktion( } } -export async function trockenlaufAktion(schluesselId: string): Promise { +export type AusrollAuswahl = { readonly schluesselId: string } | { readonly hostId: string }; + +export async function trockenlaufAktion(auswahl: AusrollAuswahl): Promise { try { const { benutzer } = await rechtErforderlich("publicKeyAusrollen"); + const id = z.string().min(1).max(64); return { vorgangId: await trockenlaufStarten( - { schluesselId: z.string().min(1).max(64).parse(schluesselId) }, + "schluesselId" in auswahl + ? { schluesselId: id.parse(auswahl.schluesselId) } + : { hostId: id.parse(auswahl.hostId) }, akteurVon(benutzer), ), }; diff --git a/src/server/queries/ziele.ts b/src/server/queries/ziele.ts index a30b48b..06717b8 100644 --- a/src/server/queries/ziele.ts +++ b/src/server/queries/ziele.ts @@ -20,3 +20,36 @@ export async function zielAuswahl() { } export type ZielAuswahl = Awaited>; + +/** Was sshkv auf einem Host verwaltet (#016): Public Keys direkt und ueber Gruppen. */ +export async function verwendungenAufHost(hostId: string) { + const [oeffentlich, privat] = await Promise.all([ + prisma.publicVerwendung.findMany({ + where: { + zustand: { not: "ENTFERNT" }, + OR: [{ hostId }, { hostgruppe: { mitglieder: { some: { hostId } } } }], + }, + select: { + id: true, + zielbenutzer: true, + optionen: true, + zustand: true, + hostgruppe: { select: { name: true } }, + schluessel: { select: { id: true, name: true, typ: true, fingerprint: true } }, + }, + orderBy: [{ zielbenutzer: "asc" }, { angelegtAm: "asc" }], + }), + prisma.privateVerwendung.findMany({ + where: { hostId, zustand: { not: "ENTFERNT" } }, + select: { + id: true, + zielbenutzer: true, + pfad: true, + zustand: true, + schluessel: { select: { id: true, name: true, typ: true, fingerprint: true } }, + }, + orderBy: [{ zielbenutzer: "asc" }, { pfad: "asc" }], + }), + ]); + return { oeffentlich, privat }; +} diff --git a/src/server/services/soll.ts b/src/server/services/soll.ts index 7ca83ba..698f6fa 100644 --- a/src/server/services/soll.ts +++ b/src/server/services/soll.ts @@ -100,11 +100,20 @@ export type Ziel = { readonly hostId: string; readonly zielbenutzer: string }; export async function zieleVonVerwendungen(where: { schluesselId?: string; ids?: string[]; + hostId?: string; }): Promise { const verwendungen = await prisma.publicVerwendung.findMany({ where: { ...(where.schluesselId ? { schluesselId: where.schluesselId } : {}), ...(where.ids ? { id: { in: where.ids } } : {}), + ...(where.hostId + ? { + OR: [ + { hostId: where.hostId }, + { hostgruppe: { mitglieder: { some: { hostId: where.hostId } } } }, + ], + } + : {}), zustand: { not: "ENTFERNT" }, }, select: { @@ -117,9 +126,11 @@ export async function zieleVonVerwendungen(where: { }); const ziele = new Map(); for (const verwendung of verwendungen) { - const hostIds = verwendung.hostId + const alleHostIds = verwendung.hostId ? [verwendung.hostId] : (verwendung.hostgruppe?.mitglieder.filter((m) => !m.host.archiviertAm).map((m) => m.hostId) ?? []); + // Bei der Auswahl nach Host nur diesen Host, auch wenn die Verwendung an einer Gruppe haengt. + const hostIds = where.hostId ? alleHostIds.filter((id) => id === where.hostId) : alleHostIds; for (const hostId of hostIds) { ziele.set(`${hostId}\u0000${verwendung.zielbenutzer}`, { hostId, diff --git a/src/server/services/verwendungen.ts b/src/server/services/verwendungen.ts index 6e5e466..f375a93 100644 --- a/src/server/services/verwendungen.ts +++ b/src/server/services/verwendungen.ts @@ -117,13 +117,14 @@ export async function publicVerwendungEntfernen(id: string, akteur: Akteur): Pro /** Trockenlauf fuer die Ziele eines Schluessels oder fuer ausgewaehlte Verwendungen. */ export async function trockenlaufStarten( - auswahl: { schluesselId?: string; verwendungIds?: string[]; ziele?: Ziel[] }, + auswahl: { schluesselId?: string; hostId?: string; verwendungIds?: string[]; ziele?: Ziel[] }, akteur: Akteur, ): Promise { const ziele = auswahl.ziele ?? (await zieleVonVerwendungen({ ...(auswahl.schluesselId ? { schluesselId: auswahl.schluesselId } : {}), + ...(auswahl.hostId ? { hostId: auswahl.hostId } : {}), ...(auswahl.verwendungIds ? { ids: auswahl.verwendungIds } : {}), })); if (ziele.length === 0) throw new VerwendungsFehler("Es gibt nichts auszurollen."); @@ -131,6 +132,7 @@ export async function trockenlaufStarten( art: "TROCKENLAUF", angefordertVonId: akteur.id, schluesselId: auswahl.schluesselId ?? null, + hostId: auswahl.hostId ?? null, eingabe: { ziele }, }); } @@ -144,6 +146,7 @@ export async function ausrollenStarten(trockenlaufId: string, akteur: Akteur): P art: "AUSROLLEN", angefordertVonId: akteur.id, schluesselId: trockenlauf.schluesselId, + hostId: trockenlauf.hostId, eingabe: { trockenlaufId }, }); } From 8056b8a0c9b8a12c633cc570234c76f7a5fd6f23 Mon Sep 17 00:00:00 2001 From: Florian Wilde Date: Mon, 12 Oct 2026 10:21:54 +0200 Subject: [PATCH 3/3] test(ausrollen): Dry-Run und Ausrollen im Browser (#016) Gebauter Stand mit Worker gegen das Testziel: Host einrichten, Dry-Run zeigt das Diff, auf dem Host aendert sich noch nichts, nach dem Anwenden steht der Key mit seinen Einschraenkungen im Block. Co-Authored-By: Claude Opus 5.5 --- docs/anleitung/bilder/dry-run-desktop.png | Bin 0 -> 147635 bytes tests/e2e/ausrollen.spec.ts | 103 ++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 docs/anleitung/bilder/dry-run-desktop.png create mode 100644 tests/e2e/ausrollen.spec.ts diff --git a/docs/anleitung/bilder/dry-run-desktop.png b/docs/anleitung/bilder/dry-run-desktop.png new file mode 100644 index 0000000000000000000000000000000000000000..534a8d8ce211dcb30e2cd14f02e3ae82ea80866e GIT binary patch literal 147635 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%0Ao9m=dkQ@l|`~ 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

=3Zs$0?f1=Ws0o(>Pm<%Sf}wCvf1!eELv+pi_LU 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 { + aufZielB("userdel -r sshkv 2>/dev/null; rm -f /etc/sudoers.d/sshkv /root/.ssh/authorized_keys*; true"); +}); + +test("Public Key ueber die Hostseite ausrollen", async ({ page }) => { + const name = `zz-e2e-${Date.now().toString(36)}`; + await anmelden(page); + + // Host einrichten wie in host-einrichten.spec.ts. + await page.goto("/hosts/neu"); + await page.getByLabel("Name").fill(name); + await page.getByLabel("Adresse").fill("127.0.0.1"); + await page.getByLabel("Port").fill(ZIEL_PORT); + await page.getByRole("button", { name: "Host anlegen" }).click(); + await page.getByRole("button", { name: "Host-Key abrufen" }).click(); + await page.getByRole("button", { name: "Fingerprint stimmt – vertrauen" }).click({ timeout: 30_000 }); + await page.getByLabel("Passwort", { exact: true }).fill(ROOT_PASSWORT); + await page.getByRole("button", { name: "Zugang einrichten" }).click(); + await expect(page.getByRole("button", { name: "Verbindung prüfen" })).toBeVisible({ timeout: 60_000 }); + const hostId = page.url().split("/").pop()!; + + // Schluessel und Zuweisung direkt anlegen. + const key = publicKey(`sshkv:${name}`); + const client = new pg.Client({ connectionString: process.env["DATABASE_URL"] }); + await client.connect(); + try { + const schluesselId = randomUUID(); + await client.query( + `INSERT INTO "Schluessel" ("id","name","slug","typ","kategorie","publicKey","fingerprint","kommentar","geaendertAm") + VALUES ($1,$2,$3,'ED25519','DIENST',$4,$5,$6,now())`, + [schluesselId, `${name} Backup`, name, key.zeile, key.fingerprint, `sshkv:${name}`], + ); + await client.query( + `INSERT INTO "PublicVerwendung" ("id","schluesselId","hostId","zielbenutzer","optionen") VALUES ($1,$2,$3,'root',$4)`, + [randomUUID(), schluesselId, hostId, 'restrict,from="10.0.0.12"'], + ); + } finally { + await client.end(); + } + + await page.reload(); + await expect(page.getByText("1 Zuweisung(en) noch nicht ausgerollt.")).toBeVisible(); + await page.getByRole("button", { name: "Dry-Run starten" }).click(); + await expect(page.getByText(`root@${name}:/root/.ssh/authorized_keys`)).toBeVisible({ timeout: 30_000 }); + await expect(page.getByText(`restrict,from="10.0.0.12" ${key.zeile}`)).toBeVisible(); + expect(aufZielB("cat /root/.ssh/authorized_keys 2>/dev/null || true")).not.toContain(key.fingerprint); + await page.screenshot({ path: "docs/anleitung/bilder/dry-run-desktop.png", fullPage: true }); + + await page.getByRole("button", { name: /Änderung anwenden/ }).click(); + await expect(page.getByText("Ausgerollt.")).toBeVisible({ timeout: 30_000 }); + await expect(page.getByText("synchron").first()).toBeVisible(); + expect(aufZielB("cat /root/.ssh/authorized_keys")).toContain(`restrict,from="10.0.0.12" ${key.zeile}`); + + await page.getByRole("button", { name: "Host archivieren" }).click(); + await page.getByRole("dialog").getByRole("button", { name: "Archivieren", exact: true }).click(); + await expect(page).toHaveURL(/\/hosts$/); +});