Files
sshkeyvault/tests/e2e/deploymentkey.spec.ts
T
florian.wildeandClaude Opus 5.5 cab6f45510 feat(deploymentkey): geführte Rotation des Deployment-Keys (#031)
Bisher ließ sich der Instanz-Key nur von Hand auf jedem Host tauschen.
Die Rotation läuft jetzt in fünf Schritten: Nachfolger erzeugen, per
Dry-Run neben den alten verteilen, Anmeldung je Host prüfen, umstellen,
alten Key aus allen Blöcken entfernen und seinen Private Key verwerfen.

Der Nachfolger steht während der Rotation in Block und Pflichtliste,
damit kein Host ohne funktionierenden Key dasteht. Umgestellt wird nur
nach erfolgreicher Prüfung oder bewusstem Übergehen eines Hosts.

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

79 lines
3.9 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 { 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<string, unknown>;
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("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);
});