Merge branch 'phase/018-benutzer-sudoers'
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
ZielkontoFehler,
|
||||
befehlPruefen,
|
||||
sudoersInhalt,
|
||||
zielkontoEntfernenSkript,
|
||||
zielkontoSkript,
|
||||
} from "@/lib/ssh/zielkonto";
|
||||
|
||||
/** Zielkonten und sudoers (#018), als Text; ausgefuehrt im Integrationstest. */
|
||||
|
||||
describe("sudoers-Inhalt", () => {
|
||||
it("gibt vollen Zugriff nur ausdruecklich", () => {
|
||||
expect(sudoersInhalt("deploy", "VOLL")).toContain("deploy ALL=(ALL:ALL) NOPASSWD: ALL");
|
||||
expect(sudoersInhalt("deploy", "KEINER")).toBeNull();
|
||||
});
|
||||
|
||||
it("listet bestimmte Befehle", () => {
|
||||
expect(
|
||||
sudoersInhalt("deploy", "BEFEHLE", [
|
||||
"/usr/bin/systemctl restart app",
|
||||
" /usr/bin/docker compose up -d ",
|
||||
]),
|
||||
).toBe(
|
||||
"# Verwaltet von SSHKeyVault (sshkv) - nicht manuell bearbeiten\n" +
|
||||
"deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart app, /usr/bin/docker compose up -d\n",
|
||||
);
|
||||
expect(() => sudoersInhalt("deploy", "BEFEHLE", [])).toThrow(ZielkontoFehler);
|
||||
});
|
||||
|
||||
it.each([
|
||||
["relativer Pfad", "systemctl restart app"],
|
||||
["Komma", "/bin/a, ALL"],
|
||||
["Doppelpunkt", "/bin/a:b"],
|
||||
["Gleichheitszeichen", "/bin/a x=y"],
|
||||
["Wildcard", "/bin/rm *"],
|
||||
["Backslash", "/bin/a \\,"],
|
||||
["Zeilenumbruch", "/bin/a\nALL ALL=(ALL) ALL"],
|
||||
["Shell-Zeichen", "/bin/sh -c id;id"],
|
||||
])("weist ab: %s", (_, befehl) => {
|
||||
expect(() => befehlPruefen(befehl)).toThrow(ZielkontoFehler);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Skripte", () => {
|
||||
it("schuetzt Systemkonten", () => {
|
||||
for (const benutzer of ["root", "sshkv", "nobody"]) {
|
||||
expect(() => zielkontoSkript(benutzer, "VOLL")).toThrow(ZielkontoFehler);
|
||||
expect(() => zielkontoEntfernenSkript(benutzer, true)).toThrow(ZielkontoFehler);
|
||||
}
|
||||
expect(() => zielkontoSkript("Root;id", "KEINER")).toThrow(ZielkontoFehler);
|
||||
});
|
||||
|
||||
it("prueft sudoers vor dem Aktivieren und ueberschreibt keine fremde Datei", () => {
|
||||
const skript = zielkontoSkript("deploy", "VOLL");
|
||||
expect(skript.indexOf("visudo -cf")).toBeLessThan(skript.indexOf('mv "$NEU" "$DATEI"'));
|
||||
expect(skript).toContain("SSHKV-SUDOERS-FREMD");
|
||||
expect(zielkontoSkript("deploy", "KEINER")).toContain('rm -f "$DATEI"');
|
||||
});
|
||||
|
||||
it("loescht den Benutzer nur, wenn gewuenscht", () => {
|
||||
expect(zielkontoEntfernenSkript("deploy", true)).toContain("userdel");
|
||||
expect(zielkontoEntfernenSkript("deploy", false)).not.toContain("userdel");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user