Files
sshkeyvault/tests/e2e/host-einrichten.spec.ts
T
florian.wildeandClaude Opus 5.5 3b28f73c95 docs(betrieb): Handbuch, Bedrohungsanalyse, geprobte Sicherung (#029)
Handbuch mit Bildern fuer alle Ablaeufe, darunter der Kern-
Anwendungsfall und der Deployment-Zugang fuer Claude Code.
Betriebsdoku zu Installation, authentik und Reverse Proxy sowie eine
Bedrohungsanalyse mit Notfallplan fuer den Fall, dass sshkv selbst
kompromittiert wird.

Sicherung und Wiederherstellung als Skripte, auf einem Wegwerf-Stapel
geprobt. Dabei fielen zwei Fehler auf: das Schema der Warteschlange
liess sich nicht zurueckspielen, und ein Abbruch ohne Transaktion
loeschte es halb. Jetzt nur Schema public, in einer Transaktion, mit
Pruefwert des Master-Keys. Der Worker startet nicht, wenn der
Master-Key nicht zur Datenbank passt.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 16:46:38 +02:00

74 lines
3.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { execFileSync } from "node:child_process";
import { expect, test } from "@playwright/test";
import "dotenv/config";
/**
* Einen neuen Host in Betrieb nehmen (#008–#010), im gebauten Stand mit laufendem Worker
* gegen das Testziel B aus docker-compose.test.yml:
* Host anlegen → Host-Key abrufen → vertrauen → Bootstrap per Passwort → Verbindung pruefen.
* Laeuft gegen die Entwicklungsdatenbank; der Host heisst zz-e2e-… und wird am Ende archiviert.
*/
const ZIEL_PORT = (process.env["SSHKV_TEST_ZIEL_B"] ?? "127.0.0.1:2222").split(":")[1] ?? "2222";
const ROOT_PASSWORT = process.env["SSHKV_TEST_ROOT_PASSWORT"] || "sshkv-test";
test.setTimeout(120_000);
test.beforeAll(() => {
// Testziel ohne Dienstbenutzer, damit der Bootstrap wirklich etwas zu tun hat.
execFileSync("docker", [
"compose",
"-f",
"docker-compose.test.yml",
"exec",
"-T",
"ziel-b",
"sh",
"-c",
"userdel -r sshkv 2>/dev/null; rm -f /etc/sudoers.d/sshkv; true",
]);
});
test("neuen Host einrichten", async ({ page }) => {
const name = `zz-e2e-${Date.now().toString(36)}`;
await page.goto("/anmelden");
await page.getByRole("link", { name: /Mit Mock-OIDC anmelden/ }).click();
await page.locator('button[data-rolle="admin"]').click();
await page.getByRole("button", { name: "Anmelden", exact: true }).click();
await expect(page.getByRole("heading", { name: "Übersicht" })).toBeVisible();
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 expect(page.getByRole("heading", { name })).toBeVisible();
await page.getByRole("button", { name: "Host-Key abrufen" }).click();
await expect(page.getByRole("button", { name: "Fingerprint stimmt – vertrauen" })).toBeVisible({
timeout: 30_000,
});
await page.screenshot({ path: "docs/anleitung/bilder/hostkey-bestaetigen-desktop.png", fullPage: true });
await page.getByRole("button", { name: "Fingerprint stimmt – vertrauen" }).click();
await expect(page.getByText("Einmalig per Passwort")).toBeVisible({ timeout: 15_000 });
await expect(page.getByLabel("Bootstrap-Befehl", { exact: true })).toContainText("SSHKV_BOOTSTRAP");
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 });
await expect(page.getByText("noch nicht eingerichtet")).toHaveCount(0);
await expect(page.getByText(/Empfohlen: PermitRootLogin prohibit-password/)).toBeVisible();
const pruefen = page.getByRole("button", { name: "Verbindung prüfen" });
await pruefen.click();
// Gezielt im Block dieses Knopfs – der Host-Key-Knopf zeigt sein eigenes „Erledigt.“.
await expect(pruefen.locator("..").getByText("Erledigt.")).toBeVisible({ timeout: 30_000 });
await page.screenshot({ path: "docs/anleitung/bilder/host-eingerichtet-desktop.png", fullPage: true });
// Aufraeumen: archivieren, damit die Liste nicht waechst.
await page.getByRole("button", { name: "Host archivieren" }).click();
await page.getByRole("dialog").getByRole("button", { name: "Archivieren", exact: true }).click();
await expect(page).toHaveURL(/\/hosts$/);
});