Files
florian.wildeandClaude Opus 5.5 edc38d0c27 feat(ausrollen): Private Key mit ssh-config und known_hosts (#017)
Der Kern-Anwendungsfall im Worker: Private Key auf Host A, Public Key
auf Host B, dazu ein Block in ~/.ssh/config von A und der vertraute
Host-Key von B in known_hosts - danach meldet sich A per Alias bei B
an, ohne Rueckfrage zum Host-Key (Integrationstest gegen zwei echte
sshd).

Gleiches Muster wie bei Public Keys: Plan je Datei mit Hashes, dann
genau dieser Plan. Der Inhalt eines Private Keys steht nie im Plan
oder Protokoll, eine fremde Datei am Zielpfad wird nie ueberschrieben,
und von einem alten Private Key bleibt keine Sicherungskopie liegen.
Der config-Block steht am Anfang, weil ssh den ersten Treffer nimmt.

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

69 lines
2.5 KiB
TypeScript
Raw Permalink 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 { describe, expect, it } from "vitest";
import { knownHostsZeile, SshConfigFehler, sshConfigZeilen } from "@/lib/ssh/ssh-config";
import { TextblockFehler, textblockSetzen } from "@/lib/ssh/textblock";
/** Verwaltete Bloecke in ~/.ssh/config und known_hosts (#017). */
describe("Textblock", () => {
it("setzt den Block an den Anfang und laesst den Rest stehen", () => {
const vorher = "Host *\n ServerAliveInterval 30\n";
const ergebnis = textblockSetzen(vorher, ["Host a", " HostName b"], "anfang");
expect(ergebnis.inhalt).toBe(
"# BEGIN SSHKV MANAGED – nicht manuell bearbeiten\nHost a\n HostName b\n# END SSHKV MANAGED\n" +
vorher,
);
expect(textblockSetzen(ergebnis.inhalt, ["Host a", " HostName b"], "anfang").geaendert).toBe(false);
});
it("entfernt den Block bei leerem Soll", () => {
const mit = textblockSetzen("x\n", ["a"], "ende").inhalt;
expect(textblockSetzen(mit, [], "ende").inhalt).toBe("x\n");
expect(textblockSetzen("x\n", [], "ende").geaendert).toBe(false);
});
it("verweigert beschaedigte Bloecke und Zeilenumbrueche", () => {
expect(() => textblockSetzen("# BEGIN SSHKV MANAGED\n", ["a"])).toThrow(TextblockFehler);
expect(() => textblockSetzen("", ["a\nb"])).toThrow(TextblockFehler);
});
});
describe("ssh-config und known_hosts", () => {
it("schreibt einen Host-Eintrag mit IdentitiesOnly", () => {
expect(
sshConfigZeilen([
{
alias: "deploy-app01",
hostName: "10.0.0.5",
benutzer: "deploy",
port: 2222,
identityFile: "~/.ssh/id_ed25519_x",
},
]),
).toEqual([
"Host deploy-app01",
" HostName 10.0.0.5",
" User deploy",
" Port 2222",
" IdentityFile ~/.ssh/id_ed25519_x",
" IdentitiesOnly yes",
]);
});
it.each([
{ alias: "a b" },
{ alias: "a\nHost *" },
{ hostName: "x;id" },
{ benutzer: "Root" },
{ port: 0 },
{ identityFile: "~/../etc/passwd" },
])("weist ab: %o", (fehler) => {
const basis = { alias: "a", hostName: "h", benutzer: "u", port: 22, identityFile: "~/.ssh/k" };
expect(() => sshConfigZeilen([{ ...basis, ...fehler }])).toThrow(SshConfigFehler);
});
it("schreibt known_hosts mit Port in Klammern", () => {
expect(knownHostsZeile("ziel-b", 22, "ssh-ed25519", "AAAA")).toBe("ziel-b ssh-ed25519 AAAA");
expect(knownHostsZeile("10.0.0.5", 2222, "ssh-ed25519", "AAAA")).toBe("[10.0.0.5]:2222 ssh-ed25519 AAAA");
});
});