fix: confirm SSH forward ownership
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { expect, test, vi } from "vitest";
|
||||
import { EventEmitter } from "node:events";
|
||||
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
@@ -333,7 +334,7 @@ test("provides a default bounded SSH factory through injected spawn and loopback
|
||||
const kill = vi.fn(() => true);
|
||||
const sshSpawn = vi.fn(() => ({ kill }));
|
||||
try {
|
||||
const adapter = createConcreteDiagnosticAdapters({ sshSpawn, reserveLoopbackPort: async () => 45432, waitForSshReady: async () => undefined } as any);
|
||||
const adapter = createConcreteDiagnosticAdapters({ sshSpawn, reserveLoopbackPort: async () => 45432, sshForwardConfirmed: async () => undefined } as any);
|
||||
await adapter.withSshTunnel({ sshHost: "bastion.example.test", sshPort: 22, sshUser: "tunnel", privateKeyFile, knownHostsFile: "/run/secrets/known-hosts", targetHost: "dwh.internal", targetPort: 5432, localHost: "127.0.0.1", localPort: 0, timeoutMs: 5000, signal: new AbortController().signal }, async () => undefined);
|
||||
expect(sshSpawn).toHaveBeenCalledWith(expect.arrayContaining([
|
||||
"StrictHostKeyChecking=yes", "UserKnownHostsFile=/run/secrets/known-hosts", "-L", "127.0.0.1:45432:dwh.internal:5432",
|
||||
@@ -353,7 +354,7 @@ test("waits for SSH readiness before probing and includes ExitOnForwardFailure",
|
||||
const probe = vi.fn(async () => undefined);
|
||||
const sshSpawn = vi.fn(() => ({ kill: vi.fn(() => true) }));
|
||||
try {
|
||||
const adapter = createConcreteDiagnosticAdapters({ sshSpawn, reserveLoopbackPort: async () => 45432, waitForSshReady: async () => await ready } as any);
|
||||
const adapter = createConcreteDiagnosticAdapters({ sshSpawn, reserveLoopbackPort: async () => 45432, sshForwardConfirmed: async () => await ready } as any);
|
||||
const running = adapter.withSshTunnel({ sshHost: "bastion.example.test", sshPort: 22, sshUser: "tunnel", privateKeyFile, knownHostsFile: "/run/secrets/known-hosts", targetHost: "dwh.internal", targetPort: 5432, localHost: "127.0.0.1", localPort: 0, timeoutMs: 5000, signal: new AbortController().signal }, probe);
|
||||
await Promise.resolve();
|
||||
expect(probe).not.toHaveBeenCalled();
|
||||
@@ -366,6 +367,45 @@ test("waits for SSH readiness before probing and includes ExitOnForwardFailure",
|
||||
}
|
||||
});
|
||||
|
||||
test("rejects unrelated listener readiness until the SSH child confirms its own forward", async () => {
|
||||
const directory = await mkdtemp(join(tmpdir(), "thothii-diagnostic-"));
|
||||
const privateKeyFile = join(directory, "ssh-key");
|
||||
await writeFile(privateKeyFile, "test-key\n", { mode: 0o600 });
|
||||
const child = Object.assign(new EventEmitter(), { kill: vi.fn(() => true) });
|
||||
const probe = vi.fn(async () => undefined);
|
||||
try {
|
||||
const adapter = createConcreteDiagnosticAdapters({ sshSpawn: vi.fn(() => child), reserveLoopbackPort: async () => 45432, waitForSshReady: async () => undefined } as any);
|
||||
await expect(adapter.withSshTunnel({ sshHost: "bastion.example.test", sshPort: 22, sshUser: "tunnel", privateKeyFile, knownHostsFile: "/run/secrets/known-hosts", targetHost: "dwh.internal", targetPort: 5432, localHost: "127.0.0.1", localPort: 0, timeoutMs: 20, signal: new AbortController().signal }, probe)).rejects.toThrow("SSH tunnel readiness failed");
|
||||
expect(probe).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("permits the probe only after this SSH child confirms its forwarded port", async () => {
|
||||
const directory = await mkdtemp(join(tmpdir(), "thothii-diagnostic-"));
|
||||
const privateKeyFile = join(directory, "ssh-key");
|
||||
await writeFile(privateKeyFile, "test-key\n", { mode: 0o600 });
|
||||
const stderr = new EventEmitter();
|
||||
const child = Object.assign(new EventEmitter(), { kill: vi.fn(() => true), stderr });
|
||||
child.kill.mockImplementation(() => { child.emit("exit", 0); return true; });
|
||||
const probe = vi.fn(async () => undefined);
|
||||
try {
|
||||
const adapter = createConcreteDiagnosticAdapters({ sshSpawn: vi.fn(() => child), reserveLoopbackPort: async () => 45432 } as any);
|
||||
const running = adapter.withSshTunnel({ sshHost: "bastion.example.test", sshPort: 22, sshUser: "tunnel", privateKeyFile, knownHostsFile: "/run/secrets/known-hosts", targetHost: "dwh.internal", targetPort: 5432, localHost: "127.0.0.1", localPort: 0, timeoutMs: 500, signal: new AbortController().signal }, probe);
|
||||
for (let attempt = 0; attempt < 20 && stderr.listenerCount("data") === 0; attempt += 1) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 1));
|
||||
}
|
||||
expect(stderr.listenerCount("data")).toBeGreaterThan(0);
|
||||
expect(probe).not.toHaveBeenCalled();
|
||||
stderr.emit("data", "debug1: Local forwarding listening on 127.0.0.1 port 45432.\n");
|
||||
await running;
|
||||
expect(probe).toHaveBeenCalledOnce();
|
||||
} finally {
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("requires a matching embedding model vector and removes its unique write probe", async () => {
|
||||
const adapters = successfulAdapters();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user