@namzu/sandbox 14.0.0 → 16.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.
Files changed (100) hide show
  1. package/CHANGELOG.md +924 -0
  2. package/README.md +369 -14
  3. package/dist/backends/aci-standby-pool/index.d.ts.map +1 -1
  4. package/dist/backends/aci-standby-pool/index.js +13 -1
  5. package/dist/backends/aci-standby-pool/index.js.map +1 -1
  6. package/dist/backends/docker/index.d.ts +169 -6
  7. package/dist/backends/docker/index.d.ts.map +1 -1
  8. package/dist/backends/docker/index.js +499 -85
  9. package/dist/backends/docker/index.js.map +1 -1
  10. package/dist/backends/firecracker/index.d.ts.map +1 -1
  11. package/dist/backends/firecracker/index.js +12 -2
  12. package/dist/backends/firecracker/index.js.map +1 -1
  13. package/dist/backends/firecracker/protocol.d.ts +459 -8
  14. package/dist/backends/firecracker/protocol.d.ts.map +1 -1
  15. package/dist/backends/firecracker/protocol.js +136 -0
  16. package/dist/backends/firecracker/protocol.js.map +1 -1
  17. package/dist/backends/firecracker/transport.d.ts +539 -6
  18. package/dist/backends/firecracker/transport.d.ts.map +1 -1
  19. package/dist/backends/firecracker/transport.js +1171 -24
  20. package/dist/backends/firecracker/transport.js.map +1 -1
  21. package/dist/backends/kubernetes/egress-policy.d.ts +1181 -13
  22. package/dist/backends/kubernetes/egress-policy.d.ts.map +1 -1
  23. package/dist/backends/kubernetes/egress-policy.js +2350 -31
  24. package/dist/backends/kubernetes/egress-policy.js.map +1 -1
  25. package/dist/backends/kubernetes/identity.d.ts +193 -0
  26. package/dist/backends/kubernetes/identity.d.ts.map +1 -0
  27. package/dist/backends/kubernetes/identity.js +147 -0
  28. package/dist/backends/kubernetes/identity.js.map +1 -0
  29. package/dist/backends/kubernetes/index.d.ts +678 -33
  30. package/dist/backends/kubernetes/index.d.ts.map +1 -1
  31. package/dist/backends/kubernetes/index.js +1180 -95
  32. package/dist/backends/kubernetes/index.js.map +1 -1
  33. package/dist/backends/kubernetes/ingress-policy.d.ts +375 -0
  34. package/dist/backends/kubernetes/ingress-policy.d.ts.map +1 -0
  35. package/dist/backends/kubernetes/ingress-policy.js +1050 -0
  36. package/dist/backends/kubernetes/ingress-policy.js.map +1 -0
  37. package/dist/backends/kubernetes/k8s-client.d.ts +213 -4
  38. package/dist/backends/kubernetes/k8s-client.d.ts.map +1 -1
  39. package/dist/backends/kubernetes/k8s-client.js +359 -52
  40. package/dist/backends/kubernetes/k8s-client.js.map +1 -1
  41. package/dist/backends/kubernetes/lease.d.ts +40 -14
  42. package/dist/backends/kubernetes/lease.d.ts.map +1 -1
  43. package/dist/backends/kubernetes/lease.js +68 -18
  44. package/dist/backends/kubernetes/lease.js.map +1 -1
  45. package/dist/backends/kubernetes/objects.d.ts +423 -3
  46. package/dist/backends/kubernetes/objects.d.ts.map +1 -1
  47. package/dist/backends/kubernetes/objects.js +364 -2
  48. package/dist/backends/kubernetes/objects.js.map +1 -1
  49. package/dist/backends/kubernetes/per-sandbox-policy.d.ts +219 -0
  50. package/dist/backends/kubernetes/per-sandbox-policy.d.ts.map +1 -0
  51. package/dist/backends/kubernetes/per-sandbox-policy.js +375 -0
  52. package/dist/backends/kubernetes/per-sandbox-policy.js.map +1 -0
  53. package/dist/backends/kubernetes/rbac.d.ts +153 -0
  54. package/dist/backends/kubernetes/rbac.d.ts.map +1 -0
  55. package/dist/backends/kubernetes/rbac.js +177 -0
  56. package/dist/backends/kubernetes/rbac.js.map +1 -0
  57. package/dist/backends/kubernetes/sandbox.d.ts +81 -14
  58. package/dist/backends/kubernetes/sandbox.d.ts.map +1 -1
  59. package/dist/backends/kubernetes/sandbox.js +149 -15
  60. package/dist/backends/kubernetes/sandbox.js.map +1 -1
  61. package/dist/backends/kubernetes/transport.d.ts +935 -9
  62. package/dist/backends/kubernetes/transport.d.ts.map +1 -1
  63. package/dist/backends/kubernetes/transport.js +1958 -62
  64. package/dist/backends/kubernetes/transport.js.map +1 -1
  65. package/dist/backends/kubernetes/workspace.d.ts +1149 -18
  66. package/dist/backends/kubernetes/workspace.d.ts.map +1 -1
  67. package/dist/backends/kubernetes/workspace.js +2825 -186
  68. package/dist/backends/kubernetes/workspace.js.map +1 -1
  69. package/dist/backends/remote-execution-controller.d.ts +14 -0
  70. package/dist/backends/remote-execution-controller.d.ts.map +1 -1
  71. package/dist/backends/remote-execution-controller.js.map +1 -1
  72. package/dist/index.d.ts +294 -18
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +280 -10
  75. package/dist/index.js.map +1 -1
  76. package/dist/testing/sandbox-conformance.d.ts +39 -5
  77. package/dist/testing/sandbox-conformance.d.ts.map +1 -1
  78. package/dist/testing/sandbox-conformance.js +436 -5
  79. package/dist/testing/sandbox-conformance.js.map +1 -1
  80. package/package.json +3 -3
  81. package/src/backends/aci-standby-pool/index.ts +16 -1
  82. package/src/backends/docker/index.ts +617 -100
  83. package/src/backends/firecracker/index.ts +14 -2
  84. package/src/backends/firecracker/protocol.ts +514 -6
  85. package/src/backends/firecracker/transport.ts +1492 -40
  86. package/src/backends/kubernetes/egress-policy.ts +3334 -55
  87. package/src/backends/kubernetes/identity.ts +261 -0
  88. package/src/backends/kubernetes/index.ts +1785 -127
  89. package/src/backends/kubernetes/ingress-policy.ts +1344 -0
  90. package/src/backends/kubernetes/k8s-client.ts +444 -54
  91. package/src/backends/kubernetes/lease.ts +75 -19
  92. package/src/backends/kubernetes/objects.ts +626 -6
  93. package/src/backends/kubernetes/per-sandbox-policy.ts +497 -0
  94. package/src/backends/kubernetes/rbac.ts +192 -0
  95. package/src/backends/kubernetes/sandbox.ts +218 -20
  96. package/src/backends/kubernetes/transport.ts +2733 -124
  97. package/src/backends/kubernetes/workspace.ts +4476 -222
  98. package/src/backends/remote-execution-controller.ts +14 -0
  99. package/src/index.ts +668 -19
  100. package/src/testing/sandbox-conformance.ts +540 -5
