import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { deploymentKeySicherstellen } from "@/worker/geheimnisse"; import { hostVerbinden } from "@/worker/ssh/host"; import { befehlAusfuehren } from "@/worker/ssh/verbindung"; import { zielkontoAnwenden, zielkontoEntfernen } from "@/worker/ssh/zielkonto"; import { datenbankLeeren } from "../hilfen/db"; import { eingerichteterHost, masterKeyAnlegen } from "./hilfen/host"; import { befehlAlsRoot, zielB, zielBereit } from "./hilfen/ziele"; /** Zielkonten und sudoers gegen ein echtes sshd (#018). */ let aufraeumen: () => void; beforeAll(async () => { await zielBereit(zielB); aufraeumen = masterKeyAnlegen(); }); afterAll(() => aufraeumen()); beforeEach(async () => { await datenbankLeeren(); await deploymentKeySicherstellen(); await befehlAlsRoot( zielB, "userdel -r sshkv 2>/dev/null; userdel -r deploy 2>/dev/null; rm -f /etc/sudoers.d/sshkv /etc/sudoers.d/sshkv-deploy; true", ); }); async function verbunden() { const host = await eingerichteterHost(zielB, "ziel-b"); return { host, verbindung: await hostVerbinden(host) }; } describe("Zielkonto", () => { it("legt den Benutzer an und erlaubt genau die freigegebenen Befehle", async () => { const { host, verbindung } = await verbunden(); try { const erstes = await zielkontoAnwenden(verbindung, host, { benutzer: "deploy", modus: "BEFEHLE", befehle: ["/usr/bin/id -un"], }); expect(erstes.angelegt).toBe(true); expect( ( await zielkontoAnwenden(verbindung, host, { benutzer: "deploy", modus: "BEFEHLE", befehle: ["/usr/bin/id -un"], }) ).angelegt, ).toBe(false); const erlaubt = await befehlAusfuehren( verbindung, "sudo -n runuser -u deploy -- sudo -n /usr/bin/id -un", ); expect(erlaubt.stdout.trim()).toBe("root"); const verboten = await befehlAusfuehren( verbindung, "sudo -n runuser -u deploy -- sudo -n /usr/bin/whoami", ); expect(verboten.code).not.toBe(0); const rechte = await befehlAlsRoot( zielB, "stat -c '%a %U' /etc/sudoers.d/sshkv-deploy /home/deploy/.ssh", ); expect(rechte.stdout).toBe("440 root\n700 deploy\n"); } finally { verbindung.end(); } }); it("wechselt auf vollen Zugriff und wieder auf keinen", async () => { const { host, verbindung } = await verbunden(); try { await zielkontoAnwenden(verbindung, host, { benutzer: "deploy", modus: "VOLL" }); expect( (await befehlAusfuehren(verbindung, "sudo -n runuser -u deploy -- sudo -n whoami")).stdout.trim(), ).toBe("root"); await zielkontoAnwenden(verbindung, host, { benutzer: "deploy", modus: "KEINER" }); expect( ( await befehlAlsRoot(zielB, "test -e /etc/sudoers.d/sshkv-deploy && echo da || echo weg") ).stdout.trim(), ).toBe("weg"); } finally { verbindung.end(); } }); it("ueberschreibt keine fremde sudoers-Datei", async () => { const { host, verbindung } = await verbunden(); await befehlAlsRoot( zielB, "echo 'deploy ALL=(ALL) ALL' > /etc/sudoers.d/sshkv-deploy && chmod 440 /etc/sudoers.d/sshkv-deploy", ); try { await expect( zielkontoAnwenden(verbindung, host, { benutzer: "deploy", modus: "VOLL" }), ).rejects.toThrow(/nicht von sshkv/); expect((await befehlAlsRoot(zielB, "cat /etc/sudoers.d/sshkv-deploy")).stdout).toBe( "deploy ALL=(ALL) ALL\n", ); } finally { verbindung.end(); } }); it("entfernt sudoers und Benutzer beim Widerruf", async () => { const { host, verbindung } = await verbunden(); try { await zielkontoAnwenden(verbindung, host, { benutzer: "deploy", modus: "VOLL" }); await zielkontoEntfernen(verbindung, host, "deploy", true); const rest = await befehlAlsRoot(zielB, "id deploy 2>&1; ls /etc/sudoers.d/"); expect(rest.stdout).toMatch(/no such user/); expect(rest.stdout).not.toContain("sshkv-deploy"); } finally { verbindung.end(); } }); }); describe("Zielkonto als Vorgang", () => { it("legt an, merkt sich die Herkunft und entfernt den Benutzer wieder", async () => { const { prisma } = await import("@/lib/db"); const { vorgangLaufen } = await import("./hilfen/host"); const host = await eingerichteterHost(zielB, "ziel-b"); const anlegen = await vorgangLaufen("ZIELKONTO_ANLEGEN", { hostId: host.id, eingabe: { benutzer: "deploy", modus: "VOLL", befehle: [] }, }); expect(anlegen.fehler).toBeNull(); const konto = await prisma.zielkonto.findFirstOrThrow({ where: { hostId: host.id } }); expect(konto).toMatchObject({ benutzername: "deploy", vonSshkvAngelegt: true, sudoModus: "VOLL" }); expect(konto.sudoersInhalt).toContain("deploy ALL=(ALL:ALL) NOPASSWD: ALL"); const entfernen = await vorgangLaufen("ZIELKONTO_ENTFERNEN", { hostId: host.id, eingabe: { benutzer: "deploy" }, }); expect(entfernen.fehler).toBeNull(); expect((await befehlAlsRoot(zielB, "id deploy 2>&1")).stdout).toMatch(/no such user/); expect( (await prisma.zielkonto.findFirstOrThrow({ where: { hostId: host.id } })).entferntAm, ).not.toBeNull(); }); });