Eine begonnene Rotation ließ sich nicht zurücknehmen; nahm ein Host den neuen Key dauerhaft nicht an, blieb nur das Übergehen. Abbrechen verwirft jetzt den neuen Key samt Private Key, ein Dry-Run nimmt ihn wieder von den Hosts, und eine neue Rotation kann sofort starten. Nebenbei: Aktionen der Benutzerverwaltung (#035) bekommen im Audit-Log eine eigene Gruppe, statt den Filter „Anmeldung“ zu verwässern; der E2E-Test für lokale Konten hängt nicht mehr von der Reihenfolge ab. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
112 lines
5.6 KiB
TypeScript
112 lines
5.6 KiB
TypeScript
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("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);
|
||
});
|