Merge pull request #150 from pikasTech/fix/artificer-github-ssh-known-hosts-146
fix: 修复 Artificer GitHub SSH 装配路径
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
@@ -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[] {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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> {
|
||||
|
||||
Reference in New Issue
Block a user