feat(pi-ssh): support SSH agent authentication

This commit is contained in:
云服务部-叶林立
2026-08-26 20:01:45 +08:00
parent b7758b239e
commit 874d4da096
10 changed files with 98 additions and 22 deletions
+32 -4
View File
@@ -3,7 +3,7 @@ import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import test from "node:test";
import { validatePiSshConfig } from "../src/config.ts";
import { effectiveValue, effectiveValues, parseSshG } from "../src/import.ts";
import { effectiveIdentityAgent, effectiveValue, effectiveValues, parseSshG } from "../src/import.ts";
import { fingerprintHostKey } from "../src/ssh2-transport.ts";
test("parses the effective ssh -G output including repeated identity files", () => {
@@ -21,7 +21,19 @@ test("parses the effective ssh -G output including repeated identity files", ()
assert.deepEqual(effectiveValues(parsed, "identityfile"), ["~/.ssh/first", "~/.ssh/second"]);
});
test("validates password and private-key host definitions", () => {
test("resolves IdentityAgent socket paths and SSH_AUTH_SOCK references", () => {
assert.equal(
effectiveIdentityAgent(parseSshG("identityagent /Users/test/.1password/agent.sock"), {}),
"/Users/test/.1password/agent.sock",
);
assert.equal(
effectiveIdentityAgent(parseSshG("identityagent $SSH_AUTH_SOCK"), { SSH_AUTH_SOCK: "/tmp/agent.sock" }),
"/tmp/agent.sock",
);
assert.equal(effectiveIdentityAgent(parseSshG("identityagent none"), { SSH_AUTH_SOCK: "/tmp/agent.sock" }), undefined);
});
test("validates password, private-key, and agent host definitions", () => {
const password = validatePiSshConfig({
version: 1,
hosts: {
@@ -37,6 +49,20 @@ test("validates password and private-key host definitions", () => {
});
assert.equal(password.hosts.build.auth.type, "password");
const agent = validatePiSshConfig({
version: 1,
hosts: {
build: {
hostName: "build.example.test",
user: "builder",
port: 22,
auth: { type: "agent", socketPath: "~/.1password/agent.sock" },
hostKey: { algorithm: "ssh-ed25519", fingerprint: "SHA256:value" },
},
},
});
assert.deepEqual(agent.hosts.build.auth, { type: "agent", socketPath: "~/.1password/agent.sock" });
assert.throws(() => validatePiSshConfig({
version: 1,
hosts: {
@@ -44,11 +70,11 @@ test("validates password and private-key host definitions", () => {
hostName: "build.example.test",
user: "builder",
port: 22,
auth: { type: "password", password: "" },
auth: { type: "agent", socketPath: "" },
hostKey: { algorithm: "ssh-ed25519", fingerprint: "SHA256:value" },
},
},
}), /password/);
}), /socketPath/);
});
test("formats SSH host keys as pinned SHA256 fingerprints", () => {
@@ -68,4 +94,6 @@ test("configuration import validates remote HOME and cwd with framed probes", as
assert.match(source, /probeRemotePath\(transport, "home"\)/);
assert.match(source, /probeRemotePath\(transport, "cwd"\)/);
assert.doesNotMatch(source, /transport\.capture\(/);
assert.match(source, /Authentication \[agent\/key\/password\]/);
assert.match(source, /effectiveIdentityAgent\(effective\)/);
});
+13 -1
View File
@@ -1,7 +1,7 @@
import assert from "node:assert/strict";
import { EventEmitter } from "node:events";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { homedir, tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
import type { Client, ClientChannel, ConnectConfig, SFTPWrapper } from "ssh2";
@@ -235,6 +235,18 @@ test("loads private-key authentication and forwards the passphrase", async () =>
}
});
test("forwards SSH agent authentication through ssh2", async () => {
const host: SshHostConfig = {
...passwordHost(),
auth: { type: "agent", socketPath: "~/.1password/agent.sock" },
};
const { fake, transport } = await connectedTransport(undefined, host);
assert.equal(fake.connectConfig?.agent, join(homedir(), ".1password", "agent.sock"));
assert.equal(fake.connectConfig?.password, undefined);
assert.equal(fake.connectConfig?.privateKey, undefined);
await transport.dispose();
});
test("reads files, checks access modes, detects images, and reuses SFTP", async () => {
const sftp = new FakeSftp();
sftp.files.set("/srv/app/a.txt", Buffer.from("hello"));