Skip to content

Commit fedbdfb

Browse files
authored
Merge branch 'main' into fix/external-trace-id-per-run
2 parents 600f742 + d7056a9 commit fedbdfb

8 files changed

Lines changed: 632 additions & 5 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: fix
4+
---
5+
6+
Runs triggered with a `ttl` could get permanently stuck in the queued state if they started executing and were then requeued after a failure (for example a worker dying mid-run) once the TTL had already elapsed. Requeued runs now dequeue normally: a run's TTL only applies while it is waiting to start for the first time.

apps/supervisor/src/env.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ export const Env = z
220220
KUBERNETES_RUNNER_SECCOMP_PROFILE_RUNTIMES: z
221221
.enum(["none", "node-24-plus", "all"])
222222
.default("node-24-plus"),
223+
KUBERNETES_RUNNER_SECURITY_CONTEXT: z.enum(["off", "baseline", "restricted"]).default("off"),
224+
KUBERNETES_RUNNER_RUN_AS_USER: z.coerce.number().int().min(1).default(1000),
223225

224226
// Pod DNS config — override the cluster default ndots to `KUBERNETES_POD_DNS_NDOTS`.
225227
// Default k8s ndots is 5: any name with fewer than 5 dots (e.g. `api.example.com`, 2 dots) is first walked

apps/supervisor/src/workloadManager/kubernetes.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
22
import {
33
nodetypeNodeSelector,
44
runPodTolerations,
5+
runnerSecurityContext,
56
withRunnerSeccompProfile,
67
withNodeSelector,
78
} from "./kubernetesPodSpec.js";
@@ -153,3 +154,35 @@ describe("withRunnerSeccompProfile", () => {
153154
}
154155
});
155156
});
157+
158+
describe("runnerSecurityContext", () => {
159+
it("sets nothing when off", () => {
160+
expect(runnerSecurityContext("off", 1000, "node-24")).toBeUndefined();
161+
});
162+
163+
it("drops all capabilities and blocks escalation at baseline", () => {
164+
expect(runnerSecurityContext("baseline", 1000, "node-24")).toEqual({
165+
allowPrivilegeEscalation: false,
166+
capabilities: { drop: ["ALL"] },
167+
});
168+
});
169+
170+
it("pins the configured uid when restricted", () => {
171+
expect(runnerSecurityContext("restricted", 1000, "node-24")).toEqual({
172+
allowPrivilegeEscalation: false,
173+
capabilities: { drop: ["ALL"] },
174+
runAsNonRoot: true,
175+
runAsUser: 1000,
176+
});
177+
});
178+
179+
it("pins bun's own uid, which differs from node's", () => {
180+
expect(runnerSecurityContext("restricted", 1000, "bun")?.runAsUser).toBe(1001);
181+
});
182+
183+
it("falls back to the configured uid when the runtime is unknown", () => {
184+
for (const runtime of [undefined, null, "", "node", "node-22", "node-26"]) {
185+
expect(runnerSecurityContext("restricted", 1000, runtime)?.runAsUser).toBe(1000);
186+
}
187+
});
188+
});

apps/supervisor/src/workloadManager/kubernetes.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { getRunnerId } from "../util.js";
1717
import {
1818
nodetypeNodeSelector,
1919
runPodTolerations,
20+
runnerSecurityContext,
2021
withRunnerSeccompProfile,
2122
withNodeSelector,
2223
} from "./kubernetesPodSpec.js";
@@ -175,6 +176,11 @@ export class KubernetesWorkloadManager implements WorkloadManager {
175176
},
176177
],
177178
resources: this.#getResourcesForMachine(opts.machine),
179+
securityContext: runnerSecurityContext(
180+
env.KUBERNETES_RUNNER_SECURITY_CONTEXT,
181+
env.KUBERNETES_RUNNER_RUN_AS_USER,
182+
opts.runtime
183+
),
178184
env: [
179185
{
180186
name: "TRIGGER_DEQUEUED_AT_MS",

apps/supervisor/src/workloadManager/kubernetesPodSpec.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,33 @@ export function withRunnerSeccompProfile(
9696
},
9797
};
9898
}
99+
100+
const BUN_RUN_AS_USER = 1001;
101+
102+
/**
103+
* runnerSecurityContext maps a configured level onto the run container's security
104+
* context. "baseline" drops the capability bounding set and blocks setuid
105+
* escalation; "restricted" additionally pins the container to a non-root uid.
106+
*
107+
* The uid is set explicitly rather than read from the image: the kubelet cannot
108+
* verify `runAsNonRoot` against an image that declares a named user, and fails
109+
* the container instead. Bun images carry their user at a different uid to
110+
* node's, so the runtime selects which uid is pinned.
111+
*/
112+
export function runnerSecurityContext(
113+
level: "off" | "baseline" | "restricted",
114+
runAsUser: number,
115+
runtime: string | null | undefined
116+
): k8s.V1SecurityContext | undefined {
117+
if (level === "off") {
118+
return undefined;
119+
}
120+
121+
return {
122+
allowPrivilegeEscalation: false,
123+
capabilities: { drop: ["ALL"] },
124+
...(level === "restricted"
125+
? { runAsNonRoot: true, runAsUser: runtime === "bun" ? BUN_RUN_AS_USER : runAsUser }
126+
: {}),
127+
};
128+
}

apps/webapp/app/components/billing/OrgBanner.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,14 +157,14 @@ function NoLimitConfiguredBanner() {
157157
to={v3BillingLimitsPath(organization)}
158158
>
159159
<span className="mx-auto grow self-center truncate text-text-bright system:text-white">
160-
Configure billing limit
160+
Billing limit settings
161161
</span>
162162
</LinkButton>
163163
) : undefined
164164
}
165165
>
166166
{canManageBillingLimits
167-
? "Protect your organization from unexpected usage spikes."
167+
? "Add a billing limit to your account to prevent overspending"
168168
: "Billing limits are not configured for this organization. Contact an organization administrator to configure them."}
169169
</AnimatedOrgBannerBar>
170170
);

0 commit comments

Comments
 (0)