@@ -0,0 +1,219 @@
1
+ /**
2
+ * Per-sandbox egress: one policy object per LIVE sandbox, written by this
3
+ * host while the sandbox runs, so `Sandbox.setNetworkPolicy` means something
4
+ * here instead of being omitted.
5
+ *
6
+ * ## Why this is a separate file from `egress-policy.ts`
7
+ *
8
+ * That one is about the boundary an OPERATOR applies and this backend only
9
+ * ever reads: one object per backend, translated from config, verified and
10
+ * never written. This one is about an object this backend CREATES, replaces
11
+ * and lets the cluster collect — a different lifecycle, a different RBAC
12
+ * grant, and the only place in `@namzu/sandbox` that writes a policy at all.
13
+ * They share the manifest builder and the read-back comparator, which is the
14
+ * point: two spellings of "what a namzu egress policy looks like" would be
15
+ * one comparing itself against the other's shape.
16
+ *
17
+ * ## The shape
18
+ *
19
+ * - **Name** `namzu-sbx-<uid>`, where the uid is the object the acquire
20
+ * created for this sandbox — the `SandboxClaim` on the pooled path, the
21
+ * `Sandbox` on the pool-less one. Unique per sandbox by construction, and
22
+ * derivable from the owner reference alone, which is what lets the
23
+ * admission policy check the two against each other.
24
+ * - **Selector** one per-sandbox pod label, composed by
25
+ * `composeAdditionalPodLabels` — the ONE composer — and carried onto the
26
+ * pod as claim-time metadata before the pod exists. Not patched onto a
27
+ * running pod: this backend holds no write verb on pods, and a label added
28
+ * after the fact would leave a window in which the policy selected nothing.
29
+ * - **Owner** an `ownerReferences` entry naming that same object, so
30
+ * `destroy()` deletes the claim and the cluster's garbage collector
31
+ * removes the policy. This backend issues no deletion of its own on
32
+ * teardown, which is what keeps a crashed host from leaking policies.
33
+ * - **Rules** the DNS-visibility rule plus one `toFQDNs` rule per allowed
34
+ * host — `SandboxNetworkPolicy.allowedHosts`'s own grammar, where
35
+ * `.example.com` means the domain and its subdomains. A `.domain` entry
36
+ * and `narrowing.tlsServerNames` are refused TOGETHER: a TLS server name
37
+ * is one exact SNI value and an expanded entry is a name plus a pattern,
38
+ * so no single value means both and the emitted policy would deny the
39
+ * domain it claims to allow.
40
+ *
41
+ * ## The fence is not a nicety
42
+ *
43
+ * Writing these needs `create`/`patch`/`delete` on the namespace's
44
+ * `ciliumnetworkpolicies`, and RBAC has no way to say "only the objects you
45
+ * own". A host holding those verbs could widen — or simply delete — the
46
+ * operator's own baseline policy. So before the FIRST write, this module
47
+ * proves an operator applied a `ValidatingAdmissionPolicy` and its binding,
48
+ * and refuses with {@link KubernetesAdmissionFenceMissingError}, having
49
+ * written nothing, if either is absent. The shipped example
50
+ * (`k8s/manifests/validatingadmissionpolicy-cilium.yaml`) bounds the host
51
+ * identity to this name prefix, an owner reference whose uid the name has to
52
+ * match, ONE selector label whose VALUE is that owner's own name (the shape
53
+ * alone would not bound anything — one label is exactly what selects every
54
+ * sandbox pod in the namespace), `toFQDNs` entries that each name something
55
+ * (`matchPattern: '*'` is a `toFQDNs` rule and is not an allowlist), the
56
+ * kube-dns rule on port 53, no address-based or entity-based peers and no
57
+ * ingress.
58
+ *
59
+ * ## Union, not replacement
60
+ *
61
+ * The cluster unions every policy selecting a pod, so what this writes ADDS
62
+ * to whatever `config.egress.policy` translated to. Under a `no-network` or
63
+ * `deny-all` baseline that makes the per-sandbox list the pod's whole
64
+ * boundary, which is the deployment this capability is for; under
65
+ * `allow-all` it adds nothing to a pod that could already reach everything,
66
+ * and `setNetworkPolicy([])` there does not deny everything the way
67
+ * `SandboxNetworkPolicy` describes. Nothing refuses that wiring — a
68
+ * permissive baseline with per-sandbox additions is a legitimate deployment
69
+ * — but it is not the one the SDK's sentence is about.
70
+ *
71
+ * ## What is NOT proven anywhere in this repository
72
+ *
73
+ * That any of it is ENFORCED. Enforcement is one CNI's data plane, and the
74
+ * only cluster available here runs none — every object below is proved
75
+ * against a fake API server, and its lifecycle, ownership, garbage collection
76
+ * and admission refusals against a local single-node cluster. "The allowed
77
+ * host answers and the disallowed one does not" is a statement about a
78
+ * network, and it needs a cluster that enforces plus a positive control.
79
+ */
80
+ import type { SandboxNetworkPolicy } from '@namzu/sdk';
81
+ import { type KubernetesEgressConfig, KubernetesNetworkPolicyHostError, type KubernetesPerSandboxEgressConfig } from './egress-policy.js';
82
+ import { type KubernetesClient } from './k8s-client.js';
83
+ import { type KubernetesOwnerReference } from './objects.js';
84
+ /**
85
+ * Every per-sandbox policy's name starts with this, and the shipped admission
86
+ * policy refuses one that does not.
87
+ *
88
+ * It is a CONSTANT rather than an option because it is half of a contract
89
+ * with an object an operator applies: a configurable prefix would be a
90
+ * configuration the fence could not know about, and a fence that trusted the
91
+ * host to tell it what to allow would not be one.
92
+ */
93
+ export declare const PER_SANDBOX_POLICY_NAME_PREFIX = "namzu-sbx-";
94
+ /** `namzu-sbx-<uid>` — the only name this backend ever writes a policy under. */
95
+ export declare function perSandboxPolicyName(ownerUid: string): string;
96
+ /**
97
+ * The object a per-sandbox policy belongs to: what the acquire created, which
98
+ * is what the cluster deletes when the sandbox is destroyed.
99
+ *
100
+ * Two kinds, because this backend has two acquire paths and both must be able
101
+ * to carry the capability: a `SandboxClaim` when the sandbox came out of a
102
+ * warm pool, the `Sandbox` itself when it did not. The claim is the one the
103
+ * issue names; the direct object works identically for garbage collection,
104
+ * and refusing the pool-less path would be refusing it for a reason that is
105
+ * about the pool rather than about the policy.
106
+ */
107
+ export interface PerSandboxPolicyOwner {
108
+ readonly kind: 'SandboxClaim' | 'Sandbox';
109
+ readonly name: string;
110
+ /** `metadata.uid`, as the API server assigned it. Also the policy's name suffix. */
111
+ readonly uid: string;
112
+ }
113
+ /** The `ownerReferences` entry that makes the cluster collect the policy. */
114
+ export declare function perSandboxPolicyOwnerReference(owner: PerSandboxPolicyOwner): KubernetesOwnerReference;
115
+ /**
116
+ * Raised when the fence check is REFUSED rather than answered — a 401/403 on
117
+ * one of the two cluster-scoped admission objects.
118
+ *
119
+ * Its own class rather than a reuse of
120
+ * {@link KubernetesAdmissionFenceMissingError}, because the two ask for
121
+ * different actions and conflating them would send an operator to apply a
122
+ * fence that may already be there: 404 means the object does not exist, 403
123
+ * means this host may not look. The most common cause is applying
124
+ * `rbac-per-sandbox-egress.yaml`'s namespaced Role without the ClusterRole in
125
+ * the same file, so the message names that file rather than the manifest the
126
+ * missing-fence error names.
127
+ */
128
+ export declare class KubernetesAdmissionFenceUnreadableError extends Error {
129
+ readonly resourceKind: 'ValidatingAdmissionPolicy' | 'ValidatingAdmissionPolicyBinding';
130
+ readonly objectName: string;
131
+ readonly path: string;
132
+ readonly name = "KubernetesAdmissionFenceUnreadableError";
133
+ constructor(resourceKind: 'ValidatingAdmissionPolicy' | 'ValidatingAdmissionPolicyBinding', objectName: string, path: string, cause: unknown);
134
+ }
135
+ /**
136
+ * Raised when the admission fence is not in place — the policy, its binding,
137
+ * or both — and therefore before anything has been written.
138
+ *
139
+ * Refusing rather than proceeding is the whole design: the fence is what
140
+ * makes the write verbs safe to hold, so a deployment that granted them and
141
+ * has not applied the fence is in exactly the state the capability exists to
142
+ * avoid. A `ValidatingAdmissionPolicy` with no BINDING is inert — it validates
143
+ * nothing at all — which is why the binding is checked separately rather than
144
+ * assumed from the policy's existence.
145
+ */
146
+ export declare class KubernetesAdmissionFenceMissingError extends Error {
147
+ readonly resourceKind: 'ValidatingAdmissionPolicy' | 'ValidatingAdmissionPolicyBinding';
148
+ readonly objectName: string;
149
+ readonly path: string;
150
+ readonly name = "KubernetesAdmissionFenceMissingError";
151
+ constructor(resourceKind: 'ValidatingAdmissionPolicy' | 'ValidatingAdmissionPolicyBinding', objectName: string, path: string);
152
+ }
153
+ /**
154
+ * Raised for an `allowedHosts` entry this backend will not translate —
155
+ * re-exported rather than defined here.
156
+ *
157
+ * It lives in `egress-policy.ts` beside {@link assertHostsFitNarrowing} and
158
+ * {@link assertUsableHost}, because the config-level translation raises the
159
+ * same class for the same entry and this module imports both checks from
160
+ * there: defining it here would make the two modules import each other. The
161
+ * identifier is re-exported so every existing import path — including the
162
+ * package index — keeps resolving.
163
+ */
164
+ export { KubernetesNetworkPolicyHostError };
165
+ /**
166
+ * Raised when the object an acquire created reported no `metadata.uid`.
167
+ *
168
+ * Its own exported class rather than a bare `Error` because every other
169
+ * refusal this capability adds is catchable by class, and this one refuses an
170
+ * ACQUIRE: a caller that wants to fall back to a sandbox without per-sandbox
171
+ * egress has to be able to tell it from a readiness timeout.
172
+ */
173
+ export declare class KubernetesOwnerUidMissingError extends Error {
174
+ readonly objectName: string;
175
+ readonly namespace: string;
176
+ readonly name = "KubernetesOwnerUidMissingError";
177
+ constructor(objectName: string, namespace: string);
178
+ }
179
+ /**
180
+ * The once-per-backend proof that the fence exists.
181
+ *
182
+ * Memoized on SUCCESS only, exactly as the egress named-object check is: a
183
+ * transient API failure must not wedge every later `setNetworkPolicy` behind
184
+ * a stale rejection, and an operator who applies the fence after the first
185
+ * refusal gets the next call through without restarting the host.
186
+ */
187
+ export interface PerSandboxPolicyFence {
188
+ assertApplied(signal?: AbortSignal): Promise<void>;
189
+ }
190
+ export declare function buildAdmissionFence(client: KubernetesClient, perSandbox: KubernetesPerSandboxEgressConfig): PerSandboxPolicyFence;
191
+ /** What one sandbox's policy writer needs to know. */
192
+ export interface PerSandboxPolicySetterOptions {
193
+ readonly client: KubernetesClient;
194
+ readonly fence: PerSandboxPolicyFence;
195
+ readonly namespace: string;
196
+ readonly egress: KubernetesEgressConfig & {
197
+ readonly perSandbox: KubernetesPerSandboxEgressConfig;
198
+ };
199
+ /** The object the acquire created, which owns the policy. */
200
+ readonly owner: PerSandboxPolicyOwner;
201
+ /**
202
+ * The per-sandbox label's VALUE on this sandbox's pod — the name of the
203
+ * object above, put there by `composeAdditionalPodLabels` and CONFIRMED on
204
+ * the bound pod before the sandbox was admitted. The key comes from
205
+ * config, through the one resolver.
206
+ */
207
+ readonly selectorValue: string;
208
+ }
209
+ /**
210
+ * Build the `setNetworkPolicy` this backend attaches to a sandbox handle.
211
+ *
212
+ * Calls are SERIALIZED per sandbox. Two overlapping calls would otherwise
213
+ * interleave create, replace and read-back against one object, and the loser
214
+ * would resolve having verified the winner's rules — a caller told its policy
215
+ * was applied when a different one is in force is the exact failure the
216
+ * read-back exists to prevent.
217
+ */
218
+ export declare function buildPerSandboxPolicySetter(options: PerSandboxPolicySetterOptions): (policy: SandboxNetworkPolicy) => Promise<void>;
219
+ //# sourceMappingURL=per-sandbox-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"per-sandbox-policy.d.ts","sourceRoot":"","sources":["../../../src/backends/kubernetes/per-sandbox-policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Per-sandbox egress: one policy object per LIVE sandbox, written by this
3
+ * host while the sandbox runs, so `Sandbox.setNetworkPolicy` means something
4
+ * here instead of being omitted.
5
+ *
6
+ * ## Why this is a separate file from `egress-policy.ts`
7
+ *
8
+ * That one is about the boundary an OPERATOR applies and this backend only
9
+ * ever reads: one object per backend, translated from config, verified and
10
+ * never written. This one is about an object this backend CREATES, replaces
11
+ * and lets the cluster collect — a different lifecycle, a different RBAC
12
+ * grant, and the only place in `@namzu/sandbox` that writes a policy at all.
13
+ * They share the manifest builder and the read-back comparator, which is the
14
+ * point: two spellings of "what a namzu egress policy looks like" would be
15
+ * one comparing itself against the other's shape.
16
+ *
17
+ * ## The shape
18
+ *
19
+ * - **Name** `namzu-sbx-<uid>`, where the uid is the object the acquire
20
+ * created for this sandbox — the `SandboxClaim` on the pooled path, the
21
+ * `Sandbox` on the pool-less one. Unique per sandbox by construction, and
22
+ * derivable from the owner reference alone, which is what lets the
23
+ * admission policy check the two against each other.
24
+ * - **Selector** one per-sandbox pod label, composed by
25
+ * `composeAdditionalPodLabels` — the ONE composer — and carried onto the
26
+ * pod as claim-time metadata before the pod exists. Not patched onto a
27
+ * running pod: this backend holds no write verb on pods, and a label added
28
+ * after the fact would leave a window in which the policy selected nothing.
29
+ * - **Owner** an `ownerReferences` entry naming that same object, so
30
+ * `destroy()` deletes the claim and the cluster's garbage collector
31
+ * removes the policy. This backend issues no deletion of its own on
32
+ * teardown, which is what keeps a crashed host from leaking policies.
33
+ * - **Rules** the DNS-visibility rule plus one `toFQDNs` rule per allowed
34
+ * host — `SandboxNetworkPolicy.allowedHosts`'s own grammar, where
35
+ * `.example.com` means the domain and its subdomains. A `.domain` entry
36
+ * and `narrowing.tlsServerNames` are refused TOGETHER: a TLS server name
37
+ * is one exact SNI value and an expanded entry is a name plus a pattern,
38
+ * so no single value means both and the emitted policy would deny the
39
+ * domain it claims to allow.
40
+ *
41
+ * ## The fence is not a nicety
42
+ *
43
+ * Writing these needs `create`/`patch`/`delete` on the namespace's
44
+ * `ciliumnetworkpolicies`, and RBAC has no way to say "only the objects you
45
+ * own". A host holding those verbs could widen — or simply delete — the
46
+ * operator's own baseline policy. So before the FIRST write, this module
47
+ * proves an operator applied a `ValidatingAdmissionPolicy` and its binding,
48
+ * and refuses with {@link KubernetesAdmissionFenceMissingError}, having
49
+ * written nothing, if either is absent. The shipped example
50
+ * (`k8s/manifests/validatingadmissionpolicy-cilium.yaml`) bounds the host
51
+ * identity to this name prefix, an owner reference whose uid the name has to
52
+ * match, ONE selector label whose VALUE is that owner's own name (the shape
53
+ * alone would not bound anything — one label is exactly what selects every
54
+ * sandbox pod in the namespace), `toFQDNs` entries that each name something
55
+ * (`matchPattern: '*'` is a `toFQDNs` rule and is not an allowlist), the
56
+ * kube-dns rule on port 53, no address-based or entity-based peers and no
57
+ * ingress.
58
+ *
59
+ * ## Union, not replacement
60
+ *
61
+ * The cluster unions every policy selecting a pod, so what this writes ADDS
62
+ * to whatever `config.egress.policy` translated to. Under a `no-network` or
63
+ * `deny-all` baseline that makes the per-sandbox list the pod's whole
64
+ * boundary, which is the deployment this capability is for; under
65
+ * `allow-all` it adds nothing to a pod that could already reach everything,
66
+ * and `setNetworkPolicy([])` there does not deny everything the way
67
+ * `SandboxNetworkPolicy` describes. Nothing refuses that wiring — a
68
+ * permissive baseline with per-sandbox additions is a legitimate deployment
69
+ * — but it is not the one the SDK's sentence is about.
70
+ *
71
+ * ## What is NOT proven anywhere in this repository
72
+ *
73
+ * That any of it is ENFORCED. Enforcement is one CNI's data plane, and the
74
+ * only cluster available here runs none — every object below is proved
75
+ * against a fake API server, and its lifecycle, ownership, garbage collection
76
+ * and admission refusals against a local single-node cluster. "The allowed
77
+ * host answers and the disallowed one does not" is a statement about a
78
+ * network, and it needs a cluster that enforces plus a positive control.
79
+ */
80
+ import { KubernetesNetworkPolicyHostError, PER_SANDBOX_NARROWING_REFUSAL, assertHostsFitNarrowing, assertUsableHost, buildCiliumEgressManifest, perSandboxEgressLabelKey, verifyEgressPolicyApplied, } from './egress-policy.js';
81
+ import { KubernetesAlreadyGoneError, KubernetesConflictError, KubernetesCredentialError, } from './k8s-client.js';
82
+ import { SANDBOX_API_GROUP, SANDBOX_API_VERSION, SANDBOX_EXTENSIONS_API_GROUP, ciliumNetworkPolicyCollectionPath, ciliumNetworkPolicyPath, validatingAdmissionPolicyBindingPath, validatingAdmissionPolicyPath, } from './objects.js';
83
+ /**
84
+ * Every per-sandbox policy's name starts with this, and the shipped admission
85
+ * policy refuses one that does not.
86
+ *
87
+ * It is a CONSTANT rather than an option because it is half of a contract
88
+ * with an object an operator applies: a configurable prefix would be a
89
+ * configuration the fence could not know about, and a fence that trusted the
90
+ * host to tell it what to allow would not be one.
91
+ */
92
+ export const PER_SANDBOX_POLICY_NAME_PREFIX = 'namzu-sbx-';
93
+ /** `namzu-sbx-<uid>` — the only name this backend ever writes a policy under. */
94
+ export function perSandboxPolicyName(ownerUid) {
95
+ return `${PER_SANDBOX_POLICY_NAME_PREFIX}${ownerUid}`;
96
+ }
97
+ /** The `ownerReferences` entry that makes the cluster collect the policy. */
98
+ export function perSandboxPolicyOwnerReference(owner) {
99
+ return {
100
+ apiVersion: `${owner.kind === 'SandboxClaim' ? SANDBOX_EXTENSIONS_API_GROUP : SANDBOX_API_GROUP}/${SANDBOX_API_VERSION}`,
101
+ kind: owner.kind,
102
+ name: owner.name,
103
+ uid: owner.uid,
104
+ };
105
+ }
106
+ /**
107
+ * Raised when the fence check is REFUSED rather than answered — a 401/403 on
108
+ * one of the two cluster-scoped admission objects.
109
+ *
110
+ * Its own class rather than a reuse of
111
+ * {@link KubernetesAdmissionFenceMissingError}, because the two ask for
112
+ * different actions and conflating them would send an operator to apply a
113
+ * fence that may already be there: 404 means the object does not exist, 403
114
+ * means this host may not look. The most common cause is applying
115
+ * `rbac-per-sandbox-egress.yaml`'s namespaced Role without the ClusterRole in
116
+ * the same file, so the message names that file rather than the manifest the
117
+ * missing-fence error names.
118
+ */
119
+ export class KubernetesAdmissionFenceUnreadableError extends Error {
120
+ resourceKind;
121
+ objectName;
122
+ path;
123
+ name = 'KubernetesAdmissionFenceUnreadableError';
124
+ constructor(resourceKind, objectName, path, cause) {
125
+ super(`kubernetes: config.egress.perSandbox is configured but this host may not READ the ${resourceKind} named ${objectName} (${path}), so it cannot prove the fence that bounds what it writes. Both admission objects are cluster-scoped: the namespaced Role in k8s/manifests/rbac-per-sandbox-egress.yaml is not enough on its own, and the ClusterRole and ClusterRoleBinding in that same file are what grant the read. Nothing was written.`, { cause });
126
+ this.resourceKind = resourceKind;
127
+ this.objectName = objectName;
128
+ this.path = path;
129
+ }
130
+ }
131
+ /**
132
+ * Raised when the admission fence is not in place — the policy, its binding,
133
+ * or both — and therefore before anything has been written.
134
+ *
135
+ * Refusing rather than proceeding is the whole design: the fence is what
136
+ * makes the write verbs safe to hold, so a deployment that granted them and
137
+ * has not applied the fence is in exactly the state the capability exists to
138
+ * avoid. A `ValidatingAdmissionPolicy` with no BINDING is inert — it validates
139
+ * nothing at all — which is why the binding is checked separately rather than
140
+ * assumed from the policy's existence.
141
+ */
142
+ export class KubernetesAdmissionFenceMissingError extends Error {
143
+ resourceKind;
144
+ objectName;
145
+ path;
146
+ name = 'KubernetesAdmissionFenceMissingError';
147
+ constructor(resourceKind, objectName, path) {
148
+ super(`kubernetes: config.egress.perSandbox is configured but no ${resourceKind} named ${objectName} exists (${path}), so nothing would bound what this host writes to the namespace's CiliumNetworkPolicies. Apply k8s/manifests/validatingadmissionpolicy-cilium.yaml (and its binding) before calling setNetworkPolicy — the host holds create/patch/delete on every policy in the namespace, and the fence is what limits that to objects named ${PER_SANDBOX_POLICY_NAME_PREFIX}<owner uid>, selected by one label, carrying only toFQDNs and the cluster-DNS rule. Nothing was written.`);
149
+ this.resourceKind = resourceKind;
150
+ this.objectName = objectName;
151
+ this.path = path;
152
+ }
153
+ }
154
+ /**
155
+ * Raised for an `allowedHosts` entry this backend will not translate —
156
+ * re-exported rather than defined here.
157
+ *
158
+ * It lives in `egress-policy.ts` beside {@link assertHostsFitNarrowing} and
159
+ * {@link assertUsableHost}, because the config-level translation raises the
160
+ * same class for the same entry and this module imports both checks from
161
+ * there: defining it here would make the two modules import each other. The
162
+ * identifier is re-exported so every existing import path — including the
163
+ * package index — keeps resolving.
164
+ */
165
+ export { KubernetesNetworkPolicyHostError };
166
+ /**
167
+ * One `allowedHosts` entry, validated and canonicalised to the bytes a
168
+ * `CiliumNetworkPolicy` should carry.
169
+ *
170
+ * Lowercasing is not leniency: DNS names are case-insensitive, so
171
+ * `API.example.com` and `api.example.com` name the same host, and Cilium's
172
+ * `matchName` is compared against what the DNS proxy saw — lowercase. The
173
+ * docker backend accepts either case, and a list that worked there and threw
174
+ * here would be a portability trap with no boundary behind it.
175
+ *
176
+ * The grammar itself is {@link assertUsableHost}'s, in `egress-policy.ts`
177
+ * beside the error it throws, because it is no longer only this writer's: the
178
+ * shared translation calls it too, so a config-level allowlist is refused the
179
+ * same entries this path refuses. What is HERE is the part that is this
180
+ * writer's alone — the canonicalisation, applied before the fence is read and
181
+ * before anything is queued behind a previous call.
182
+ */
183
+ function normalizeHost(entry) {
184
+ if (typeof entry !== 'string') {
185
+ throw new KubernetesNetworkPolicyHostError(String(entry), 'is not a string');
186
+ }
187
+ const canonical = entry.toLowerCase();
188
+ assertUsableHost(canonical);
189
+ return canonical;
190
+ }
191
+ /**
192
+ * Raised when the object an acquire created reported no `metadata.uid`.
193
+ *
194
+ * Its own exported class rather than a bare `Error` because every other
195
+ * refusal this capability adds is catchable by class, and this one refuses an
196
+ * ACQUIRE: a caller that wants to fall back to a sandbox without per-sandbox
197
+ * egress has to be able to tell it from a readiness timeout.
198
+ */
199
+ export class KubernetesOwnerUidMissingError extends Error {
200
+ objectName;
201
+ namespace;
202
+ name = 'KubernetesOwnerUidMissingError';
203
+ constructor(objectName, namespace) {
204
+ super(`kubernetes: ${objectName} in namespace ${namespace} was created but reported no metadata.uid, in its create reply or in any readiness read. config.egress.perSandbox needs that uid: it is what a per-sandbox CiliumNetworkPolicy names in its ownerReferences (so the cluster deletes the policy with the object) and the suffix of the policy's own name. Refusing rather than handing back a sandbox whose setNetworkPolicy would write an orphan.`);
205
+ this.objectName = objectName;
206
+ this.namespace = namespace;
207
+ }
208
+ }
209
+ export function buildAdmissionFence(client, perSandbox) {
210
+ const policyName = perSandbox.admissionPolicyName;
211
+ const bindingName = perSandbox.admissionPolicyBindingName ?? `${policyName}-binding`;
212
+ let applied;
213
+ const read = async (kind, name, path, signal) => {
214
+ try {
215
+ await client.request('GET', path, undefined, signal);
216
+ }
217
+ catch (err) {
218
+ if (err instanceof KubernetesAlreadyGoneError) {
219
+ throw new KubernetesAdmissionFenceMissingError(kind, name, path);
220
+ }
221
+ // A 401/403 is NOT "the fence is missing": the object may be there
222
+ // and this host simply may not read it. Both refuse the write, and
223
+ // they send an operator to different files.
224
+ if (err instanceof KubernetesCredentialError) {
225
+ throw new KubernetesAdmissionFenceUnreadableError(kind, name, path, err);
226
+ }
227
+ throw err;
228
+ }
229
+ };
230
+ return {
231
+ async assertApplied(signal) {
232
+ applied ??= (async () => {
233
+ await read('ValidatingAdmissionPolicy', policyName, validatingAdmissionPolicyPath(policyName), signal);
234
+ await read('ValidatingAdmissionPolicyBinding', bindingName, validatingAdmissionPolicyBindingPath(bindingName), signal);
235
+ })().catch((err) => {
236
+ applied = undefined;
237
+ throw err;
238
+ });
239
+ await applied;
240
+ },
241
+ };
242
+ }
243
+ /**
244
+ * Build the `setNetworkPolicy` this backend attaches to a sandbox handle.
245
+ *
246
+ * Calls are SERIALIZED per sandbox. Two overlapping calls would otherwise
247
+ * interleave create, replace and read-back against one object, and the loser
248
+ * would resolve having verified the winner's rules — a caller told its policy
249
+ * was applied when a different one is in force is the exact failure the
250
+ * read-back exists to prevent.
251
+ */
252
+ export function buildPerSandboxPolicySetter(options) {
253
+ const { client, fence, namespace, egress, owner, selectorValue } = options;
254
+ const perSandbox = egress.perSandbox;
255
+ const labelKey = perSandboxEgressLabelKey(egress);
256
+ if (labelKey === undefined) {
257
+ // Unreachable: the type above requires `perSandbox`, and the resolver
258
+ // returns a key whenever it is present.
259
+ throw new Error('kubernetes: per-sandbox egress is not configured');
260
+ }
261
+ const name = perSandboxPolicyName(owner.uid);
262
+ const path = ciliumNetworkPolicyPath(namespace, name);
263
+ let queue = Promise.resolve();
264
+ const translate = (allowedHosts) => buildCiliumEgressManifest({
265
+ namespace,
266
+ name,
267
+ selectorLabels: { [labelKey]: selectorValue },
268
+ allowedHosts,
269
+ // The CONFIGURED kind this stands in for: a host-supplied
270
+ // allowlist is exactly what `'static'` means, and a refusal that
271
+ // named anything else would send a reader to the wrong config key.
272
+ policyKind: 'static',
273
+ ...(perSandbox.narrowing !== undefined ? { narrowing: perSandbox.narrowing } : {}),
274
+ ownerReferences: [perSandboxPolicyOwnerReference(owner)],
275
+ refusalContext: PER_SANDBOX_NARROWING_REFUSAL,
276
+ });
277
+ const write = async (translated) => {
278
+ const body = translated.manifest;
279
+ try {
280
+ await client.request('POST', ciliumNetworkPolicyCollectionPath(namespace), body);
281
+ }
282
+ catch (err) {
283
+ // The object already exists — this sandbox is narrowing its egress
284
+ // a second time. A merge patch replaces `spec.egress` wholesale
285
+ // (RFC 7386 replaces arrays), which is what a REPLACEMENT policy
286
+ // needs, and the read-back below proves it rather than assuming it.
287
+ if (!(err instanceof KubernetesConflictError))
288
+ throw err;
289
+ await client.request('PATCH', path, body);
290
+ }
291
+ // Resolve only once the cluster's own copy deep-equals the
292
+ // translation, through the comparator the config-level check already
293
+ // uses. A policy the API server accepted and mutated — a defaulting
294
+ // webhook, an operator's own automation — would otherwise be reported
295
+ // as the policy the caller asked for.
296
+ //
297
+ // One inherited wording to know about: if the object is GONE by the
298
+ // time it is read back, the shared comparator raises
299
+ // `KubernetesEgressPolicyNotAppliedError`, whose message asks an
300
+ // operator to apply the manifest this backend computed. Here that is
301
+ // not the action — the object was written a moment ago, so it being
302
+ // gone means its owner was deleted underneath the call and this
303
+ // sandbox is already being destroyed. The refusal is still right; only
304
+ // its advice belongs to the other caller.
305
+ await verifyEgressPolicyApplied(client, translated);
306
+ };
307
+ const remove = async () => {
308
+ try {
309
+ await client.request('DELETE', path);
310
+ }
311
+ catch (err) {
312
+ // Already gone is the state DELETE was asking for — the cluster
313
+ // may have collected it with the claim while this call was in
314
+ // flight.
315
+ if (!(err instanceof KubernetesAlreadyGoneError))
316
+ throw err;
317
+ }
318
+ };
319
+ return async (policy) => {
320
+ // Validated BEFORE the queue, so a malformed list is refused with
321
+ // nothing written and nothing waited for. `normalizeHost` also
322
+ // lowercases: DNS names are case-insensitive, the docker backend
323
+ // accepts either case, and a `CiliumNetworkPolicy` matches names the
324
+ // resolver returns — which are lowercase. Refusing `API.example.com`
325
+ // on one backend and honouring it on another would be a portability
326
+ // trap, so it is canonicalised here instead.
327
+ const hosts = [...(policy?.allowedHosts ?? [])].map(normalizeHost);
328
+ // And refused here rather than emitted: an entry the CONFIGURED
329
+ // narrowing cannot express is one whose policy would read as applied
330
+ // and deny what it names. The shared guard refuses it again inside the
331
+ // translation, so every caller is covered; this call is the earlier
332
+ // one, before the fence is read and before anything is queued behind a
333
+ // previous call.
334
+ assertHostsFitNarrowing(hosts, perSandbox.narrowing,
335
+ // The per-sandbox context, from `egress-policy.ts` rather than
336
+ // rebuilt here: it is the SAME value `buildCiliumEgressManifest`
337
+ // refuses by on this path (`refusalContext`), so the earlier check
338
+ // and the translation's own cannot name different fields — and the
339
+ // sentence it carries is the true one here, where the entry really
340
+ // does become a name plus a `*.domain` pattern. Sending this caller
341
+ // to `config.egress.ciliumNarrowing` would name a field a
342
+ // `perSandbox` host never set.
343
+ PER_SANDBOX_NARROWING_REFUSAL);
344
+ // A previous call's failure belongs to that caller; this one still runs,
345
+ // in order behind it.
346
+ const run = queue.then(async () => {
347
+ // Before EVERY write — the DELETE below included — and before
348
+ // every one until the fence check succeeds: nothing is sent while
349
+ // the fence is missing.
350
+ //
351
+ // The DELETE is checked too because the plan's invariant is about
352
+ // writes, not about widening: a host that never proved the fence
353
+ // must not reach the namespace's policies with any verb it holds.
354
+ // The cost is a deployment that REMOVES the fence mid-run, whose
355
+ // `setNetworkPolicy([])` is then refused and whose sandbox keeps
356
+ // the wider per-sandbox allowance until it is destroyed and the
357
+ // cluster collects the policy with its owner. That is a loud,
358
+ // named refusal the caller can act on, which is the better half
359
+ // of the trade against a silent unfenced write.
360
+ await fence.assertApplied();
361
+ // An EMPTY list is not "no policy": it deletes this sandbox's own
362
+ // object and leaves the configured baseline — whatever
363
+ // `config.egress.policy` translated to, plus every other policy
364
+ // selecting the pod — in force.
365
+ if (hosts.length === 0) {
366
+ await remove();
367
+ return;
368
+ }
369
+ await write(translate(hosts));
370
+ });
371
+ queue = run.then(() => undefined, () => undefined);
372
+ await run;
373
+ };
374
+ }
375
+ //# sourceMappingURL=per-sandbox-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"per-sandbox-policy.js","sourceRoot":"","sources":["../../../src/backends/kubernetes/per-sandbox-policy.ts"],"names":[],"mappings":"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"}