Files
ForgeFlow/tests/deploy-key-host.test.mjs
T
NuklearRabbitandClaude Opus 5 beeafdcba7 perf: reuse a deploy-key proof instead of asking the server twice
A key rotation verified the candidate with `git ls-remote`, then immediately ran
preflightCandidate, which threw that result away and ran the same command over a
second SSH connection. Nothing happens between the two calls that could change
the answer, and the proof was already being passed in.

preflightCandidate now uses a proof that established a remote commit and falls
back to verifying when it is handed nothing usable, so it still works as a
standalone gate. Every ssh.exec opens its own connection, so this removes a full
TCP, key exchange and authentication round trip from a rotation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 14:53:53 +02:00

217 lines
11 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import { createRequire } from "node:module";
const require = createRequire(import.meta.url);
const { UnraidDeployKeyHost, parseDeployKeyMarker } = require("../src/main/unraid-deploy-key-host.cjs");
const SERVER = { id: "unraid", basePath: "/mnt/user/appdata" };
const REPOSITORY = { fullName: "Jens/Portfolio" };
// The host reaches the server through a single exec call, so capturing the script
// it sends is the only way to assert what actually happens to the key material.
function keyHost(stdout = "") {
const scripts = [];
const ssh = {
exec: async (serverId, command, options) => {
const encoded = command.match(/printf '%s' '([^']+)'/)?.[1] || "";
scripts.push({ serverId, options, script: Buffer.from(encoded, "base64").toString("utf8") });
return { stdout };
},
};
return { host: new UnraidDeployKeyHost({ ssh }), scripts };
}
test("deploy-key storage is repository-scoped, deterministic and stays under the server base path", () => {
const { host } = keyHost();
const first = host.paths(REPOSITORY, SERVER);
const again = host.paths({ fullName: "jens/portfolio" }, SERVER);
const other = host.paths({ fullName: "Jens/Other" }, SERVER);
assert.deepEqual(first, again, "the same repository always resolves to the same directory");
assert.notEqual(first.directory, other.directory, "a different repository never shares a key directory");
for (const value of Object.values(first)) {
assert.ok(value.startsWith("/mnt/user/appdata/.forgeflow/git-credentials/"), value);
assert.ok(!value.includes(".."));
}
assert.ok(!first.directory.toLowerCase().includes("portfolio"), "the repository name is hashed, not embedded");
});
test("the server pull remote is taken from the first usable SSH URL and refused when there is none", () => {
const { host } = keyHost();
assert.equal(
host.remote({ ...REPOSITORY, localStatus: { remoteUrl: "https://gitea.example/Jens/Portfolio.git" }, sshUrl: "git@gitea.example:Jens/Portfolio.git" }, {}),
"git@gitea.example:Jens/Portfolio.git",
"an HTTPS remote is skipped in favour of the SSH URL",
);
assert.equal(
host.remote({ ...REPOSITORY }, { cloneUrl: "ssh://git@gitea.example:2222/Jens/Portfolio.git" }),
"ssh://git@gitea.example:2222/Jens/Portfolio.git",
);
assert.throws(
() => host.remote({ ...REPOSITORY, sshUrl: "https://gitea.example/Jens/Portfolio.git" }, {}),
(error) => {
assert.equal(error.code, "SERVER_GIT_SSH_URL_REQUIRED");
return true;
},
);
});
test("the Git SSH environment pins the scoped key and refuses an unknown host", () => {
const { host } = keyHost();
const paths = host.paths(REPOSITORY, SERVER);
const environment = host.environment(paths);
assert.match(environment, /IdentitiesOnly=yes/);
assert.match(environment, /BatchMode=yes/);
assert.match(environment, /StrictHostKeyChecking=yes/);
assert.ok(environment.includes(paths.knownHosts), "the pinned host key file is repository-scoped");
assert.ok(environment.includes(paths.privateKey));
});
test("a backup copies the current key material into a fresh recovery slot", async () => {
const publicKey = "ssh-ed25519 QkFL forgeflow";
const { host, scripts } = keyHost(
`__FORGEFLOW_KEY_BACKUP__\nrecovery=/mnt/user/appdata/.forgeflow/git-credentials/abc/recovery/backup-1\npublicKey=${Buffer.from(publicKey).toString("base64")}\n`,
);
const backup = await host.backup({ repository: REPOSITORY, server: SERVER });
assert.equal(backup.publicKey, publicKey);
assert.match(backup.recovery, /recovery\/backup-1$/);
assert.match(scripts[0].script, /umask 077/, "recovered key material is not world readable");
assert.match(scripts[0].script, /deploy-key deploy-key\.pub known_hosts/);
});
test("candidate verification only reports ready on a real remote commit", async () => {
const remoteSha = "d".repeat(40);
const candidate = { paths: { privateKey: "/k/deploy-key", publicKey: "/k/deploy-key.pub", knownHosts: "/k/known_hosts" } };
const context = {
repository: { ...REPOSITORY, sshUrl: "git@gitea.example:Jens/Portfolio.git" },
profile: { branch: "main" },
server: SERVER,
candidate,
};
const proven = keyHost(`__FORGEFLOW_KEY_PROOF__\nremoteSha=${remoteSha}\nfingerprint=SHA256:new\nhostFingerprint=SHA256:host\n`);
const proof = await proven.host.verifyCandidate(context);
assert.deepEqual(proof, { ready: true, remoteSha, fingerprint: "SHA256:new", hostFingerprint: "SHA256:host" });
assert.match(proven.scripts[0].script, /git ls-remote --exit-code/);
assert.match(proven.scripts[0].script, /refs\/heads\/main/);
assert.equal(proven.scripts[0].options.timeout, 45_000);
assert.deepEqual(await proven.host.preflightCandidate(context), proof);
const unproven = keyHost("__FORGEFLOW_KEY_PROOF__\nremoteSha=\nfingerprint=\nhostFingerprint=\n");
assert.equal((await unproven.host.verifyCandidate(context)).ready, false);
await assert.rejects(() => unproven.host.preflightCandidate(context), /did not prove the remote branch/);
});
test("a preflight reuses a proof it was handed instead of asking the server again", async () => {
const reused = keyHost("__FORGEFLOW_KEY_PROOF__\nremoteSha=\nfingerprint=\nhostFingerprint=\n");
const proof = { ready: true, remoteSha: "f".repeat(40), fingerprint: "SHA256:new", hostFingerprint: "SHA256:host" };
const context = {
repository: { ...REPOSITORY, sshUrl: "git@gitea.example:Jens/Portfolio.git" },
profile: { branch: "main" },
server: SERVER,
candidate: { paths: { privateKey: "/k/deploy-key", publicKey: "/k/deploy-key.pub", knownHosts: "/k/known_hosts" } },
proof,
};
assert.deepEqual(await reused.host.preflightCandidate(context), proof);
assert.equal(reused.scripts.length, 0, "no second connection is opened");
// A proof that never established a remote commit is not a shortcut.
await assert.rejects(
() => reused.host.preflightCandidate({ ...context, proof: { ready: false } }),
/did not prove the remote branch/,
);
assert.equal(reused.scripts.length, 1, "an unusable proof falls back to verifying");
});
test("verifying the active key uses the repository-scoped paths rather than a candidate", async () => {
const { host, scripts } = keyHost(`__FORGEFLOW_KEY_PROOF__\nremoteSha=${"e".repeat(40)}\nfingerprint=SHA256:active\nhostFingerprint=SHA256:host\n`);
const paths = host.paths(REPOSITORY, SERVER);
const proof = await host.verifyActive({
repository: { ...REPOSITORY, sshUrl: "git@gitea.example:Jens/Portfolio.git" },
profile: { branch: "main" },
server: SERVER,
});
assert.equal(proof.ready, true);
assert.ok(scripts[0].script.includes(paths.privateKey));
assert.ok(scripts[0].script.includes(paths.knownHosts));
});
test("promotion only replaces key material after proving the candidate is complete", async () => {
const { host, scripts } = keyHost();
const paths = host.paths(REPOSITORY, SERVER);
const candidate = { paths: { directory: "/c", privateKey: "/c/deploy-key", publicKey: "/c/deploy-key.pub", knownHosts: "/c/known_hosts" } };
await host.promote({ repository: REPOSITORY, server: SERVER, candidate });
const script = scripts[0].script;
assert.ok(script.includes("test -s '/c/deploy-key'"), "an empty candidate key is refused before anything is replaced");
assert.ok(script.includes("test -s '/c/known_hosts'"));
assert.ok(script.indexOf("test -s") < script.indexOf("mv "), "the checks run before the swap");
// The staging suffix is appended outside the quoted path, so the command reads
// mv '<path>'.new '<path>' rather than mv '<path>.new' '<path>'.
assert.ok(script.includes(`mv '${paths.privateKey}'.new '${paths.privateKey}'`), "the swap is atomic");
assert.ok(script.includes(`cp -p '/c/deploy-key' '${paths.privateKey}'.new`), "the copy lands on the staging name first");
});
test("rollback restores the recovery slot and removes the candidate", async () => {
const { host, scripts } = keyHost();
const paths = host.paths(REPOSITORY, SERVER);
await host.rollback({
repository: REPOSITORY,
server: SERVER,
candidate: { paths: { directory: "/candidate" } },
previous: { key: { recovery: "/recovery/backup-1" } },
});
assert.ok(scripts[0].script.includes("cp -p '/recovery/backup-1'"));
assert.ok(scripts[0].script.includes(paths.directory));
assert.ok(scripts[0].script.includes("rm -rf -- '/candidate'"));
});
test("committing a rotation discards only the candidate directory", async () => {
const { host, scripts } = keyHost();
await host.commit({ server: SERVER, candidate: { paths: { directory: "/candidate" } } });
// Every script carries the strict-mode preamble that bash() prepends.
assert.equal(scripts[0].script.split("\n").at(-1), "rm -rf -- '/candidate'");
assert.ok(!scripts[0].script.includes(".forgeflow/git-credentials"), "the active key directory is never touched on commit");
});
test("every server script runs under strict mode with Git prompts disabled", async () => {
const { host, scripts } = keyHost();
await host.commit({ server: SERVER, candidate: { paths: { directory: "/candidate" } } });
assert.match(scripts[0].script, /^set -euo pipefail\nexport GIT_TERMINAL_PROMPT=0\n/);
assert.equal(scripts[0].serverId, SERVER.id);
});
test("revocation moves key material aside so it can still be restored", async () => {
const { host, scripts } = keyHost();
const paths = host.paths(REPOSITORY, SERVER);
await host.revoke({ repository: REPOSITORY, server: SERVER });
const script = scripts[0].script;
assert.ok(script.includes(`${paths.recovery}/revoked-`), "revoked material is kept in the recovery area");
assert.match(script, /mv /, "the key is moved, never deleted");
assert.ok(!/rm -rf/.test(script), "revocation must not destroy the recovery path");
});
test("restore reinstates the newest recovery slot and reports the public evidence", async () => {
const publicKey = "ssh-ed25519 UkVT forgeflow";
const { host, scripts } = keyHost(
`__FORGEFLOW_KEY_RESTORE__\npublicKey=${Buffer.from(publicKey).toString("base64")}\nfingerprint=SHA256:restored\nhostFingerprint=SHA256:host\n`,
);
const restored = await host.restore({ repository: REPOSITORY, server: SERVER });
assert.deepEqual(restored, { publicKey, fingerprint: "SHA256:restored", hostFingerprint: "SHA256:host" });
assert.match(scripts[0].script, /sort \| tail -1/, "the newest slot is chosen deterministically");
assert.ok(scripts[0].script.includes('test -n "$slot"'), "restoring without a recovery slot fails loudly");
});
test("marker parsing keeps values that themselves contain separators", () => {
const parsed = parseDeployKeyMarker("noise\n__M__\nkey=a=b=c\nempty\nother=1\n", "__M__");
assert.deepEqual(parsed, { key: "a=b=c", empty: "", other: "1" });
});