~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/atlibs-iroh

iroh transport for atlibs hosts: com.fedfork.exec.iroh.connect in Rust (atlibs-host) and JavaScript

0
0
main
122 lines · 7.7 KB Raw History
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { existsSync, mkdtempSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "node:test";

import { OUTPUT_MAX, exchange, irohCall, irohHost, irohSession, sshArgs } from "./index.js";

// fake-bin/dumbpipe and fake-bin/ssh stand in for the real binaries (no network).
const FAKE = new URL("./fake-bin/", import.meta.url).pathname;
const T = "endpointabex5k47heqqkdykv2b27ydg75jjaxeucm2ej74nt34a7up6wqlkyaiaenuhi5dqom5c6l3von3tcljrfzzgk3dbpexg4mbonfzg62bonruw42zof4";
const hostKeys = [{ type: "ssh-ed25519", publicKey: "AAAAC3Bare" }];
const spawned = [];
const session = irohSession({ key: "/nonexistent/key", spawn: (cmd, args, options) => (spawned.push(args), spawn(join(FAKE, cmd), args, options)) });
const all = async (stream) => {
	const got = [];
	for await (const m of stream) got.push(m);
	return got;
};

test("a raw alpn session streams stdout, takes sends on stdin, and ends with the exit code", async () => {
	const s = await session({ ticket: `iroh://${T}`, alpn: "echo/1" });
	const it = s[Symbol.asyncIterator]();
	assert.deepEqual((await it.next()).value, { stdout: "ready\n" });
	await s.send("hello\n");
	const rest = [];
	for (let n = await it.next(); !n.done; n = await it.next()) rest.push(n.value);
	assert.deepEqual(rest, [{ stdout: "got hello\n" }, { exitCode: 0 }]);
	await assert.rejects(s.send("late"), /session is closed/);
});

test("ssh sessions: output as it arrives, then the exit code; ssh's own failure fails the stream", async () => {
	assert.deepEqual(await all(await session({ ticket: T, hostKeys, command: "uname -a" })), [{ stdout: "line 1\n" }, { stdout: "line 2\n" }, { stderr: "oops\n" }, { exitCode: 3 }]);
	await assert.rejects(all(await session({ ticket: T, hostKeys, command: "fail" })), /ssh failed: Host key verification failed\./);
	await assert.rejects(all(await session({ ticket: T, alpn: "fail/1" })), /dumbpipe exited 1: no route to endpoint/);
	const files = spawned.flat().filter((a) => a.startsWith("UserKnownHostsFile=")).map((a) => a.slice("UserKnownHostsFile=".length));
	assert.ok(files.length >= 2 && files.every((f) => !existsSync(f)), "each session's known_hosts dir is removed when it ends");
});

test("a shell session (no command) runs until closed; bad input is refused before anything runs", async () => {
	const s = await session({ ticket: T, hostKeys });
	const it = s[Symbol.asyncIterator]();
	assert.deepEqual((await it.next()).value, { stdout: "$ " });
	await s.send("hello\n");
	assert.deepEqual((await it.next()).value, { stdout: "hello\n$ " });
	s.close();
	assert.equal((await it.next()).done, true, "killed: no exitCode, a clean end");
	const stop = new AbortController();
	const aborted = await session({ ticket: T, hostKeys }, { signal: stop.signal });
	stop.abort();
	assert.deepEqual(await all(aborted), []);
	await assert.rejects(session({ ticket: T }), /no host keys/);
	await assert.rejects(session({ ticket: T, hostKeys, command: "" }), /command must be a non-empty string/);
	await assert.rejects(session({ ticket: "not a ticket!" }), /not an iroh ticket/);
	await assert.rejects(session({ ticket: T, alpn: "bad alpn" }), /alpn must be/);
});

test("dumbpipe's 'using secret key' line never reaches the module; stderr is capped; a spawn failure has no exitCode", async () => {
	const messages = await all(await session({ ticket: T, alpn: "secret/1" }));
	assert.deepEqual(messages, [{ stdout: "ok\n" }, { exitCode: 0 }]);
	process.env.PATH = `${FAKE}:${process.env.PATH}`; // the one-shot call spawns dumbpipe from PATH
	const oneShot = irohHost({ key: "/nonexistent/key" });
	assert.deepEqual(await oneShot({ ticket: T, alpn: "secret/1" }), { output: "ok\n" });
	for (const run of [async () => all(await session({ ticket: T, alpn: "noisy/1" })), () => oneShot({ ticket: T, alpn: "noisy/1" })]) {
		const error = await run().then(() => assert.fail("noisy/1 should fail"), (e) => e);
		assert.match(error.message, /^dumbpipe exited 1: x+\nlast line$/);
		assert.ok(error.message.length < 16500, `stderr kept: ${error.message.length} bytes`);
	}
	const missing = irohSession({ key: "/nonexistent/key", spawn: (cmd, args, options) => spawn(join(FAKE, "no-such-binary"), args, options) });
	const got = [];
	const failed = await (async () => {
		for await (const m of await missing({ ticket: T, alpn: "echo/1" })) got.push(m);
	})().then(() => null, (e) => e);
	assert.match(failed?.message ?? "", /spawn dumbpipe .*ENOENT/);
	assert.deepEqual(got, [], "no spurious exitCode -2 after the spawn error");
});

test("irohCall: com.fedfork.exec.iroh.connect with the host key; ssh needs the key, a raw alpn does not", async () => {
	const stateDir = () => mkdtempSync(join(tmpdir(), "atlibs-iroh-"));
	const key = join(stateDir(), "iroh_ed25519");
	const ran = [];
	const run = async (cmd, args, input) => (ran.push({ cmd, args, input }), { stdout: "fc-1\n", stderr: "", code: 0 });
	const iroh = irohCall({ key, run });
	const ssh = { input: { ticket: `iroh://${T}`, hostKeys, command: "hostname" } };
	await assert.rejects(iroh(ssh), /set ATLIBS_IROH_KEY or put it at .*iroh_ed25519/);
	assert.equal(ran.length, 0);
	writeFileSync(key, "not a real key\n", { mode: 0o600 });
	assert.deepEqual(await iroh(ssh), { output: "fc-1\n", exitCode: 0 });
	assert.equal(ran[0].cmd, "ssh");
	assert.equal(ran[0].args[ran[0].args.indexOf("-i") + 1], key);
	// A raw ALPN exchange needs no ssh key.
	const raw = irohCall({ key: join(stateDir(), "missing"), run });
	assert.deepEqual(await raw({ input: { ticket: T, alpn: "atirc/irc/1", send: "PING\r\n" } }), { output: "fc-1\n" });
	// review 3b #7: a peer that never closes is killed at the timeout (code null); what it sent comes back, flagged.
	const slow = irohCall({ key: join(stateDir(), "missing"), run: async () => ({ stdout: "partial", stderr: "", code: null }) });
	assert.deepEqual(await slow({ input: { ticket: T, alpn: "atirc/irc/1" } }), { output: "partial", timedOut: true });
	// review 4a M3: one killed at OUTPUT_MAX says truncated (ssh and alpn), not timed out or failed
	const flood = irohCall({ key, run: async () => ({ stdout: "x", stderr: "", code: null, truncated: true }) });
	assert.deepEqual(await flood(ssh), { output: "x", truncated: true });
	assert.deepEqual(await flood({ input: { ticket: T, alpn: "atirc/irc/1" } }), { output: "x", truncated: true });
});

// review 4a M2/M3: no user ssh config, no host env (ATPROTO_*, SSH_AUTH_SOCK) in dumbpipe or ssh, stdout capped.
test("children: ssh -F /dev/null, a cleared env (PATH, HOME, LANG, IROH_SECRET), at most OUTPUT_MAX of stdout", async () => {
	assert.deepEqual(sshArgs("/k", T, "/kh", ["true"]).slice(0, 2), ["-F", "/dev/null"]);
	process.env.ATPROTO_PASSWORD_TEST = "never-in-a-child";
	try {
		const keys = (await exchange("/usr/bin/env", [])).stdout.split("\n").filter(Boolean).map((l) => l.split("=")[0]);
		assert.ok(keys.includes("IROH_SECRET") && keys.every((k) => ["PATH", "HOME", "LANG", "IROH_SECRET"].includes(k)), keys.join(" "));
		const seen = [];
		const spy = irohSession({ key: "/nonexistent/key", spawn: (cmd, args, options) => (seen.push(options.env), spawn(join(FAKE, cmd), args, options)) });
		await all(await spy({ ticket: T, alpn: "secret/1" }));
		assert.ok(!Object.keys(seen[0]).some((k) => k.startsWith("ATPROTO_")), "a session's child env");
	} finally {
		delete process.env.ATPROTO_PASSWORD_TEST;
	}
	const flood = await exchange("sh", ["-c", "head -c 10000000 /dev/zero; sleep 5"]);
	assert.deepEqual([flood.stdout.length, flood.truncated, flood.code], [OUTPUT_MAX, true, null]);
	const exact = await exchange("sh", ["-c", `head -c ${OUTPUT_MAX} /dev/zero`]);
	assert.deepEqual([exact.stdout.length, exact.truncated, exact.code], [OUTPUT_MAX, undefined, 0], "exactly the cap is all of it");
});