test(ausrollen): Dry-Run und Ausrollen im Browser (#016)
Gebauter Stand mit Worker gegen das Testziel: Host einrichten, Dry-Run zeigt das Diff, auf dem Host aendert sich noch nichts, nach dem Anwenden steht der Key mit seinen Einschraenkungen im Block. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 144 KiB |
@@ -0,0 +1,103 @@
|
|||||||
|
import { execFileSync } from "node:child_process";
|
||||||
|
import { generateKeyPairSync, randomUUID } from "node:crypto";
|
||||||
|
import { expect, test, type Page } from "@playwright/test";
|
||||||
|
import pg from "pg";
|
||||||
|
import "dotenv/config";
|
||||||
|
import { fingerprintSha256 } from "../../src/lib/ssh/openssh";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public Key ausrollen ueber die Hostseite (#016), gebauter Stand + Worker, Testziel B.
|
||||||
|
* Schluessel und Zuweisung legt der Test direkt in der Entwicklungsdatenbank an (Praefix zz-),
|
||||||
|
* damit er nur den Ausroll-Weg prueft.
|
||||||
|
*/
|
||||||
|
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(150_000);
|
||||||
|
|
||||||
|
function aufZielB(befehl: string): string {
|
||||||
|
return execFileSync(
|
||||||
|
"docker",
|
||||||
|
["compose", "-f", "docker-compose.test.yml", "exec", "-T", "ziel-b", "sh", "-c", befehl],
|
||||||
|
{
|
||||||
|
encoding: "utf8",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function publicKey(kommentar: string) {
|
||||||
|
const { publicKey: schluessel } = generateKeyPairSync("ed25519");
|
||||||
|
const roh = schluessel.export({ format: "der", type: "spki" }).subarray(-32);
|
||||||
|
const typ = Buffer.from("ssh-ed25519");
|
||||||
|
const blob = Buffer.concat([Buffer.from([0, 0, 0, typ.length]), typ, Buffer.from([0, 0, 0, 32]), roh]);
|
||||||
|
return {
|
||||||
|
zeile: `ssh-ed25519 ${blob.toString("base64")} ${kommentar}`,
|
||||||
|
fingerprint: fingerprintSha256(blob),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function anmelden(page: Page) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
test.beforeAll(() => {
|
||||||
|
aufZielB("userdel -r sshkv 2>/dev/null; rm -f /etc/sudoers.d/sshkv /root/.ssh/authorized_keys*; true");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Public Key ueber die Hostseite ausrollen", async ({ page }) => {
|
||||||
|
const name = `zz-e2e-${Date.now().toString(36)}`;
|
||||||
|
await anmelden(page);
|
||||||
|
|
||||||
|
// Host einrichten wie in host-einrichten.spec.ts.
|
||||||
|
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 page.getByRole("button", { name: "Host-Key abrufen" }).click();
|
||||||
|
await page.getByRole("button", { name: "Fingerprint stimmt – vertrauen" }).click({ timeout: 30_000 });
|
||||||
|
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 });
|
||||||
|
const hostId = page.url().split("/").pop()!;
|
||||||
|
|
||||||
|
// Schluessel und Zuweisung direkt anlegen.
|
||||||
|
const key = publicKey(`sshkv:${name}`);
|
||||||
|
const client = new pg.Client({ connectionString: process.env["DATABASE_URL"] });
|
||||||
|
await client.connect();
|
||||||
|
try {
|
||||||
|
const schluesselId = randomUUID();
|
||||||
|
await client.query(
|
||||||
|
`INSERT INTO "Schluessel" ("id","name","slug","typ","kategorie","publicKey","fingerprint","kommentar","geaendertAm")
|
||||||
|
VALUES ($1,$2,$3,'ED25519','DIENST',$4,$5,$6,now())`,
|
||||||
|
[schluesselId, `${name} Backup`, name, key.zeile, key.fingerprint, `sshkv:${name}`],
|
||||||
|
);
|
||||||
|
await client.query(
|
||||||
|
`INSERT INTO "PublicVerwendung" ("id","schluesselId","hostId","zielbenutzer","optionen") VALUES ($1,$2,$3,'root',$4)`,
|
||||||
|
[randomUUID(), schluesselId, hostId, 'restrict,from="10.0.0.12"'],
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
await client.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
await expect(page.getByText("1 Zuweisung(en) noch nicht ausgerollt.")).toBeVisible();
|
||||||
|
await page.getByRole("button", { name: "Dry-Run starten" }).click();
|
||||||
|
await expect(page.getByText(`root@${name}:/root/.ssh/authorized_keys`)).toBeVisible({ timeout: 30_000 });
|
||||||
|
await expect(page.getByText(`restrict,from="10.0.0.12" ${key.zeile}`)).toBeVisible();
|
||||||
|
expect(aufZielB("cat /root/.ssh/authorized_keys 2>/dev/null || true")).not.toContain(key.fingerprint);
|
||||||
|
await page.screenshot({ path: "docs/anleitung/bilder/dry-run-desktop.png", fullPage: true });
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: /Änderung anwenden/ }).click();
|
||||||
|
await expect(page.getByText("Ausgerollt.")).toBeVisible({ timeout: 30_000 });
|
||||||
|
await expect(page.getByText("synchron").first()).toBeVisible();
|
||||||
|
expect(aufZielB("cat /root/.ssh/authorized_keys")).toContain(`restrict,from="10.0.0.12" ${key.zeile}`);
|
||||||
|
|
||||||
|
await page.getByRole("button", { name: "Host archivieren" }).click();
|
||||||
|
await page.getByRole("dialog").getByRole("button", { name: "Archivieren", exact: true }).click();
|
||||||
|
await expect(page).toHaveURL(/\/hosts$/);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user