Files
florian.wildeandClaude Opus 5.5 e89112a761 test: SSH-Testziele und Mock-OIDC fuer Integrationstests (#011)
Bootstrap, Host-Key-Pruefung und spaeteres Ausrollen brauchen echte
sshd-Gegenstellen. Zwei Debian-13-Container (ziel-a, ziel-b) in einem
gemeinsamen Netz bilden frische Hosts vor dem Bootstrap nach: root- und
Passwort-Login sind absichtlich offen, Host-Keys entstehen erst beim
Start, und `hostkeys-tauschen` erlaubt den Test "geaenderter Host-Key
wird abgelehnt" ohne Neuerstellen des Containers.

Die Testhilfen lesen dieselben Adress-Variablen wie die Compose-Datei
und lassen Tests mit Hinweis auf den Startbefehl fehlschlagen, statt
sie still zu ueberspringen - ein uebersprungener Integrationstest sieht
sonst wie ein bestandener aus.

Der Mock-OIDC-Anbieter ersetzt authentik in der Entwicklung (PLAN.md,
Entscheidung 7). Bei interaktivem Login uebernimmt er nur Claims aus dem
Formular, daher belegt eine eigene Login-Seite email, name und groups
vor und laesst die Gruppen frei waehlen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 09:13:56 +02:00

64 lines
2.2 KiB
TypeScript

import { beforeAll, describe, expect, it } from "vitest";
import {
befehlAlsRoot,
hostKeyFingerprint,
hostKeysTauschen,
testPasswort,
zielA,
zielB,
zielBereit,
} from "./hilfen/ziele";
// Rauchtest fuer die Testumgebung selbst (#011): Spaetere Integrationstests (Bootstrap,
// Host-Key-Pruefung, Ausrollen) verlassen sich darauf, dass diese Annahmen gelten.
describe("SSH-Testziele", () => {
beforeAll(async () => {
await Promise.all([zielBereit(zielA), zielBereit(zielB)]);
});
it.each([zielA, zielB])("$dienst: Passwort-Login als root, whoami = root", async (ziel) => {
const ergebnis = await befehlAlsRoot(ziel, "whoami");
expect(ergebnis).toEqual({ stdout: "root\n", stderr: "", code: 0 });
});
it("hat sudo installiert", async () => {
const ergebnis = await befehlAlsRoot(zielA, "sudo -V");
expect(ergebnis.code).toBe(0);
expect(ergebnis.stdout).toMatch(/^Sudo version/m);
});
it("erlaubt den Benutzer test per Passwort, mit sudo-Gruppe", async () => {
const ergebnis = await befehlAlsRoot(zielB, "id -un; id -Gn", {
benutzer: "test",
passwort: testPasswort(),
});
expect(ergebnis.code).toBe(0);
const [name, gruppen] = ergebnis.stdout.trim().split("\n");
expect(name).toBe("test");
expect(gruppen?.split(" ")).toContain("sudo");
});
it("lehnt ein falsches Passwort ab", async () => {
await expect(befehlAlsRoot(zielA, "true", { passwort: "falsch" })).rejects.toThrow(/authentication/i);
});
it("Ziel A erreicht Ziel B über das Docker-Netz", async () => {
const ergebnis = await befehlAlsRoot(zielA, "ssh-keyscan -T 5 -t ed25519 ziel-b");
expect(ergebnis.code).toBe(0);
expect(ergebnis.stdout).toMatch(/^ziel-b ssh-ed25519 AAAA\S+$/m);
});
it("Host-Key-Tausch ändert den Fingerprint", async () => {
const vorher = await hostKeyFingerprint(zielB);
expect(vorher).toMatch(/^SHA256:[A-Za-z0-9+/]{43}$/);
await hostKeysTauschen(zielB);
const nachher = await hostKeyFingerprint(zielB);
expect(nachher).toMatch(/^SHA256:[A-Za-z0-9+/]{43}$/);
expect(nachher).not.toBe(vorher);
// Der Container bleibt nach dem Tausch benutzbar.
expect((await befehlAlsRoot(zielB, "whoami")).stdout).toBe("root\n");
});
});