diff --git a/src/runner/k8s-job.ts b/src/runner/k8s-job.ts index 04c230c..c7b60d2 100644 --- a/src/runner/k8s-job.ts +++ b/src/runner/k8s-job.ts @@ -274,7 +274,7 @@ function runnerEnv(options: RunnerJobRenderOptions, context: { namespace: string const selectedSecret = context.secretRefs.find((item) => item.profile === options.run.backendProfile); const codexHome = runnerCodexHome(options.run.backendProfile, selectedSecret, context.sessionPvc); const bootRepoUrl = optionalString(options.bootRepoUrl) ?? defaultBootRepoUrl; - return dedupeEnvVars([ + const env = dedupeEnvVars([ { name: "AGENTRUN_MGR_URL", value: options.managerUrl }, { name: "AGENTRUN_API_KEY", valueFrom: { secretKeyRef: context.runnerApiKeySecretRef } }, { name: "AGENTRUN_RUN_ID", value: options.run.id }, @@ -287,7 +287,6 @@ function runnerEnv(options: RunnerJobRenderOptions, context: { namespace: string { name: "AGENTRUN_CODEX_SHELL_SANDBOX", value: codexShellSandbox(options.run.executionPolicy) }, { name: "AGENTRUN_SESSION_REF_JSON", value: JSON.stringify(options.run.sessionRef ?? null) }, { name: "AGENTRUN_RESOURCE_BUNDLE_JSON", value: JSON.stringify(options.run.resourceBundleRef ?? null) }, - ...(context.resourceCredential ? [{ name: "AGENTRUN_RESOURCE_CREDENTIAL_PATH", value: context.resourceCredential.mountPath }] : []), { name: "AGENTRUN_WORKSPACE_ROOT", value: "/home/agentrun/workspaces" }, { name: "AGENTRUN_RESOURCE_BIN_PATH", value: defaultResourceBinPath }, { name: "AGENTRUN_SOURCE_COMMIT", value: context.sourceCommit }, @@ -325,6 +324,13 @@ function runnerEnv(options: RunnerJobRenderOptions, context: { namespace: string ...toolCredentialEnvVars(context.toolCredentials), ...transientEnvVars(options.transientEnv ?? []), ]); + if (context.resourceCredential) { + const item: JsonRecord = { name: "AGENTRUN_RESOURCE_CREDENTIAL_PATH", value: context.resourceCredential.mountPath }; + const existing = env.findIndex((candidate) => candidate.name === item.name); + if (existing >= 0) env[existing] = item; + else env.push(item); + } + return env; } function normalizeRunnerIdleTimeoutMs(value: number | undefined): number { diff --git a/src/selftest/cases/20-runner-k8s-job.ts b/src/selftest/cases/20-runner-k8s-job.ts index d79e96b..085d1f1 100644 --- a/src/selftest/cases/20-runner-k8s-job.ts +++ b/src/selftest/cases/20-runner-k8s-job.ts @@ -5,7 +5,7 @@ import { startManagerServer } from "../../mgr/server.js"; import { MemoryAgentRunStore } from "../../mgr/store.js"; import { ManagerClient } from "../../mgr/client.js"; import { enforceRunnerRetentionBeforeCreate } from "../../mgr/runner-retention.js"; -import { renderRunnerJobDryRun } from "../../runner/k8s-job.js"; +import { renderRunnerJobDryRun, renderRunnerJobManifest } from "../../runner/k8s-job.js"; import type { JsonRecord, RunRecord } from "../../common/types.js"; import { assertNoSecretLeak, createRunWithCommand, loadArtificerImageRef, type SelfTestCase } from "../harness.js"; @@ -66,7 +66,7 @@ const selfTest: SelfTestCase = async (context) => { assert.deepEqual((((rendered.transientEnv as JsonRecord).names) as string[]), ["HWLAB_API_KEY"]); assertNoSecretLeak(rendered); - const resourceCredentialRendered = renderRunnerJobDryRun({ + const resourceCredentialOptions: Parameters[0] = { run: { ...baseRun, resourceBundleRef: { @@ -83,8 +83,11 @@ const selfTest: SelfTestCase = async (context) => { image: "127.0.0.1:5000/agentrun/agentrun-mgr@sha256:1111111111111111111111111111111111111111111111111111111111111111", attemptId: "attempt_resource_credential_selftest", sourceCommit: "self-test", - }); - assertRunnerJobUsesResourceCredential(resourceCredentialRendered, "agentrun-v01-resource-github", ["id_ed25519", "known_hosts", "config"]); + transientEnv: [{ name: "AGENTRUN_RESOURCE_CREDENTIAL_PATH", value: "/tmp/untrusted-resource-credential" }], + }; + const resourceCredentialRendered = renderRunnerJobDryRun(resourceCredentialOptions); + assertRunnerJobUsesResourceCredential(resourceCredentialRendered, "agentrun-v01-resource-github", ["id_ed25519", "known_hosts", "config"], "REDACTED"); + assert.equal(runnerEnvValue(renderRunnerJobManifest(resourceCredentialOptions).manifest, "AGENTRUN_RESOURCE_CREDENTIAL_PATH"), "/var/run/agentrun/resource-git"); assertNoSecretLeak(resourceCredentialRendered); await assert.rejects( @@ -702,7 +705,7 @@ function assertRunnerJobUsesToolCredentialVolume(rendered: JsonRecord, secretNam assert.equal(summaryEntry.valuesPrinted, false); } -function assertRunnerJobUsesResourceCredential(rendered: JsonRecord, secretName: string, secretKeys: string[]): void { +function assertRunnerJobUsesResourceCredential(rendered: JsonRecord, secretName: string, secretKeys: string[], expectedEnvValue = "/var/run/agentrun/resource-git"): void { const manifest = rendered.manifest as JsonRecord; const spec = manifest.spec as JsonRecord; const template = spec.template as JsonRecord; @@ -717,7 +720,7 @@ function assertRunnerJobUsesResourceCredential(rendered: JsonRecord, secretName: const volume = volumes.find((item) => item.name === mount.name) as JsonRecord; assert.equal(((volume.secret as JsonRecord).secretName), secretName); assert.deepEqual(((volume.secret as JsonRecord).items as JsonRecord[]).map((item) => item.key), secretKeys); - assert.equal(runnerEnvValue(manifest, "AGENTRUN_RESOURCE_CREDENTIAL_PATH"), "/var/run/agentrun/resource-git"); + assert.equal(runnerEnvValue(manifest, "AGENTRUN_RESOURCE_CREDENTIAL_PATH"), expectedEnvValue); const summary = rendered.resourceCredential as JsonRecord; assert.equal(summary.present, true); assert.equal(summary.name, secretName);