@namzu/sandbox 13.0.0 → 14.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +309 -0
- package/README.md +151 -0
- package/dist/backends/firecracker/protocol.d.ts +22 -0
- package/dist/backends/firecracker/protocol.d.ts.map +1 -1
- package/dist/backends/firecracker/protocol.js.map +1 -1
- package/dist/backends/firecracker/transport.d.ts +104 -9
- package/dist/backends/firecracker/transport.d.ts.map +1 -1
- package/dist/backends/firecracker/transport.js +139 -13
- package/dist/backends/firecracker/transport.js.map +1 -1
- package/dist/backends/kubernetes/egress-policy.d.ts +219 -0
- package/dist/backends/kubernetes/egress-policy.d.ts.map +1 -0
- package/dist/backends/kubernetes/egress-policy.js +314 -0
- package/dist/backends/kubernetes/egress-policy.js.map +1 -0
- package/dist/backends/kubernetes/index.d.ts +374 -0
- package/dist/backends/kubernetes/index.d.ts.map +1 -0
- package/dist/backends/kubernetes/index.js +671 -0
- package/dist/backends/kubernetes/index.js.map +1 -0
- package/dist/backends/kubernetes/k8s-client.d.ts +125 -0
- package/dist/backends/kubernetes/k8s-client.d.ts.map +1 -0
- package/dist/backends/kubernetes/k8s-client.js +246 -0
- package/dist/backends/kubernetes/k8s-client.js.map +1 -0
- package/dist/backends/kubernetes/lease.d.ts +119 -0
- package/dist/backends/kubernetes/lease.d.ts.map +1 -0
- package/dist/backends/kubernetes/lease.js +151 -0
- package/dist/backends/kubernetes/lease.js.map +1 -0
- package/dist/backends/kubernetes/objects.d.ts +282 -0
- package/dist/backends/kubernetes/objects.d.ts.map +1 -0
- package/dist/backends/kubernetes/objects.js +156 -0
- package/dist/backends/kubernetes/objects.js.map +1 -0
- package/dist/backends/kubernetes/privilege-probe.d.ts +136 -0
- package/dist/backends/kubernetes/privilege-probe.d.ts.map +1 -0
- package/dist/backends/kubernetes/privilege-probe.js +185 -0
- package/dist/backends/kubernetes/privilege-probe.js.map +1 -0
- package/dist/backends/kubernetes/sandbox.d.ts +123 -0
- package/dist/backends/kubernetes/sandbox.d.ts.map +1 -0
- package/dist/backends/kubernetes/sandbox.js +299 -0
- package/dist/backends/kubernetes/sandbox.js.map +1 -0
- package/dist/backends/kubernetes/transport.d.ts +122 -0
- package/dist/backends/kubernetes/transport.d.ts.map +1 -0
- package/dist/backends/kubernetes/transport.js +197 -0
- package/dist/backends/kubernetes/transport.js.map +1 -0
- package/dist/backends/kubernetes/workspace.d.ts +381 -0
- package/dist/backends/kubernetes/workspace.d.ts.map +1 -0
- package/dist/backends/kubernetes/workspace.js +1064 -0
- package/dist/backends/kubernetes/workspace.js.map +1 -0
- package/dist/index.d.ts +132 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +102 -34
- package/dist/index.js.map +1 -1
- package/dist/testing/sandbox-conformance.d.ts +193 -0
- package/dist/testing/sandbox-conformance.d.ts.map +1 -0
- package/dist/testing/sandbox-conformance.js +465 -0
- package/dist/testing/sandbox-conformance.js.map +1 -0
- package/package.json +5 -4
- package/src/backends/firecracker/protocol.ts +27 -0
- package/src/backends/firecracker/transport.ts +199 -28
- package/src/backends/kubernetes/egress-policy.ts +437 -0
- package/src/backends/kubernetes/index.ts +1012 -0
- package/src/backends/kubernetes/k8s-client.ts +352 -0
- package/src/backends/kubernetes/lease.ts +198 -0
- package/src/backends/kubernetes/objects.ts +363 -0
- package/src/backends/kubernetes/privilege-probe.ts +261 -0
- package/src/backends/kubernetes/sandbox.ts +395 -0
- package/src/backends/kubernetes/transport.ts +286 -0
- package/src/backends/kubernetes/workspace.ts +1386 -0
- package/src/index.ts +257 -35
- package/src/testing/sandbox-conformance.ts +667 -0
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Acquire-time privilege probe: prove the guest process really was
|
|
3
|
+
* deprivileged, rather than assume the entrypoint did its job.
|
|
4
|
+
*
|
|
5
|
+
* The image's entrypoint mounts the workspace as root and then
|
|
6
|
+
* `exec setpriv --reuid --regid --clear-groups --inh-caps=-all
|
|
7
|
+
* --bounding-set=-all --no-new-privs -- tini -- node agent.cjs` (`tini` is
|
|
8
|
+
* the container's pid 1 so it can reap an orphan `agent.cjs` itself never
|
|
9
|
+
* spawned — see `k8s/entrypoint.sh` and the Dockerfile). Nothing in
|
|
10
|
+
* `agent.cjs` knows about any of that, and nothing on the host can see it
|
|
11
|
+
* either — an image built from an older entrypoint, a `RuntimeClass` change,
|
|
12
|
+
* a hand-edited `SandboxTemplate` all produce a sandbox that works perfectly
|
|
13
|
+
* and is not deprivileged. So the backend asks the guest, once, before it
|
|
14
|
+
* hands a caller a handle.
|
|
15
|
+
*
|
|
16
|
+
* ## Why `execute`, and why not `read-file`
|
|
17
|
+
*
|
|
18
|
+
* The guest's `read-file` resolves every path against `READ_ROOTS`
|
|
19
|
+
* (`WORKSPACE_ROOT` only), so it cannot reach `/proc` at all, and widening
|
|
20
|
+
* `READ_ROOTS` to make the probe work would hand every caller of `readFile`
|
|
21
|
+
* a window into the guest's process tree for the sake of one diagnostic.
|
|
22
|
+
* `handleExecute` jails only `cwd`, so a command whose ARGUMENT is an
|
|
23
|
+
* absolute path outside the workspace runs fine. The probe therefore spends
|
|
24
|
+
* one `exec` and touches no jail.
|
|
25
|
+
*
|
|
26
|
+
* ## Why all four masks
|
|
27
|
+
*
|
|
28
|
+
* Checking `CapEff` alone is a true-looking answer: an ordinary unprivileged
|
|
29
|
+
* process shows `CapEff: 0000000000000000` whether or not its bounding set
|
|
30
|
+
* was ever dropped, so a container running as uid 0 with the full bounding
|
|
31
|
+
* set still passes. `CapBnd` is the one that says a capability can never be
|
|
32
|
+
* regained; `CapInh` and `CapPrm` close the two ways one could be carried
|
|
33
|
+
* across an exec. `NoNewPrivs: 1` is what makes a setuid binary inside the
|
|
34
|
+
* guest unable to raise any of it back.
|
|
35
|
+
*
|
|
36
|
+
* ## Why every failure is a refusal
|
|
37
|
+
*
|
|
38
|
+
* A probe that could not run, one that never answered at all, output that
|
|
39
|
+
* could not be parsed and a process that is genuinely privileged are all
|
|
40
|
+
* reasons NOT to hand back a handle, and they are separated only in the error
|
|
41
|
+
* TEXT — a distroless image with no `cat` on `PATH` must be diagnosable as
|
|
42
|
+
* exactly that rather than read as a hardening failure. There is no
|
|
43
|
+
* configuration that turns this off.
|
|
44
|
+
*
|
|
45
|
+
* ## The clock is the caller's, and it lives in `index.ts`
|
|
46
|
+
*
|
|
47
|
+
* Nothing here has a timeout: the probe is one `exec`, and the budget it may
|
|
48
|
+
* spend belongs to the `create()` that ordered it. `admitProbedSandbox` runs
|
|
49
|
+
* it under an `OperationDeadline` and turns an expiry into
|
|
50
|
+
* {@link privilegeProbeTimedOut}, so a guest that accepts the connection and
|
|
51
|
+
* then goes quiet is refused on the caller's clock rather than on the
|
|
52
|
+
* execution controller's five-minute generic default.
|
|
53
|
+
*/
|
|
54
|
+
import type { SandboxExecResult } from '@namzu/sdk';
|
|
55
|
+
/**
|
|
56
|
+
* The probe command. `cat` rather than an absolute `/bin/cat` so a guest
|
|
57
|
+
* that keeps its coreutils somewhere else still answers, and rather than a
|
|
58
|
+
* shell so there is no quoting to get wrong. A guest without it fails with
|
|
59
|
+
* a spawn error the refusal repeats verbatim.
|
|
60
|
+
*/
|
|
61
|
+
export declare const PRIVILEGE_PROBE_COMMAND = "cat";
|
|
62
|
+
/** `/proc/self/status` — of the process the guest agent spawns, which
|
|
63
|
+
* inherits exactly the agent's own credentials and capability masks. */
|
|
64
|
+
export declare const PRIVILEGE_PROBE_ARGS: readonly string[];
|
|
65
|
+
/** Why a probe refused. The text says the same thing in words. */
|
|
66
|
+
export type PrivilegeProbeFailure =
|
|
67
|
+
/** The `exec` failed, exited non-zero, or never answered at all. */
|
|
68
|
+
'probe-failed'
|
|
69
|
+
/** It ran, but its output is not a readable `/proc/<pid>/status`. */
|
|
70
|
+
| 'unreadable-output'
|
|
71
|
+
/** It ran, it parsed, and the process has capabilities it should not. */
|
|
72
|
+
| 'privileged';
|
|
73
|
+
/**
|
|
74
|
+
* Raised by {@link runPrivilegeProbe} and {@link parseProcStatus}. The
|
|
75
|
+
* `reason` is the machine-readable form of the distinction the message
|
|
76
|
+
* draws in prose: `'privileged'` means the guest is under-hardened, and the
|
|
77
|
+
* other two mean the backend could not tell.
|
|
78
|
+
*/
|
|
79
|
+
export declare class KubernetesPrivilegeProbeError extends Error {
|
|
80
|
+
readonly reason: PrivilegeProbeFailure;
|
|
81
|
+
readonly name = "KubernetesPrivilegeProbeError";
|
|
82
|
+
constructor(reason: PrivilegeProbeFailure, message: string, options?: ErrorOptions);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* The five fields the probe reads, parsed. The four capability masks are
|
|
86
|
+
* `bigint` because a capability mask is 64 bits wide and `Number` stops
|
|
87
|
+
* being exact at 53 — `000001ffffffffff` is only 41 bits today, but a mask
|
|
88
|
+
* that silently rounds is precisely the bug this whole module exists to
|
|
89
|
+
* catch.
|
|
90
|
+
*/
|
|
91
|
+
export interface ProcStatusPrivileges {
|
|
92
|
+
readonly capInh: bigint;
|
|
93
|
+
readonly capPrm: bigint;
|
|
94
|
+
readonly capEff: bigint;
|
|
95
|
+
readonly capBnd: bigint;
|
|
96
|
+
/** `prctl(PR_GET_NO_NEW_PRIVS)`, 0 or 1 as the kernel prints it. */
|
|
97
|
+
readonly noNewPrivs: number;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Parse `/proc/<pid>/status` into the five fields that decide admission.
|
|
101
|
+
*
|
|
102
|
+
* Pure: no transport, no clock, no I/O. Everything it cannot read is a
|
|
103
|
+
* throw, never a default — a zero substituted for a missing mask is the one
|
|
104
|
+
* mistake that would make this function report hardening that is not there.
|
|
105
|
+
*/
|
|
106
|
+
export declare function parseProcStatus(text: string): ProcStatusPrivileges;
|
|
107
|
+
/**
|
|
108
|
+
* Admit only an all-zero capability set with `no_new_privs` set. Every
|
|
109
|
+
* non-zero mask is named in the refusal, because "one of them is set" sends
|
|
110
|
+
* the reader back to the guest to find out which.
|
|
111
|
+
*/
|
|
112
|
+
export declare function assertDeprivileged(privileges: ProcStatusPrivileges, sandboxName: string): void;
|
|
113
|
+
/**
|
|
114
|
+
* Run the probe over an already-built sandbox's `exec` and admit or refuse.
|
|
115
|
+
*
|
|
116
|
+
* `run` is the sandbox's own `exec`, not the raw transport, so the probe
|
|
117
|
+
* traverses exactly the path every later call will: reserve, admit, stream,
|
|
118
|
+
* confirm. A probe that cannot get through this is a sandbox a caller
|
|
119
|
+
* cannot use either.
|
|
120
|
+
*/
|
|
121
|
+
export declare function runPrivilegeProbe(run: (command: string, args: string[]) => Promise<SandboxExecResult>, sandboxName: string): Promise<ProcStatusPrivileges>;
|
|
122
|
+
/**
|
|
123
|
+
* The refusal for a probe that never answered.
|
|
124
|
+
*
|
|
125
|
+
* A guest that accepts the TCP connection and then goes quiet — an agent
|
|
126
|
+
* process out of memory, an event loop blocked by the workload, a container
|
|
127
|
+
* alive with a listener that has stopped reading — cannot be told apart from
|
|
128
|
+
* a healthy one by the wire alone, so the caller's acquire budget is the only
|
|
129
|
+
* thing that ends the wait. That expiry is a `'probe-failed'` like any other
|
|
130
|
+
* way the probe could not run, but it gets its own words: "the deadline
|
|
131
|
+
* expired" on its own says nothing about WHICH half of the acquire stopped
|
|
132
|
+
* answering, and a reader who sees `cat` blamed for a hang goes looking for a
|
|
133
|
+
* missing binary that is not missing.
|
|
134
|
+
*/
|
|
135
|
+
export declare function privilegeProbeTimedOut(sandboxName: string, timeoutMs: number, cause: unknown): KubernetesPrivilegeProbeError;
|
|
136
|
+
//# sourceMappingURL=privilege-probe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"privilege-probe.d.ts","sourceRoot":"","sources":["../../../src/backends/kubernetes/privilege-probe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoDG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAEnD;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,QAAQ,CAAA;AAE5C;wEACwE;AACxE,eAAO,MAAM,oBAAoB,EAAE,SAAS,MAAM,EAA0B,CAAA;AAE5E,kEAAkE;AAClE,MAAM,MAAM,qBAAqB;AAChC,oEAAoE;AAClE,cAAc;AAChB,qEAAqE;GACnE,mBAAmB;AACrB,yEAAyE;GACvE,YAAY,CAAA;AAEf;;;;;GAKG;AACH,qBAAa,6BAA8B,SAAQ,KAAK;IAItD,QAAQ,CAAC,MAAM,EAAE,qBAAqB;IAHvC,SAAkB,IAAI,mCAAkC;gBAG9C,MAAM,EAAE,qBAAqB,EACtC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,YAAY;CAIvB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,oEAAoE;IACpE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAC3B;AAuBD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,CA8BlE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAe9F;AAED;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CACtC,GAAG,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,iBAAiB,CAAC,EACpE,WAAW,EAAE,MAAM,GACjB,OAAO,CAAC,oBAAoB,CAAC,CA6B/B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CACrC,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,OAAO,GACZ,6BAA6B,CAQ/B"}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Acquire-time privilege probe: prove the guest process really was
|
|
3
|
+
* deprivileged, rather than assume the entrypoint did its job.
|
|
4
|
+
*
|
|
5
|
+
* The image's entrypoint mounts the workspace as root and then
|
|
6
|
+
* `exec setpriv --reuid --regid --clear-groups --inh-caps=-all
|
|
7
|
+
* --bounding-set=-all --no-new-privs -- tini -- node agent.cjs` (`tini` is
|
|
8
|
+
* the container's pid 1 so it can reap an orphan `agent.cjs` itself never
|
|
9
|
+
* spawned — see `k8s/entrypoint.sh` and the Dockerfile). Nothing in
|
|
10
|
+
* `agent.cjs` knows about any of that, and nothing on the host can see it
|
|
11
|
+
* either — an image built from an older entrypoint, a `RuntimeClass` change,
|
|
12
|
+
* a hand-edited `SandboxTemplate` all produce a sandbox that works perfectly
|
|
13
|
+
* and is not deprivileged. So the backend asks the guest, once, before it
|
|
14
|
+
* hands a caller a handle.
|
|
15
|
+
*
|
|
16
|
+
* ## Why `execute`, and why not `read-file`
|
|
17
|
+
*
|
|
18
|
+
* The guest's `read-file` resolves every path against `READ_ROOTS`
|
|
19
|
+
* (`WORKSPACE_ROOT` only), so it cannot reach `/proc` at all, and widening
|
|
20
|
+
* `READ_ROOTS` to make the probe work would hand every caller of `readFile`
|
|
21
|
+
* a window into the guest's process tree for the sake of one diagnostic.
|
|
22
|
+
* `handleExecute` jails only `cwd`, so a command whose ARGUMENT is an
|
|
23
|
+
* absolute path outside the workspace runs fine. The probe therefore spends
|
|
24
|
+
* one `exec` and touches no jail.
|
|
25
|
+
*
|
|
26
|
+
* ## Why all four masks
|
|
27
|
+
*
|
|
28
|
+
* Checking `CapEff` alone is a true-looking answer: an ordinary unprivileged
|
|
29
|
+
* process shows `CapEff: 0000000000000000` whether or not its bounding set
|
|
30
|
+
* was ever dropped, so a container running as uid 0 with the full bounding
|
|
31
|
+
* set still passes. `CapBnd` is the one that says a capability can never be
|
|
32
|
+
* regained; `CapInh` and `CapPrm` close the two ways one could be carried
|
|
33
|
+
* across an exec. `NoNewPrivs: 1` is what makes a setuid binary inside the
|
|
34
|
+
* guest unable to raise any of it back.
|
|
35
|
+
*
|
|
36
|
+
* ## Why every failure is a refusal
|
|
37
|
+
*
|
|
38
|
+
* A probe that could not run, one that never answered at all, output that
|
|
39
|
+
* could not be parsed and a process that is genuinely privileged are all
|
|
40
|
+
* reasons NOT to hand back a handle, and they are separated only in the error
|
|
41
|
+
* TEXT — a distroless image with no `cat` on `PATH` must be diagnosable as
|
|
42
|
+
* exactly that rather than read as a hardening failure. There is no
|
|
43
|
+
* configuration that turns this off.
|
|
44
|
+
*
|
|
45
|
+
* ## The clock is the caller's, and it lives in `index.ts`
|
|
46
|
+
*
|
|
47
|
+
* Nothing here has a timeout: the probe is one `exec`, and the budget it may
|
|
48
|
+
* spend belongs to the `create()` that ordered it. `admitProbedSandbox` runs
|
|
49
|
+
* it under an `OperationDeadline` and turns an expiry into
|
|
50
|
+
* {@link privilegeProbeTimedOut}, so a guest that accepts the connection and
|
|
51
|
+
* then goes quiet is refused on the caller's clock rather than on the
|
|
52
|
+
* execution controller's five-minute generic default.
|
|
53
|
+
*/
|
|
54
|
+
/**
|
|
55
|
+
* The probe command. `cat` rather than an absolute `/bin/cat` so a guest
|
|
56
|
+
* that keeps its coreutils somewhere else still answers, and rather than a
|
|
57
|
+
* shell so there is no quoting to get wrong. A guest without it fails with
|
|
58
|
+
* a spawn error the refusal repeats verbatim.
|
|
59
|
+
*/
|
|
60
|
+
export const PRIVILEGE_PROBE_COMMAND = 'cat';
|
|
61
|
+
/** `/proc/self/status` — of the process the guest agent spawns, which
|
|
62
|
+
* inherits exactly the agent's own credentials and capability masks. */
|
|
63
|
+
export const PRIVILEGE_PROBE_ARGS = ['/proc/self/status'];
|
|
64
|
+
/**
|
|
65
|
+
* Raised by {@link runPrivilegeProbe} and {@link parseProcStatus}. The
|
|
66
|
+
* `reason` is the machine-readable form of the distinction the message
|
|
67
|
+
* draws in prose: `'privileged'` means the guest is under-hardened, and the
|
|
68
|
+
* other two mean the backend could not tell.
|
|
69
|
+
*/
|
|
70
|
+
export class KubernetesPrivilegeProbeError extends Error {
|
|
71
|
+
reason;
|
|
72
|
+
name = 'KubernetesPrivilegeProbeError';
|
|
73
|
+
constructor(reason, message, options) {
|
|
74
|
+
super(message, options);
|
|
75
|
+
this.reason = reason;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const CAPABILITY_FIELDS = ['CapInh', 'CapPrm', 'CapEff', 'CapBnd'];
|
|
79
|
+
const NO_NEW_PRIVS_FIELD = 'NoNewPrivs';
|
|
80
|
+
/** Clip a value before it goes into an error message. */
|
|
81
|
+
function clip(value) {
|
|
82
|
+
return value.length > 40 ? `${value.slice(0, 40)}…` : value;
|
|
83
|
+
}
|
|
84
|
+
function readField(text, field) {
|
|
85
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
86
|
+
const colon = rawLine.indexOf(':');
|
|
87
|
+
if (colon < 0)
|
|
88
|
+
continue;
|
|
89
|
+
if (rawLine.slice(0, colon).trim() !== field)
|
|
90
|
+
continue;
|
|
91
|
+
return rawLine.slice(colon + 1).trim();
|
|
92
|
+
}
|
|
93
|
+
throw new KubernetesPrivilegeProbeError('unreadable-output', `the privilege probe ran but its output carries no ${field} line, so this sandbox's privileges could not be read. The probe is \`${PRIVILEGE_PROBE_COMMAND} ${PRIVILEGE_PROBE_ARGS.join(' ')}\` — a guest whose /proc is not mounted, or whose kernel does not publish ${field}, cannot be admitted, because an unreadable answer is not a safe one.`);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Parse `/proc/<pid>/status` into the five fields that decide admission.
|
|
97
|
+
*
|
|
98
|
+
* Pure: no transport, no clock, no I/O. Everything it cannot read is a
|
|
99
|
+
* throw, never a default — a zero substituted for a missing mask is the one
|
|
100
|
+
* mistake that would make this function report hardening that is not there.
|
|
101
|
+
*/
|
|
102
|
+
export function parseProcStatus(text) {
|
|
103
|
+
const masks = CAPABILITY_FIELDS.map((field) => {
|
|
104
|
+
const raw = readField(text, field);
|
|
105
|
+
// The kernel prints a bare, fixed-width hex mask with no `0x`. A
|
|
106
|
+
// `0x` prefix, a sign, whitespace inside, or anything non-hex means
|
|
107
|
+
// this is not the file this parser thinks it is.
|
|
108
|
+
if (!/^[0-9a-fA-F]+$/.test(raw)) {
|
|
109
|
+
throw new KubernetesPrivilegeProbeError('unreadable-output', `the privilege probe ran but ${field} is ${JSON.stringify(clip(raw))}, which is not the bare hexadecimal capability mask /proc/<pid>/status publishes, so this sandbox's privileges could not be read.`);
|
|
110
|
+
}
|
|
111
|
+
return BigInt(`0x${raw}`);
|
|
112
|
+
});
|
|
113
|
+
const noNewPrivsRaw = readField(text, NO_NEW_PRIVS_FIELD);
|
|
114
|
+
if (!/^\d+$/.test(noNewPrivsRaw)) {
|
|
115
|
+
throw new KubernetesPrivilegeProbeError('unreadable-output', `the privilege probe ran but ${NO_NEW_PRIVS_FIELD} is ${JSON.stringify(clip(noNewPrivsRaw))}, which is not the integer /proc/<pid>/status publishes, so this sandbox's privileges could not be read.`);
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
capInh: masks[0],
|
|
119
|
+
capPrm: masks[1],
|
|
120
|
+
capEff: masks[2],
|
|
121
|
+
capBnd: masks[3],
|
|
122
|
+
noNewPrivs: Number(noNewPrivsRaw),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Admit only an all-zero capability set with `no_new_privs` set. Every
|
|
127
|
+
* non-zero mask is named in the refusal, because "one of them is set" sends
|
|
128
|
+
* the reader back to the guest to find out which.
|
|
129
|
+
*/
|
|
130
|
+
export function assertDeprivileged(privileges, sandboxName) {
|
|
131
|
+
const offenders = [];
|
|
132
|
+
if (privileges.capInh !== 0n)
|
|
133
|
+
offenders.push(`CapInh=${privileges.capInh.toString(16)}`);
|
|
134
|
+
if (privileges.capPrm !== 0n)
|
|
135
|
+
offenders.push(`CapPrm=${privileges.capPrm.toString(16)}`);
|
|
136
|
+
if (privileges.capEff !== 0n)
|
|
137
|
+
offenders.push(`CapEff=${privileges.capEff.toString(16)}`);
|
|
138
|
+
if (privileges.capBnd !== 0n)
|
|
139
|
+
offenders.push(`CapBnd=${privileges.capBnd.toString(16)}`);
|
|
140
|
+
if (privileges.noNewPrivs !== 1)
|
|
141
|
+
offenders.push(`NoNewPrivs=${privileges.noNewPrivs}`);
|
|
142
|
+
if (offenders.length === 0)
|
|
143
|
+
return;
|
|
144
|
+
throw new KubernetesPrivilegeProbeError('privileged', `kubernetes sandbox ${sandboxName} is PRIVILEGED and was refused: ${offenders.join(', ')} (every capability mask must be 0 and NoNewPrivs must be 1). The probe ran and was read successfully — this is the guest's real state, not a diagnostic failure. The image's entrypoint is expected to end with \`exec setpriv --reuid --regid --clear-groups --inh-caps=-all --bounding-set=-all --no-new-privs -- tini -- node agent.cjs\`; a sandbox that reaches this message is running with capabilities the workload could use.`);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Run the probe over an already-built sandbox's `exec` and admit or refuse.
|
|
148
|
+
*
|
|
149
|
+
* `run` is the sandbox's own `exec`, not the raw transport, so the probe
|
|
150
|
+
* traverses exactly the path every later call will: reserve, admit, stream,
|
|
151
|
+
* confirm. A probe that cannot get through this is a sandbox a caller
|
|
152
|
+
* cannot use either.
|
|
153
|
+
*/
|
|
154
|
+
export async function runPrivilegeProbe(run, sandboxName) {
|
|
155
|
+
let result;
|
|
156
|
+
try {
|
|
157
|
+
result = await run(PRIVILEGE_PROBE_COMMAND, [...PRIVILEGE_PROBE_ARGS]);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
throw new KubernetesPrivilegeProbeError('probe-failed', `the privilege probe could not run in kubernetes sandbox ${sandboxName}: ${error instanceof Error ? error.message : String(error)}. The probe is \`${PRIVILEGE_PROBE_COMMAND} ${PRIVILEGE_PROBE_ARGS.join(' ')}\`; an image without it on PATH cannot be admitted, because a sandbox whose privileges cannot be checked is refused rather than trusted.`, { cause: error });
|
|
161
|
+
}
|
|
162
|
+
if (result.exitCode !== 0) {
|
|
163
|
+
throw new KubernetesPrivilegeProbeError('probe-failed', `the privilege probe could not run in kubernetes sandbox ${sandboxName}: \`${PRIVILEGE_PROBE_COMMAND} ${PRIVILEGE_PROBE_ARGS.join(' ')}\` exited ${result.exitCode}${result.stderr.trim() ? ` (${clip(result.stderr.trim())})` : ''}. This is a diagnostic failure, not a privilege failure — the sandbox is refused because its state is unknown.`);
|
|
164
|
+
}
|
|
165
|
+
const privileges = parseProcStatus(result.stdout);
|
|
166
|
+
assertDeprivileged(privileges, sandboxName);
|
|
167
|
+
return privileges;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* The refusal for a probe that never answered.
|
|
171
|
+
*
|
|
172
|
+
* A guest that accepts the TCP connection and then goes quiet — an agent
|
|
173
|
+
* process out of memory, an event loop blocked by the workload, a container
|
|
174
|
+
* alive with a listener that has stopped reading — cannot be told apart from
|
|
175
|
+
* a healthy one by the wire alone, so the caller's acquire budget is the only
|
|
176
|
+
* thing that ends the wait. That expiry is a `'probe-failed'` like any other
|
|
177
|
+
* way the probe could not run, but it gets its own words: "the deadline
|
|
178
|
+
* expired" on its own says nothing about WHICH half of the acquire stopped
|
|
179
|
+
* answering, and a reader who sees `cat` blamed for a hang goes looking for a
|
|
180
|
+
* missing binary that is not missing.
|
|
181
|
+
*/
|
|
182
|
+
export function privilegeProbeTimedOut(sandboxName, timeoutMs, cause) {
|
|
183
|
+
return new KubernetesPrivilegeProbeError('probe-failed', `the privilege probe could not run in kubernetes sandbox ${sandboxName}: the guest accepted the connection and did not answer \`${PRIVILEGE_PROBE_COMMAND} ${PRIVILEGE_PROBE_ARGS.join(' ')}\` within ${timeoutMs} ms, so the probe was abandoned. This is a diagnostic failure, not a privilege failure — the sandbox is refused because its state is unknown. A wedged agent (out of memory, an event loop blocked by the workload) looks exactly like this from the host; check the pod's logs before raising the acquire budget.`, { cause });
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=privilege-probe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"privilege-probe.js","sourceRoot":"","sources":["../../../src/backends/kubernetes/privilege-probe.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The {@link Sandbox} a kubernetes acquire hands back: the SDK contract,
|
|
3
|
+
* served over the guest agent's TCP transport, with the lease that keeps the
|
|
4
|
+
* cluster from deleting the pod out from under a long run.
|
|
5
|
+
*
|
|
6
|
+
* Split out of `index.ts` because that file is about the CONTROL plane —
|
|
7
|
+
* claim, poll, address, release — and this one is about the DATA plane, and
|
|
8
|
+
* the two are read for different reasons.
|
|
9
|
+
*
|
|
10
|
+
* ## What it implements, and what it deliberately does not
|
|
11
|
+
*
|
|
12
|
+
* Implemented: `exec` (through the shared {@link RemoteExecutionController},
|
|
13
|
+
* so an `AbortSignal` terminates the guest process rather than abandoning
|
|
14
|
+
* the wait), `writeFile`, `readFile`, `listFiles`, `openTerminal`,
|
|
15
|
+
* `openTcpConnection`, `destroy`.
|
|
16
|
+
*
|
|
17
|
+
* Absent on purpose, because the SDK's contract says a backend that cannot
|
|
18
|
+
* honour an optional method must omit it rather than accept and ignore:
|
|
19
|
+
*
|
|
20
|
+
* - `setNetworkPolicy` — egress here is a `NetworkPolicy` attached to the
|
|
21
|
+
* pool's `SandboxTemplate`. There is no per-running-pod knob to turn, and
|
|
22
|
+
* a policy accepted and not applied is worse than one never offered: the
|
|
23
|
+
* caller stops looking.
|
|
24
|
+
* - `spawnDetached` — the guest agent has no op that starts a process and
|
|
25
|
+
* returns it running. A host asking for background jobs must be told no.
|
|
26
|
+
* - `walkFiles` — not in this batch. A host that requires bounded search
|
|
27
|
+
* refuses an absent method, which is the honest answer today.
|
|
28
|
+
*
|
|
29
|
+
* ## Terminals are owned
|
|
30
|
+
*
|
|
31
|
+
* `openTerminal` is only a compliant implementation if `destroy()` kills and
|
|
32
|
+
* awaits every terminal it returned, so open terminals are tracked and
|
|
33
|
+
* reaped before the object is released — the same thing the Firecracker
|
|
34
|
+
* backend does, for the same contract.
|
|
35
|
+
*
|
|
36
|
+
* ## Two terminal states, one `SandboxStatus`
|
|
37
|
+
*
|
|
38
|
+
* `SandboxStatus` has exactly four members and this change does not widen
|
|
39
|
+
* the SDK's union, so both ways a sandbox ends report `'destroyed'`. They
|
|
40
|
+
* are told apart by the error a later call throws:
|
|
41
|
+
* {@link KubernetesSandboxDestroyedError} (this host released it) and
|
|
42
|
+
* {@link KubernetesSandboxGoneError} (the cluster deleted it — the lease
|
|
43
|
+
* renewal found the object already gone).
|
|
44
|
+
*/
|
|
45
|
+
import type { Sandbox } from '@namzu/sdk';
|
|
46
|
+
import type { KubernetesAgentTransport } from './transport.js';
|
|
47
|
+
/** Thrown by any operation on a sandbox this host already destroyed. */
|
|
48
|
+
export declare class KubernetesSandboxDestroyedError extends Error {
|
|
49
|
+
readonly operation: string;
|
|
50
|
+
readonly sandboxName: string;
|
|
51
|
+
readonly name = "KubernetesSandboxDestroyedError";
|
|
52
|
+
constructor(operation: string, sandboxName: string);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Thrown by any operation on a sandbox the CLUSTER removed while this
|
|
56
|
+
* handle still held it — the lease renewal PATCH came back 404/410. Distinct
|
|
57
|
+
* from {@link KubernetesSandboxDestroyedError} because nothing this host did
|
|
58
|
+
* caused it: the object expired, an operator deleted it, or the controller
|
|
59
|
+
* reaped it, and the actionable advice is different.
|
|
60
|
+
*/
|
|
61
|
+
export declare class KubernetesSandboxGoneError extends Error {
|
|
62
|
+
readonly operation: string;
|
|
63
|
+
readonly sandboxName: string;
|
|
64
|
+
readonly name = "KubernetesSandboxGoneError";
|
|
65
|
+
constructor(operation: string, sandboxName: string);
|
|
66
|
+
}
|
|
67
|
+
interface KubernetesSandboxBaseOptions {
|
|
68
|
+
/** The cluster's own name for the bound sandbox — also the sandbox id. */
|
|
69
|
+
readonly name: string;
|
|
70
|
+
readonly rootDir: string;
|
|
71
|
+
readonly transport: KubernetesAgentTransport;
|
|
72
|
+
/** DELETE the object this backend created. Already-gone counts as done. */
|
|
73
|
+
readonly release: (signal?: AbortSignal) => Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* The lease half of the options: a way to move the expiry, and the expiry it
|
|
77
|
+
* is moving. Required TOGETHER, because `renew` without `ttlSeconds` is a
|
|
78
|
+
* renewal loop with nothing to stamp — it would re-stamp `now + 0`, an
|
|
79
|
+
* expiry already in the past, and hand the object straight to the
|
|
80
|
+
* controller's reaper while reporting every tick a success. A pair is the
|
|
81
|
+
* only shape that cannot be half-configured.
|
|
82
|
+
*/
|
|
83
|
+
interface KubernetesSandboxLeaseOptions {
|
|
84
|
+
/** PATCH the object's `shutdownTime` forward. See `lease.ts`. */
|
|
85
|
+
readonly renew: (shutdownTime: string, signal?: AbortSignal) => Promise<void>;
|
|
86
|
+
/** The TTL acquire stamped; each renewal re-stamps exactly this much. */
|
|
87
|
+
readonly ttlSeconds: number;
|
|
88
|
+
readonly onRenewalError?: (error: unknown) => void;
|
|
89
|
+
/** Test seam: the renewal loop's base interval. Default: half the TTL. */
|
|
90
|
+
readonly leaseIntervalMs?: number;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The other arm: an object that carries no expiry, so this handle runs no
|
|
94
|
+
* renewal loop at all — the persistent workspace (`workspace.ts`), which is
|
|
95
|
+
* explicitly managed and must outlive a host that stopped renewing. A no-op
|
|
96
|
+
* `renew` would be the wrong way to say that: it would leave a timer ticking
|
|
97
|
+
* forever to do nothing. The lease fields are typed `undefined` rather than
|
|
98
|
+
* omitted so that passing one of them here is a type error and not an
|
|
99
|
+
* excess-property check a spread would slip past.
|
|
100
|
+
*/
|
|
101
|
+
interface KubernetesSandboxUnleasedOptions {
|
|
102
|
+
readonly renew?: undefined;
|
|
103
|
+
readonly ttlSeconds?: undefined;
|
|
104
|
+
readonly onRenewalError?: undefined;
|
|
105
|
+
readonly leaseIntervalMs?: undefined;
|
|
106
|
+
}
|
|
107
|
+
export type KubernetesSandboxOptions = KubernetesSandboxBaseOptions & (KubernetesSandboxLeaseOptions | KubernetesSandboxUnleasedOptions);
|
|
108
|
+
/**
|
|
109
|
+
* What this backend hands back: the SDK contract, with the two optional
|
|
110
|
+
* members it DOES implement narrowed to present, so a caller that composes
|
|
111
|
+
* one — `workspace.ts` wraps this handle — does not have to re-check for a
|
|
112
|
+
* method this file always defines.
|
|
113
|
+
*/
|
|
114
|
+
export type KubernetesSandboxHandle = Sandbox & Required<Pick<Sandbox, 'openTerminal' | 'openTcpConnection'>>;
|
|
115
|
+
/**
|
|
116
|
+
* Build the handle. It does NOT run the acquire-time privilege probe — that
|
|
117
|
+
* is `create()`'s job in `index.ts`, so that a refusal can destroy this
|
|
118
|
+
* object before any caller has a reference to it, and so this function stays
|
|
119
|
+
* usable by the workspace path that runs its own probe.
|
|
120
|
+
*/
|
|
121
|
+
export declare function buildKubernetesSandbox(options: KubernetesSandboxOptions): KubernetesSandboxHandle;
|
|
122
|
+
export {};
|
|
123
|
+
//# sourceMappingURL=sandbox.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../../src/backends/kubernetes/sandbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,KAAK,EAEX,OAAO,EAWP,MAAM,YAAY,CAAA;AAQnB,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAA;AAW9D,wEAAwE;AACxE,qBAAa,+BAAgC,SAAQ,KAAK;IAIxD,QAAQ,CAAC,SAAS,EAAE,MAAM;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM;IAJ7B,SAAkB,IAAI,qCAAoC;gBAGhD,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM;CAM7B;AAED;;;;;;GAMG;AACH,qBAAa,0BAA2B,SAAQ,KAAK;IAInD,QAAQ,CAAC,SAAS,EAAE,MAAM;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM;IAJ7B,SAAkB,IAAI,gCAA+B;gBAG3C,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM;CAM7B;AAED,UAAU,4BAA4B;IACrC,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,SAAS,EAAE,wBAAwB,CAAA;IAC5C,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CACzD;AAED;;;;;;;GAOG;AACH,UAAU,6BAA6B;IACtC,iEAAiE;IACjE,QAAQ,CAAC,KAAK,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7E,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IAClD,0EAA0E;IAC1E,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CACjC;AAED;;;;;;;;GAQG;AACH,UAAU,gCAAgC;IACzC,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,CAAA;IAC1B,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,CAAA;IAC/B,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,CAAA;IACnC,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,CAAA;CACpC;AAED,MAAM,MAAM,wBAAwB,GAAG,4BAA4B,GAClE,CAAC,6BAA6B,GAAG,gCAAgC,CAAC,CAAA;AASnE;;;;;GAKG;AACH,MAAM,MAAM,uBAAuB,GAAG,OAAO,GAC5C,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,GAAG,mBAAmB,CAAC,CAAC,CAAA;AAE9D;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,wBAAwB,GAAG,uBAAuB,CAuNjG"}
|