import { expect, test, type Page } from "@playwright/test"; import { anmelden, archivieren, aufZiel, hostEinrichten, PORT, zieleZuruecksetzen } from "./hilfen"; /** * Rotation des Deployment-Keys im Browser (#031), gebauter Stand + Worker: zwei frisch * eingerichtete Hosts, fuenf Schritte, danach meldet sich sshkv nur noch mit dem neuen Key an. */ test.setTimeout(300_000); test.beforeAll(() => zieleZuruecksetzen()); async function bestaetigen(page: Page) { await page.goto("/bestaetigen?ziel=%2Fdeployment-key"); await page.getByRole("link", { name: /erneut anmelden/ }).click(); await page.locator('button[data-rolle="admin"]').click(); const claims = JSON.parse(await page.locator("#claims").inputValue()) as Record; await page.locator("#claims").fill(JSON.stringify({ ...claims, auth_time: Math.floor(Date.now() / 1000) })); await page.getByRole("button", { name: "Anmelden", exact: true }).click(); await expect(page.getByRole("heading", { name: "Deployment-Key rotieren" })).toBeVisible(); } test("Rotation des Deployment-Keys abbrechen und aufräumen (#036)", async ({ page }) => { const kennung = Date.now().toString(36); const hostA = `zz-e2e-a-${kennung}`; await anmelden(page); await hostEinrichten(page, hostA, PORT.a); await page.goto("/deployment-key"); await bestaetigen(page); // Eine liegengebliebene Rotation (etwa aus einem abgebrochenen Testlauf) wird mit abgebrochen. const erzeugen = page.getByRole("button", { name: "Neuen Deployment-Key erzeugen" }); if (await erzeugen.isVisible()) { await erzeugen.click(); await expect(page.getByText("Neuer Key (noch nicht Standard)")).toBeVisible({ timeout: 30_000 }); } await page.getByRole("button", { name: "Dry-Run starten" }).click(); await page.getByRole("button", { name: /Änderung anwenden/ }).click({ timeout: 30_000 }); await expect(page.getByText("Ausgerollt.", { exact: true })).toBeVisible({ timeout: 60_000 }); expect(aufZiel("ziel-a", "grep -c sshkv-deployment /home/sshkv/.ssh/authorized_keys").trim()).toBe("2"); await page.getByRole("button", { name: "Rotation abbrechen" }).click(); await expect(page.getByRole("heading", { name: "Abgebrochene Rotation aufräumen" })).toBeVisible({ timeout: 30_000, }); await page.getByRole("button", { name: "Dry-Run starten" }).click(); await page.getByRole("button", { name: /Änderung anwenden/ }).click({ timeout: 30_000 }); await expect(page.getByText("Ausgerollt.", { exact: true })).toBeVisible({ timeout: 60_000 }); expect(aufZiel("ziel-a", "grep -c sshkv-deployment /home/sshkv/.ssh/authorized_keys").trim()).toBe("1"); // Ein neuer Anlauf ist sofort moeglich. await expect(page.getByRole("button", { name: "Neuen Deployment-Key erzeugen" })).toBeVisible(); await archivieren(page, hostA); }); test("Deployment-Key rotieren", async ({ page }) => { const kennung = Date.now().toString(36); const hostA = `zz-e2e-a-${kennung}`; const hostB = `zz-e2e-b-${kennung}`; await anmelden(page); await hostEinrichten(page, hostA, PORT.a); await hostEinrichten(page, hostB, PORT.b); const vorher = aufZiel("ziel-a", "cat /home/sshkv/.ssh/authorized_keys"); await page.goto("/einstellungen"); await page.getByRole("link", { name: "Deployment-Key rotieren" }).click(); await bestaetigen(page); await page.getByRole("button", { name: "Neuen Deployment-Key erzeugen" }).click(); await expect(page.getByText("Neuer Key (noch nicht Standard)")).toBeVisible({ timeout: 30_000 }); // Schritt 2: verteilen – der neue Key kommt neben den alten. await page.getByRole("button", { name: "Dry-Run starten" }).click(); await expect(page.getByRole("button", { name: /Änderung anwenden/ })).toBeVisible({ timeout: 30_000 }); await page.screenshot({ path: "docs/anleitung/bilder/deployment-key-desktop.png", fullPage: true }); await page.getByRole("button", { name: /Änderung anwenden/ }).click(); await expect(page.getByText("Ausgerollt.", { exact: true })).toBeVisible({ timeout: 60_000 }); expect(aufZiel("ziel-a", "grep -c sshkv-deployment /home/sshkv/.ssh/authorized_keys").trim()).toBe("2"); // Schritt 3 und 4: pruefen, umstellen. await page.getByRole("button", { name: "Neuen Key prüfen" }).click(); const ergebnis = page.getByRole("list", { name: "Ergebnis der Prüfung" }); await expect(ergebnis.getByText("angemeldet")).toHaveCount(2, { timeout: 60_000 }); await page.getByRole("button", { name: "Auf neuen Key umstellen" }).click(); // Schritt 5: alten Key entfernen und abschliessen. await expect(page.getByRole("heading", { name: "5 · Alten Key entfernen" })).toBeVisible({ timeout: 30_000, }); await page.getByRole("button", { name: "Dry-Run starten" }).click(); await page.getByRole("button", { name: /Änderung anwenden/ }).click({ timeout: 30_000 }); await expect(page.getByText("Ausgerollt.", { exact: true })).toBeVisible({ timeout: 60_000 }); await page.getByRole("button", { name: "Rotation abschließen" }).click(); await expect(page.getByRole("heading", { name: "1 · Neuen Key erzeugen" })).toBeVisible({ timeout: 60_000, }); const nachher = aufZiel("ziel-a", "cat /home/sshkv/.ssh/authorized_keys"); expect(nachher.match(/sshkv-deployment/g)).toHaveLength(1); expect(nachher).not.toBe(vorher); // Der Alltag geht weiter: Verbindung pruefen klappt mit dem neuen Key. await page.goto("/hosts"); await page.getByRole("link", { name: hostA }).first().click(); const pruefen = page.getByRole("button", { name: "Verbindung prüfen" }); await pruefen.click(); await expect(pruefen.locator("..").getByText("Erledigt.")).toBeVisible({ timeout: 30_000 }); await archivieren(page, hostA); await archivieren(page, hostB); });