feat(bootstrap): Deployment-Key per Einmal-Passwort oder Befehl (#010)

Ein einziges POSIX-Skript fuer beide Wege: der Worker fuehrt es nach
einer einmaligen Passwortanmeldung aus, und genau dasselbe Skript
steht als Befehl zum Kopieren bereit. So gibt es keine zwei Wahrheiten
darueber, was ein Bootstrap auf dem Host aendert.

Das Passwort reist nur verschluesselt im Job und steht nie in Vorgang,
Protokoll oder Log. Dateien im Home des Zielbenutzers liest und
schreibt das Skript als dieser Benutzer: ein Symlink auf /etc/shadow
fuehrt zum Abbruch statt zur Kopie (Integrationstest). sudoers wird
erst nach visudo -c aktiv. Am Ende beweist eine Anmeldung per
Deployment-Key, dass sshkv ohne Passwort weiterarbeiten kann.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 09:42:45 +02:00
co-authored by Claude Opus 5.5
parent 353b751df2
commit 17879f4c80
10 changed files with 595 additions and 2 deletions
+54
View File
@@ -0,0 +1,54 @@
import { describe, expect, it } from "vitest";
import { bootstrapBefehl, bootstrapSkript, sshdBefundLesen } from "@/lib/ssh/bootstrap-skript";
import { shellQuote } from "@/lib/shell";
/** Das Bootstrap-Skript als Text (#010); ausgefuehrt wird es im Integrationstest. */
const KEY =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl sshkv-deployment@x";
describe("Bootstrap-Skript", () => {
it("quotiert Shell-Werte sicher", () => {
expect(shellQuote("a'b")).toBe(`'a'\\''b'`);
expect(shellQuote("$(id)")).toBe(`'$(id)'`);
expect(() => shellQuote("a\0b")).toThrow();
});
it("weist ungueltige Benutzer und kaputte Keys ab", () => {
expect(() =>
bootstrapSkript({ benutzer: "root;id", dienstbenutzerAnlegen: false, deploymentKey: KEY }),
).toThrow();
expect(() =>
bootstrapSkript({
benutzer: "sshkv",
dienstbenutzerAnlegen: true,
deploymentKey: "ssh-ed25519 kaputt\nid",
}),
).toThrow();
});
it("setzt den Key quotiert ein und prueft sudoers vor dem Aktivieren", () => {
const skript = bootstrapSkript({ benutzer: "sshkv", dienstbenutzerAnlegen: true, deploymentKey: KEY });
expect(skript).toContain(`KEY='${KEY}'`);
expect(skript).toContain("visudo -cf");
expect(skript.indexOf("visudo -cf")).toBeLessThan(skript.indexOf('mv "$NEU_SUDO" /etc/sudoers.d/sshkv'));
expect(skript).toContain("runuser -u");
});
it("ergibt einen Befehl mit Here-Document, das nichts expandiert", () => {
const befehl = bootstrapBefehl({ benutzer: "root", dienstbenutzerAnlegen: false, deploymentKey: KEY });
expect(befehl.startsWith("sudo sh <<'SSHKV_BOOTSTRAP'\n")).toBe(true);
expect(befehl.trimEnd().endsWith("SSHKV_BOOTSTRAP")).toBe(true);
});
it("liest die sshd-Einstellungen aus der Ausgabe", () => {
expect(
sshdBefundLesen(
"SSHKV-SSHD:permitrootlogin without-password\nSSHKV-SSHD:passwordauthentication no\nanderes\nSSHKV-SSHD:authorizedkeysfile .ssh/authorized_keys .ssh/authorized_keys2\n",
),
).toEqual({
permitRootLogin: "without-password",
passwordAuthentication: "no",
authorizedKeysFile: ".ssh/authorized_keys .ssh/authorized_keys2",
});
});
});