Merge pull request #150 from pikasTech/fix/artificer-github-ssh-known-hosts-146

fix: 修复 Artificer GitHub SSH 装配路径
This commit is contained in:
Lyon
2026-06-10 21:24:03 +08:00
committed by GitHub
5 changed files with 37 additions and 7 deletions
@@ -204,7 +204,7 @@ Secret 创建和轮换不由 source branch 自动生成;source branch 只声
- 写入对象固定为 `agentrun-v01/agentrun-v01-tool-github-ssh`keys 固定为 `id_ed25519``known_hosts``config`
- CLI dry-run 只显示输入文件 bytes、SecretRef、key 列表和确认命令,不输出文件内容。
- manager upsert Secret 只返回 resourceVersion、hash suffix、SecretRef 和 redaction 状态,不输出 `data``stringData`、private key、known_hosts 或 config 明文。
- `config` 缺省为只允许 `github.com``ssh.github.com:443``IdentityFile ~/.ssh/id_ed25519``StrictHostKeyChecking yes``UserKnownHostsFile ~/.ssh/known_hosts` 的最小配置;若传入自定义 config,必须包含 `Host``IdentityFile`
- `config` 缺省为只允许 `github.com``ssh.github.com:443``IdentityFile /home/agentrun/.ssh/id_ed25519``StrictHostKeyChecking yes``UserKnownHostsFile /home/agentrun/.ssh/known_hosts` 的最小配置;runner Job 同时注入 `GIT_SSH_COMMAND`,用绝对路径指向挂载的 config、identity 和 known_hosts,避免 OpenSSH 的 `~` 按容器 passwd home 解析到错误目录;若传入自定义 config,必须包含 `Host``IdentityFile`
- runtime 消费仍必须通过 `executionPolicy.secretScope.toolCredentials[]` 的 volume projection 挂载到 `/home/agentrun/.ssh`;不得把同一 SSH private key 复制到镜像、ConfigMap、payload 或 transient env。
## 日志与事件 Redaction
+2 -2
View File
@@ -196,10 +196,10 @@ function defaultGithubSshConfig(): string {
" HostName ssh.github.com",
" User git",
" Port 443",
" IdentityFile ~/.ssh/id_ed25519",
" IdentityFile /home/agentrun/.ssh/id_ed25519",
" IdentitiesOnly yes",
" StrictHostKeyChecking yes",
" UserKnownHostsFile ~/.ssh/known_hosts",
" UserKnownHostsFile /home/agentrun/.ssh/known_hosts",
"",
].join("\n");
}
+22 -2
View File
@@ -262,7 +262,8 @@ function codexShellSandbox(policy: ExecutionPolicy): string {
}
function toolCredentialEnvVars(items: ToolCredentialProjection[]): JsonRecord[] {
return items.filter((item): item is ToolCredentialEnvProjection => item.kind === "env").map((item) => ({
return [
...items.filter((item): item is ToolCredentialEnvProjection => item.kind === "env").map((item) => ({
name: item.envName,
valueFrom: {
secretKeyRef: {
@@ -270,7 +271,26 @@ function toolCredentialEnvVars(items: ToolCredentialProjection[]): JsonRecord[]
key: item.secretKey,
},
},
}));
})),
...githubSshCommandEnvVars(items),
];
}
function githubSshCommandEnvVars(items: ToolCredentialProjection[]): JsonRecord[] {
const credential = items.find((item): item is ToolCredentialVolumeProjection => item.kind === "volume" && item.tool === "github" && item.purpose === "github-ssh");
if (!credential) return [];
const mountPath = credential.mountPath.replace(/\/+$/u, "");
return [{
name: "GIT_SSH_COMMAND",
value: [
"ssh",
"-F", `${mountPath}/config`,
"-i", `${mountPath}/id_ed25519`,
"-o", "IdentitiesOnly=yes",
"-o", "StrictHostKeyChecking=yes",
"-o", `UserKnownHostsFile=${mountPath}/known_hosts`,
].join(" "),
}];
}
function toolCredentialVolumeMounts(items: ToolCredentialProjection[]): JsonRecord[] {
+10
View File
@@ -49,6 +49,7 @@ const selfTest: SelfTestCase = async (context) => {
assertRunnerJobUsesToolCredential(rendered, "GH_TOKEN", "agentrun-v01-tool-github-pr", "GH_TOKEN");
assertRunnerJobUsesToolCredential(rendered, "UNIDESK_SSH_CLIENT_TOKEN", "agentrun-v01-tool-unidesk-ssh", "UNIDESK_SSH_CLIENT_TOKEN");
assertRunnerJobUsesToolCredentialVolume(rendered, "agentrun-v01-tool-github-ssh", "/home/agentrun/.ssh", ["id_ed25519", "known_hosts", "config"]);
assertRunnerJobUsesGithubSshCommand(rendered.manifest as JsonRecord, "/home/agentrun/.ssh");
assertRunnerJobUsesG14EgressProxy(rendered.manifest as JsonRecord);
assert.equal(runnerEnvValue(rendered.manifest as JsonRecord, "AGENTRUN_CODEX_SHELL_SANDBOX"), "danger-full-access");
assert.equal(runnerEnvValue(rendered.manifest as JsonRecord, "HWLAB_API_KEY"), "REDACTED");
@@ -219,6 +220,7 @@ process.exit(1);
assertRunnerJobUsesToolCredential({ manifest, toolCredentials: (created as JsonRecord).toolCredentials } as JsonRecord, "GH_TOKEN", "agentrun-v01-tool-github-pr", "GH_TOKEN");
assertRunnerJobUsesToolCredential({ manifest, toolCredentials: (created as JsonRecord).toolCredentials } as JsonRecord, "UNIDESK_SSH_CLIENT_TOKEN", "agentrun-v01-tool-unidesk-ssh", "UNIDESK_SSH_CLIENT_TOKEN");
assertRunnerJobUsesToolCredentialVolume({ manifest, toolCredentials: (created as JsonRecord).toolCredentials } as JsonRecord, "agentrun-v01-tool-github-ssh", "/home/agentrun/.ssh", ["id_ed25519", "known_hosts", "config"]);
assertRunnerJobUsesGithubSshCommand(manifest, "/home/agentrun/.ssh");
assertNoSecretLeak(created);
const defaultEndpointJobItem = await createRunWithCommand(jobClient, { ...context, toolCredentials: unideskSshToolCredentials }, "job create unidesk ssh default endpoint", "selftest-job-create-unidesk-ssh-default-endpoint", 15_000);
const defaultEndpointCreated = await jobClient.post(`/api/v1/runs/${defaultEndpointJobItem.runId}/runner-jobs`, {
@@ -333,6 +335,14 @@ function assertRunnerJobUsesG14EgressProxy(manifest: JsonRecord): void {
assert.ok(noProxy.includes(".svc"), "NO_PROXY must include Kubernetes Service domains");
}
function assertRunnerJobUsesGithubSshCommand(manifest: JsonRecord, mountPath: string): void {
const value = String(runnerEnvValue(manifest, "GIT_SSH_COMMAND"));
assert.ok(value.includes(`-F ${mountPath}/config`), "GIT_SSH_COMMAND must use the mounted SSH config");
assert.ok(value.includes(`-i ${mountPath}/id_ed25519`), "GIT_SSH_COMMAND must use the mounted identity by absolute path");
assert.ok(value.includes(`UserKnownHostsFile=${mountPath}/known_hosts`), "GIT_SSH_COMMAND must use mounted known_hosts by absolute path");
assert.ok(value.includes("StrictHostKeyChecking=yes"), "GIT_SSH_COMMAND must keep strict host key checking enabled");
}
function assertRunnerJobUsesToolCredential(rendered: JsonRecord, envName: string, secretName: string, secretKey: string): void {
const manifest = rendered.manifest as JsonRecord;
const spec = manifest.spec as JsonRecord;
+2 -2
View File
@@ -15,7 +15,7 @@ selftest-private-key-material
${privateKeyFooter}
`;
const knownHosts = "github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIselftestKnownHostsKey\n";
const sshConfig = "Host github.com\n HostName ssh.github.com\n User git\n Port 443\n IdentityFile ~/.ssh/id_ed25519\n";
const sshConfig = "Host github.com\n HostName ssh.github.com\n User git\n Port 443\n IdentityFile /home/agentrun/.ssh/id_ed25519\n UserKnownHostsFile /home/agentrun/.ssh/known_hosts\n";
const selfTest: SelfTestCase = async (context) => {
const fakeKubectl = path.join(context.tmp, "fake-tool-kubectl.js");
@@ -124,7 +124,7 @@ function assertNoCredentialLeak(value: unknown): void {
assert.equal(text.includes("selftest-private-key-material"), false);
assert.equal(text.includes(privateKeyHeader), false);
assert.equal(text.includes("AAAAC3NzaC1lZDI1NTE5AAAAIselftestKnownHostsKey"), false);
assert.equal(text.includes("IdentityFile ~/.ssh/id_ed25519"), false);
assert.equal(text.includes("IdentityFile /home/agentrun/.ssh/id_ed25519"), false);
}
async function runCliJson(context: { root: string }, args: string[]): Promise<JsonRecord> {