feat: 装配 UniDesk SSH 工具凭证

This commit is contained in:
Codex
2026-06-02 15:40:48 +08:00
parent 16b32af9b5
commit 458d814fa2
7 changed files with 99 additions and 21 deletions
+44 -5
View File
@@ -18,7 +18,14 @@ const selfTest: SelfTestCase = async (context) => {
secretRef: { name: "agentrun-v01-tool-github-pr", keys: ["GH_TOKEN"] },
projection: { kind: "env", envName: "GH_TOKEN", secretKey: "GH_TOKEN" },
}];
const item = await createRunWithCommand(client, { ...context, toolCredentials: githubToolCredentials }, "job smoke", "selftest-job-render", 15_000);
const unideskSshToolCredentials = [{
tool: "unidesk-ssh",
purpose: "ssh-passthrough",
secretRef: { name: "agentrun-v01-tool-unidesk-ssh", keys: ["UNIDESK_SSH_CLIENT_TOKEN"] },
projection: { kind: "env", envName: "UNIDESK_SSH_CLIENT_TOKEN", secretKey: "UNIDESK_SSH_CLIENT_TOKEN" },
}];
const combinedToolCredentials = [...githubToolCredentials, ...unideskSshToolCredentials];
const item = await createRunWithCommand(client, { ...context, toolCredentials: combinedToolCredentials }, "job smoke", "selftest-job-render", 15_000);
const rendered = renderRunnerJobDryRun({
run: await client.get(`/api/v1/runs/${item.runId}`) as RunRecord,
commandId: item.commandId,
@@ -34,10 +41,24 @@ const selfTest: SelfTestCase = async (context) => {
assert.equal((rendered.jobIdentity as { serviceAccountName?: string }).serviceAccountName, "agentrun-v01-runner");
assertRunnerJobUsesWritableCodexHome(rendered.manifest as JsonRecord, context.codexHome, "codex-0", "/var/run/agentrun/secrets/codex-0");
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");
assert.equal(runnerEnvValue(rendered.manifest as JsonRecord, "HWLAB_DEVICE_POD_SESSION_TOKEN"), "REDACTED");
assert.deepEqual((((rendered.transientEnv as JsonRecord).names) as string[]), ["HWLAB_DEVICE_POD_SESSION_TOKEN"]);
assertNoSecretLeak(rendered);
await assert.rejects(
() => createRunWithCommand(client, {
...context,
toolCredentials: [{
tool: "unidesk-ssh",
purpose: "ssh-passthrough",
secretRef: { name: "agentrun-v01-tool-unidesk-ssh", keys: ["UNIDESK_SSH_CLIENT_TOKEN"] },
projection: { kind: "env", envName: "UNIDESK_SSH_TOKEN", secretKey: "UNIDESK_SSH_CLIENT_TOKEN" },
}],
}, "bad unidesk ssh projection", "selftest-bad-unidesk-ssh-projection", 15_000),
(error) => error instanceof Error && error.message.includes("unidesk-ssh must project UNIDESK_SSH_CLIENT_TOKEN"),
);
const deepseekItem = await createRunWithCommand(client, { ...context, backendProfile: "deepseek" }, "deepseek job smoke", "selftest-deepseek-job-render", 15_000);
const deepseekRendered = renderRunnerJobDryRun({
run: await client.get(`/api/v1/runs/${deepseekItem.runId}`) as RunRecord,
@@ -91,7 +112,7 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
});
try {
const jobClient = new ManagerClient(serverWithKubectl.baseUrl);
const jobItem = await createRunWithCommand(jobClient, { ...context, toolCredentials: githubToolCredentials }, "job create smoke", "selftest-job-create", 15_000);
const jobItem = await createRunWithCommand(jobClient, { ...context, toolCredentials: combinedToolCredentials }, "job create smoke", "selftest-job-create", 15_000);
const created = await jobClient.post(`/api/v1/runs/${jobItem.runId}/runner-jobs`, {
commandId: jobItem.commandId,
attemptId: "attempt_selftest_create",
@@ -106,22 +127,33 @@ console.log(JSON.stringify({ apiVersion: manifest.apiVersion, kind: manifest.kin
{ name: "HWLAB_RUNTIME_ENDPOINT_SOURCE", value: "runtime-namespace", sensitive: true },
{ name: "HWLAB_RUNTIME_ENDPOINT_LOCKED", value: "1", sensitive: true },
{ name: "HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME", value: "1", sensitive: true },
{ name: "UNIDESK_MAIN_SERVER_IP", value: "https://unidesk.example.test", sensitive: true },
],
});
assert.equal((created as { mutation?: unknown }).mutation, true);
assert.equal(((created as JsonRecord).retention as JsonRecord).ttlSecondsAfterFinished, 86_400);
assert.deepEqual((((created as JsonRecord).transientEnv as JsonRecord).names) as string[], ["HWLAB_DEVICE_POD_SESSION_TOKEN", "HWLAB_CLOUD_API_URL", "HWLAB_DEVICE_POD_API_URL", "HWLAB_RUNTIME_API_URL", "HWLAB_RUNTIME_WEB_URL", "HWLAB_RUNTIME_NAMESPACE", "HWLAB_RUNTIME_LANE", "HWLAB_RUNTIME_ENDPOINT_SOURCE", "HWLAB_RUNTIME_ENDPOINT_LOCKED", "HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME"]);
assert.deepEqual((((created as JsonRecord).transientEnv as JsonRecord).names) as string[], ["HWLAB_DEVICE_POD_SESSION_TOKEN", "HWLAB_CLOUD_API_URL", "HWLAB_DEVICE_POD_API_URL", "HWLAB_RUNTIME_API_URL", "HWLAB_RUNTIME_WEB_URL", "HWLAB_RUNTIME_NAMESPACE", "HWLAB_RUNTIME_LANE", "HWLAB_RUNTIME_ENDPOINT_SOURCE", "HWLAB_RUNTIME_ENDPOINT_LOCKED", "HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME", "UNIDESK_MAIN_SERVER_IP"]);
const manifest = JSON.parse(await readFile(createdManifest, "utf8")) as JsonRecord;
assert.equal((manifest.spec as JsonRecord).ttlSecondsAfterFinished, 86_400);
assert.equal(runnerEnvValue(manifest, "HWLAB_DEVICE_POD_SESSION_TOKEN"), "test-token-material");
assert.equal(runnerEnvValue(manifest, "HWLAB_CLOUD_API_URL"), "http://cloud.test");
assert.equal(runnerEnvValue(manifest, "HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME"), "1");
assert.equal(runnerEnvValue(manifest, "UNIDESK_MAIN_SERVER_IP"), "https://unidesk.example.test");
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");
assertNoSecretLeak(created);
await assert.rejects(
() => jobClient.post(`/api/v1/runs/${jobItem.runId}/runner-jobs`, {
commandId: jobItem.commandId,
attemptId: "attempt_selftest_bad_unidesk_ssh_transient",
transientEnv: [{ name: "UNIDESK_SSH_CLIENT_TOKEN", value: "test-unidesk-ssh-client-token", sensitive: true }],
}),
(error) => error instanceof Error && error.message.includes("must use tool/provider credential assembly instead"),
);
} finally {
await new Promise<void>((resolve) => serverWithKubectl.server.close(() => resolve()));
}
return { name: "runner-k8s-job", tests: ["runner-k8s-job-dry-run", "runner-k8s-job-deepseek-profile-dry-run", "runner-k8s-job-minimax-m3-profile-dry-run", "runner-k8s-job-create-api", "runner-k8s-job-retention-ttl", "runner-job-transient-env", "runner-job-tool-credential-env"] };
return { name: "runner-k8s-job", tests: ["runner-k8s-job-dry-run", "runner-k8s-job-deepseek-profile-dry-run", "runner-k8s-job-minimax-m3-profile-dry-run", "runner-k8s-job-create-api", "runner-k8s-job-retention-ttl", "runner-job-transient-env", "runner-job-tool-credential-env", "runner-job-unidesk-ssh-tool-credential-env", "runner-job-unidesk-ssh-transient-env-denied"] };
} finally {
await new Promise<void>((resolve) => server.server.close(() => resolve()));
}
@@ -157,7 +189,14 @@ function assertRunnerJobUsesToolCredential(rendered: JsonRecord, envName: string
const summary = rendered.toolCredentials as JsonRecord;
assert.equal(summary.valuesPrinted, false);
assert.equal(summary.count, 1);
assert.ok(Number(summary.count) >= 1);
const items = summary.items as JsonRecord[];
const summaryEntry = items.find((item) => {
const projection = item.projection as JsonRecord;
return item.name === secretName && projection.envName === envName && projection.secretKey === secretKey;
});
assert.ok(summaryEntry, `${envName} tool credential summary should include its SecretRef and projection`);
assert.equal(summaryEntry.valuesPrinted, false);
}
function assertRunnerJobUsesWritableCodexHome(manifest: JsonRecord, expectedCodexHome: string, volumeName: string, projectionPath: string): void {