@namzu/sandbox 26.0.0 → 28.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 +62 -7
- package/README.md +20 -7
- package/dist/backends/firecracker/protocol.d.ts +46 -0
- package/dist/backends/firecracker/protocol.d.ts.map +1 -1
- package/dist/backends/firecracker/protocol.js +31 -0
- package/dist/backends/firecracker/protocol.js.map +1 -1
- package/dist/backends/firecracker/transport.d.ts +15 -1
- package/dist/backends/firecracker/transport.d.ts.map +1 -1
- package/dist/backends/firecracker/transport.js +16 -0
- package/dist/backends/firecracker/transport.js.map +1 -1
- package/dist/backends/kubernetes/allocation.d.ts +115 -0
- package/dist/backends/kubernetes/allocation.d.ts.map +1 -0
- package/dist/backends/kubernetes/allocation.js +130 -0
- package/dist/backends/kubernetes/allocation.js.map +1 -0
- package/dist/backends/kubernetes/egress-policy.d.ts +111 -35
- package/dist/backends/kubernetes/egress-policy.d.ts.map +1 -1
- package/dist/backends/kubernetes/egress-policy.js +81 -41
- package/dist/backends/kubernetes/egress-policy.js.map +1 -1
- package/dist/backends/kubernetes/identity.d.ts +129 -0
- package/dist/backends/kubernetes/identity.d.ts.map +1 -1
- package/dist/backends/kubernetes/identity.js +150 -0
- package/dist/backends/kubernetes/identity.js.map +1 -1
- package/dist/backends/kubernetes/index.d.ts +23 -1
- package/dist/backends/kubernetes/index.d.ts.map +1 -1
- package/dist/backends/kubernetes/index.js +22 -3
- package/dist/backends/kubernetes/index.js.map +1 -1
- package/dist/backends/kubernetes/objects.d.ts +76 -0
- package/dist/backends/kubernetes/objects.d.ts.map +1 -1
- package/dist/backends/kubernetes/objects.js +61 -0
- package/dist/backends/kubernetes/objects.js.map +1 -1
- package/dist/backends/kubernetes/per-sandbox-fence.d.ts +194 -0
- package/dist/backends/kubernetes/per-sandbox-fence.d.ts.map +1 -0
- package/dist/backends/kubernetes/per-sandbox-fence.js +522 -0
- package/dist/backends/kubernetes/per-sandbox-fence.js.map +1 -0
- package/dist/backends/kubernetes/per-sandbox-policy.d.ts +45 -3
- package/dist/backends/kubernetes/per-sandbox-policy.d.ts.map +1 -1
- package/dist/backends/kubernetes/per-sandbox-policy.js +98 -8
- package/dist/backends/kubernetes/per-sandbox-policy.js.map +1 -1
- package/dist/backends/kubernetes/sandbox.d.ts +4 -9
- package/dist/backends/kubernetes/sandbox.d.ts.map +1 -1
- package/dist/backends/kubernetes/sandbox.js +4 -9
- package/dist/backends/kubernetes/sandbox.js.map +1 -1
- package/dist/backends/kubernetes/transport.d.ts +108 -1
- package/dist/backends/kubernetes/transport.d.ts.map +1 -1
- package/dist/backends/kubernetes/transport.js +150 -6
- package/dist/backends/kubernetes/transport.js.map +1 -1
- package/dist/backends/kubernetes/workspace-allocate.d.ts +206 -0
- package/dist/backends/kubernetes/workspace-allocate.d.ts.map +1 -0
- package/dist/backends/kubernetes/workspace-allocate.js +332 -0
- package/dist/backends/kubernetes/workspace-allocate.js.map +1 -0
- package/dist/backends/kubernetes/workspace-observe.d.ts +187 -0
- package/dist/backends/kubernetes/workspace-observe.d.ts.map +1 -0
- package/dist/backends/kubernetes/workspace-observe.js +343 -0
- package/dist/backends/kubernetes/workspace-observe.js.map +1 -0
- package/dist/backends/kubernetes/workspace-per-sandbox.d.ts +94 -0
- package/dist/backends/kubernetes/workspace-per-sandbox.d.ts.map +1 -0
- package/dist/backends/kubernetes/workspace-per-sandbox.js +119 -0
- package/dist/backends/kubernetes/workspace-per-sandbox.js.map +1 -0
- package/dist/backends/kubernetes/workspace.d.ts +139 -5
- package/dist/backends/kubernetes/workspace.d.ts.map +1 -1
- package/dist/backends/kubernetes/workspace.js +434 -36
- package/dist/backends/kubernetes/workspace.js.map +1 -1
- package/dist/index.d.ts +83 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +100 -6
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/backends/firecracker/protocol.ts +47 -0
- package/src/backends/firecracker/transport.ts +31 -0
- package/src/backends/kubernetes/allocation.ts +199 -0
- package/src/backends/kubernetes/egress-policy.ts +165 -42
- package/src/backends/kubernetes/identity.ts +210 -0
- package/src/backends/kubernetes/index.ts +61 -2
- package/src/backends/kubernetes/objects.ts +98 -0
- package/src/backends/kubernetes/per-sandbox-fence.ts +800 -0
- package/src/backends/kubernetes/per-sandbox-policy.ts +152 -17
- package/src/backends/kubernetes/sandbox.ts +4 -9
- package/src/backends/kubernetes/transport.ts +203 -4
- package/src/backends/kubernetes/workspace-allocate.ts +607 -0
- package/src/backends/kubernetes/workspace-observe.ts +590 -0
- package/src/backends/kubernetes/workspace-per-sandbox.ts +179 -0
- package/src/backends/kubernetes/workspace.ts +655 -34
- package/src/index.ts +193 -3
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the per-sandbox egress fence is checked FOR, and the operator-pinned
|
|
3
|
+
* host set that tightens it.
|
|
4
|
+
*
|
|
5
|
+
* `per-sandbox-policy.ts` used to prove the fence by GETting the
|
|
6
|
+
* `ValidatingAdmissionPolicy` and its binding and stopping at "the request
|
|
7
|
+
* succeeded". An object that exists is not an object that fences: a binding
|
|
8
|
+
* whose `validationActions` is `[Audit]` or `[Warn]` logs and admits, a
|
|
9
|
+
* binding that names another policy attaches nothing to this one, a policy
|
|
10
|
+
* with `failurePolicy: Ignore` admits whatever its expressions cannot
|
|
11
|
+
* evaluate, a binding whose namespace selector excludes the sandboxes'
|
|
12
|
+
* namespace never runs there, and a policy whose `matchConditions` name a
|
|
13
|
+
* different user never matches this host. Each reads, from the outside,
|
|
14
|
+
* exactly like an enforced fence, which is the failure the `no-network`
|
|
15
|
+
* baseline's check exists to prevent for its own object: that one compares the
|
|
16
|
+
* named object to the translation field by field, and refuses on any
|
|
17
|
+
* difference.
|
|
18
|
+
*
|
|
19
|
+
* This module is the same standard applied to the fence. It reads what the
|
|
20
|
+
* cluster stored and compares it, field by field, to what the writer relies
|
|
21
|
+
* on, and refuses with a named error carrying the field that failed — before
|
|
22
|
+
* anything is written.
|
|
23
|
+
*
|
|
24
|
+
* ## What it can and cannot prove
|
|
25
|
+
*
|
|
26
|
+
* It proves the CONFIGURATION of the fence: that the binding attaches this
|
|
27
|
+
* policy, denies, fails closed, covers the namespace and the verbs, and that
|
|
28
|
+
* the policy matches the configured identity. It does not evaluate the
|
|
29
|
+
* policy's CEL: whether the shipped expressions refuse what they claim is
|
|
30
|
+
* measured against an API server (see the header of
|
|
31
|
+
* `validatingadmissionpolicy-cilium.yaml`), not derivable from the stored
|
|
32
|
+
* object. The one exception is the companion policy below, whose expressions
|
|
33
|
+
* this module owns and compares verbatim.
|
|
34
|
+
*
|
|
35
|
+
* ## The operator-pinned host set
|
|
36
|
+
*
|
|
37
|
+
* The shipped fence bounds the SHAPE of what a host may write, not the names:
|
|
38
|
+
* any host holding the write verbs can grant any syntactically valid
|
|
39
|
+
* `toFQDNs` entry. `config.egress.perSandbox.permittedHostsParam` names a
|
|
40
|
+
* `ConfigMap` an operator maintains — the reviewed hostnames and ports — and
|
|
41
|
+
* a COMPANION policy (`validatingadmissionpolicy-cilium-permitted-hosts.yaml`)
|
|
42
|
+
* refuses, at admission, any `toFQDNs` name or port outside it. The backend
|
|
43
|
+
* reads the same ConfigMap and refuses locally, with a precise error instead
|
|
44
|
+
* of an opaque 403, but the admission rule stays the authority: the local
|
|
45
|
+
* check is a courtesy and a second reader, never the boundary.
|
|
46
|
+
*
|
|
47
|
+
* A companion rather than a change to the shipped policy because the shipped
|
|
48
|
+
* policy is already applied in clusters, and a host that does not configure
|
|
49
|
+
* the parameter must see byte-for-byte what it saw before. The companion
|
|
50
|
+
* covers CREATE and UPDATE only: a DELETE never widens, and a missing
|
|
51
|
+
* parameter must not make `setNetworkPolicy([])` — the revoke — impossible.
|
|
52
|
+
*/
|
|
53
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
54
|
+
import { assertUsableHost, } from './egress-policy.js';
|
|
55
|
+
import { isRecord, matchesLabelSelector, readList } from './ingress-policy.js';
|
|
56
|
+
import { KubernetesAlreadyGoneError, KubernetesCredentialError, } from './k8s-client.js';
|
|
57
|
+
import { configMapPath } from './objects.js';
|
|
58
|
+
/**
|
|
59
|
+
* Raised when the fence EXISTS but would not enforce what this backend relies
|
|
60
|
+
* on — `validationActions` without `Deny`, a binding that names another
|
|
61
|
+
* policy, `failurePolicy: Ignore`, a namespace selector that leaves the
|
|
62
|
+
* sandboxes' namespace out, a `matchConditions` that names another identity,
|
|
63
|
+
* a companion policy whose expressions are not the ones this version ships,
|
|
64
|
+
* or a binding whose parameter is not the configured one.
|
|
65
|
+
*
|
|
66
|
+
* Its own class rather than a reuse of
|
|
67
|
+
* `KubernetesAdmissionFenceMissingError`, because the operator's action
|
|
68
|
+
* differs: that error says to apply a manifest, this one says an applied
|
|
69
|
+
* object is wrong and names the field. `field` is the dotted path inside
|
|
70
|
+
* `resourceKind`, `expected` what the writer needs and `actual` what the
|
|
71
|
+
* cluster holds.
|
|
72
|
+
*/
|
|
73
|
+
export class KubernetesAdmissionFenceInertError extends Error {
|
|
74
|
+
resourceKind;
|
|
75
|
+
objectName;
|
|
76
|
+
field;
|
|
77
|
+
expected;
|
|
78
|
+
actual;
|
|
79
|
+
name = 'KubernetesAdmissionFenceInertError';
|
|
80
|
+
constructor(resourceKind, objectName, field, expected, actual) {
|
|
81
|
+
super(`kubernetes: config.egress.perSandbox is configured but the ${resourceKind} named ${objectName} would not fence what this host writes: ${field} is ${actual}, expected ${expected}. An applied admission object that does not enforce reads exactly like one that does, and the host holds create/patch/delete on every CiliumNetworkPolicy in the namespace, so nothing was written. Correct the object (the shipped k8s/manifests/validatingadmissionpolicy-cilium.yaml and validatingadmissionpolicy-cilium-permitted-hosts.yaml are the references) and call again.`);
|
|
82
|
+
this.resourceKind = resourceKind;
|
|
83
|
+
this.objectName = objectName;
|
|
84
|
+
this.field = field;
|
|
85
|
+
this.expected = expected;
|
|
86
|
+
this.actual = actual;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Raised when `permittedHostsParam` is configured and the ConfigMap it names
|
|
91
|
+
* cannot be used: absent, unreadable, or not in the shape the companion
|
|
92
|
+
* policy reads. Every `setNetworkPolicy` call that grants a host is refused
|
|
93
|
+
* with it, and nothing is written.
|
|
94
|
+
*/
|
|
95
|
+
export class KubernetesPermittedHostsParamError extends Error {
|
|
96
|
+
param;
|
|
97
|
+
name = 'KubernetesPermittedHostsParamError';
|
|
98
|
+
constructor(param, reason, cause) {
|
|
99
|
+
super(`kubernetes: config.egress.perSandbox.permittedHostsParam names ConfigMap ${param.namespace}/${param.name}, which ${reason}. The admission policy that pins the permitted hosts reads the same object and denies every grant while it is missing or malformed, so this host refuses first, with nothing written. The expected shape is data.hosts (exact hostnames), data.patterns (*.domain patterns) and data.ports (numbers), each one entry per line; see k8s/manifests/validatingadmissionpolicy-cilium-permitted-hosts.yaml.`, cause === undefined ? undefined : { cause });
|
|
100
|
+
this.param = param;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Raised when a `setNetworkPolicy` call names a host or a port the
|
|
105
|
+
* operator-pinned set does not contain. Local, named and before any write; the
|
|
106
|
+
* admission policy would refuse the same body with an opaque 403.
|
|
107
|
+
*
|
|
108
|
+
* `hosts` are the `allowedHosts` entries as written; `ports` are the numbers
|
|
109
|
+
* outside the set, and `unrestrictedPortsFor` the hosts whose rule carries no
|
|
110
|
+
* port restriction at all — every port is outside any list.
|
|
111
|
+
*/
|
|
112
|
+
export class KubernetesPermittedHostsError extends Error {
|
|
113
|
+
param;
|
|
114
|
+
hosts;
|
|
115
|
+
ports;
|
|
116
|
+
unrestrictedPortsFor;
|
|
117
|
+
name = 'KubernetesPermittedHostsError';
|
|
118
|
+
constructor(param, hosts, ports, unrestrictedPortsFor) {
|
|
119
|
+
const parts = [];
|
|
120
|
+
if (hosts.length > 0) {
|
|
121
|
+
parts.push(`the host(s) ${hosts.map((h) => JSON.stringify(h)).join(', ')} are not in it`);
|
|
122
|
+
}
|
|
123
|
+
if (ports.length > 0) {
|
|
124
|
+
parts.push(`the port(s) ${ports.join(', ')} are not in it`);
|
|
125
|
+
}
|
|
126
|
+
if (unrestrictedPortsFor.length > 0) {
|
|
127
|
+
parts.push(`${unrestrictedPortsFor.map((h) => JSON.stringify(h)).join(', ')} would be granted on EVERY port, because config.egress.perSandbox.narrowing sets no ports for it, and every port is outside any list (set narrowing.ports or narrowing.hostPorts)`);
|
|
128
|
+
}
|
|
129
|
+
super(`kubernetes: refusing setNetworkPolicy: ConfigMap ${param.namespace}/${param.name} pins the hosts and ports this host may grant, and ${parts.join('; ')}. The shipped admission policy refuses the same body, so it would be a 403 after the fact. Nothing was written.`);
|
|
130
|
+
this.param = param;
|
|
131
|
+
this.hosts = hosts;
|
|
132
|
+
this.ports = ports;
|
|
133
|
+
this.unrestrictedPortsFor = unrestrictedPortsFor;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// Reading an admission object
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
function describe(value) {
|
|
140
|
+
if (value === undefined)
|
|
141
|
+
return 'absent';
|
|
142
|
+
const text = JSON.stringify(value);
|
|
143
|
+
return text === undefined ? String(value) : text.length > 200 ? `${text.slice(0, 200)}…` : text;
|
|
144
|
+
}
|
|
145
|
+
/** One expression, whitespace-collapsed — a folded YAML scalar and a one-liner compare equal. */
|
|
146
|
+
function normalizeExpression(value) {
|
|
147
|
+
return typeof value === 'string' ? value.replace(/\s+/g, ' ').trim() : '';
|
|
148
|
+
}
|
|
149
|
+
const CILIUM_GROUP = 'cilium.io';
|
|
150
|
+
const CILIUM_VERSION = 'v2';
|
|
151
|
+
const CILIUM_RESOURCE = 'ciliumnetworkpolicies';
|
|
152
|
+
/** The verbs of a `CiliumNetworkPolicy` write the shipped main policy covers. */
|
|
153
|
+
export const FENCE_OPERATIONS = ['CREATE', 'UPDATE', 'DELETE'];
|
|
154
|
+
/** The verbs the companion covers: a DELETE never widens, see the module header. */
|
|
155
|
+
export const COMPANION_OPERATIONS = ['CREATE', 'UPDATE'];
|
|
156
|
+
function listOf(value) {
|
|
157
|
+
const read = readList(value);
|
|
158
|
+
return read === 'unreadable' ? undefined : (read ?? []);
|
|
159
|
+
}
|
|
160
|
+
/** Does one admission `NamedRuleWithOperations` cover this operation on the CRD? */
|
|
161
|
+
function ruleCovers(rule, operation) {
|
|
162
|
+
if (!isRecord(rule))
|
|
163
|
+
return false;
|
|
164
|
+
const has = (field, wanted) => {
|
|
165
|
+
const list = listOf(rule[field]);
|
|
166
|
+
return list !== undefined && (list.includes(wanted) || list.includes('*'));
|
|
167
|
+
};
|
|
168
|
+
// A rule naming `resourceNames` narrows to those objects, which cannot
|
|
169
|
+
// include the ones this host creates under names it generates.
|
|
170
|
+
const names = listOf(rule.resourceNames);
|
|
171
|
+
if (names === undefined || names.length > 0)
|
|
172
|
+
return false;
|
|
173
|
+
return (has('apiGroups', CILIUM_GROUP) &&
|
|
174
|
+
has('apiVersions', CILIUM_VERSION) &&
|
|
175
|
+
has('resources', CILIUM_RESOURCE) &&
|
|
176
|
+
has('operations', operation));
|
|
177
|
+
}
|
|
178
|
+
/** Does any rule of the list cover the operation? `undefined` when the list is not a list. */
|
|
179
|
+
function rulesCover(rules, operation) {
|
|
180
|
+
const list = listOf(rules);
|
|
181
|
+
if (list === undefined)
|
|
182
|
+
return undefined;
|
|
183
|
+
return list.some((rule) => ruleCovers(rule, operation));
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* `matchConstraints` (policy) / `matchResources` (binding): both are the same
|
|
187
|
+
* `MatchResources` shape. Proves that, between them, the writes in
|
|
188
|
+
* `operations` reach the validations — resource rules cover them, no exclude
|
|
189
|
+
* rule removes them, and neither selector can be satisfied by an object the
|
|
190
|
+
* host chooses the labels of.
|
|
191
|
+
*/
|
|
192
|
+
function assertMatchResources(kind, objectName, path, match, operations, namespace,
|
|
193
|
+
/** A policy MUST state its resource rules; a binding may leave them to the policy. */
|
|
194
|
+
rulesRequired) {
|
|
195
|
+
const fail = (field, expected, actual) => {
|
|
196
|
+
throw new KubernetesAdmissionFenceInertError(kind, objectName, `${path}.${field}`, expected, describe(actual));
|
|
197
|
+
};
|
|
198
|
+
if (match === undefined || match === null) {
|
|
199
|
+
if (rulesRequired)
|
|
200
|
+
fail('resourceRules', 'rules covering cilium.io ciliumnetworkpolicies', match);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (!isRecord(match)) {
|
|
204
|
+
throw new KubernetesAdmissionFenceInertError(kind, objectName, path, 'an object', describe(match));
|
|
205
|
+
}
|
|
206
|
+
if (match.resourceRules !== undefined || rulesRequired) {
|
|
207
|
+
for (const operation of operations) {
|
|
208
|
+
const covered = rulesCover(match.resourceRules, operation);
|
|
209
|
+
if (covered !== true) {
|
|
210
|
+
fail('resourceRules', `a rule covering ${operation} of cilium.io/${CILIUM_VERSION} ${CILIUM_RESOURCE} (a binding that narrows the policy must still reach every write the host issues)`, match.resourceRules);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const excluded = match.excludeResourceRules;
|
|
215
|
+
if (excluded !== undefined) {
|
|
216
|
+
for (const operation of operations) {
|
|
217
|
+
if (rulesCover(excluded, operation) !== false) {
|
|
218
|
+
fail('excludeResourceRules', `no rule excluding ${operation} of ${CILIUM_RESOURCE}`, excluded);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// An object selector is matched against the OBJECT's labels, which the
|
|
223
|
+
// writing host chooses: a fence that applies only to objects carrying a
|
|
224
|
+
// label the host can simply leave off fences nothing.
|
|
225
|
+
const objectSelector = match.objectSelector;
|
|
226
|
+
if (objectSelector !== undefined && objectSelector !== null) {
|
|
227
|
+
const empty = isRecord(objectSelector) &&
|
|
228
|
+
Object.keys(isRecord(objectSelector.matchLabels) ? objectSelector.matchLabels : {}).length ===
|
|
229
|
+
0 &&
|
|
230
|
+
(listOf(objectSelector.matchExpressions) ?? [null]).length === 0;
|
|
231
|
+
if (!empty)
|
|
232
|
+
fail('objectSelector', "absent or empty (the host chooses its objects' labels)", objectSelector);
|
|
233
|
+
}
|
|
234
|
+
const namespaceSelector = match.namespaceSelector;
|
|
235
|
+
if (namespace !== undefined && namespaceSelector !== undefined && namespaceSelector !== null) {
|
|
236
|
+
// Matched against the one label every namespace is guaranteed to carry.
|
|
237
|
+
// A selector on any other label is not decidable from the stored
|
|
238
|
+
// objects, and an unprovable fence is an unproven one.
|
|
239
|
+
const verdict = matchesLabelSelector(namespaceSelector, {
|
|
240
|
+
'kubernetes.io/metadata.name': namespace,
|
|
241
|
+
});
|
|
242
|
+
if (verdict !== 'yes') {
|
|
243
|
+
fail('namespaceSelector', `a selector that selects namespace ${namespace} (decided from the automatic kubernetes.io/metadata.name label, the only one this host can rely on; a selector on other labels is refused as unprovable)`, namespaceSelector);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
/** The conditions the host identity is checked against, when one is configured. */
|
|
248
|
+
function assertHostIdentity(policyName, policy, hostIdentity) {
|
|
249
|
+
if (hostIdentity === undefined)
|
|
250
|
+
return;
|
|
251
|
+
const expected = `request.userInfo.username == '${hostIdentity}'`;
|
|
252
|
+
const conditions = listOf(policy.matchConditions);
|
|
253
|
+
const names = (conditions ?? []).map((c) => isRecord(c) ? normalizeExpression(c.expression) : '');
|
|
254
|
+
// Exactly that one condition: a second one can only narrow, and one that
|
|
255
|
+
// narrows to nothing fences nothing.
|
|
256
|
+
if (conditions === undefined || names.length !== 1 || names[0] !== expected) {
|
|
257
|
+
throw new KubernetesAdmissionFenceInertError('ValidatingAdmissionPolicy', policyName, 'spec.matchConditions', `exactly one condition, ${expected}`, describe(names.length === 0 ? undefined : names));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function assertFailClosed(policyName, policy) {
|
|
261
|
+
if (policy.failurePolicy !== 'Fail') {
|
|
262
|
+
throw new KubernetesAdmissionFenceInertError('ValidatingAdmissionPolicy', policyName, 'spec.failurePolicy', '"Fail" (an expression that errors must refuse the write, not admit it)', describe(policy.failurePolicy));
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function specOf(kind, objectName, object) {
|
|
266
|
+
const spec = isRecord(object) ? object.spec : undefined;
|
|
267
|
+
if (!isRecord(spec)) {
|
|
268
|
+
throw new KubernetesAdmissionFenceInertError(kind, objectName, 'spec', 'an object', describe(spec));
|
|
269
|
+
}
|
|
270
|
+
return spec;
|
|
271
|
+
}
|
|
272
|
+
function assertBindingAttaches(bindingName, spec, policyName) {
|
|
273
|
+
if (spec.policyName !== policyName) {
|
|
274
|
+
throw new KubernetesAdmissionFenceInertError('ValidatingAdmissionPolicyBinding', bindingName, 'spec.policyName', JSON.stringify(policyName), describe(spec.policyName));
|
|
275
|
+
}
|
|
276
|
+
const actions = listOf(spec.validationActions);
|
|
277
|
+
if (actions === undefined || !actions.includes('Deny')) {
|
|
278
|
+
throw new KubernetesAdmissionFenceInertError('ValidatingAdmissionPolicyBinding', bindingName, 'spec.validationActions', 'a list including "Deny" (Audit and Warn log and admit)', describe(spec.validationActions));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* The main fence: attached, denying, fail-closed, covering the namespace and
|
|
283
|
+
* every verb the host issues, and — when configured — matching the host.
|
|
284
|
+
*/
|
|
285
|
+
export function assertFenceEnforces(fence, namespace, perSandbox) {
|
|
286
|
+
assertPolicyAndBinding(fence, namespace, FENCE_OPERATIONS, perSandbox.hostIdentity);
|
|
287
|
+
}
|
|
288
|
+
function assertPolicyAndBinding(fence, namespace, operations, hostIdentity) {
|
|
289
|
+
const policy = specOf('ValidatingAdmissionPolicy', fence.policyName, fence.policy);
|
|
290
|
+
assertFailClosed(fence.policyName, policy);
|
|
291
|
+
assertMatchResources('ValidatingAdmissionPolicy', fence.policyName, 'spec.matchConstraints', policy.matchConstraints, operations, undefined, true);
|
|
292
|
+
const validations = listOf(policy.validations);
|
|
293
|
+
if (validations === undefined || validations.length === 0) {
|
|
294
|
+
throw new KubernetesAdmissionFenceInertError('ValidatingAdmissionPolicy', fence.policyName, 'spec.validations', 'at least one validation (a policy with none validates nothing)', describe(policy.validations));
|
|
295
|
+
}
|
|
296
|
+
assertHostIdentity(fence.policyName, policy, hostIdentity);
|
|
297
|
+
const binding = specOf('ValidatingAdmissionPolicyBinding', fence.bindingName, fence.binding);
|
|
298
|
+
assertBindingAttaches(fence.bindingName, binding, fence.policyName);
|
|
299
|
+
assertMatchResources('ValidatingAdmissionPolicyBinding', fence.bindingName, 'spec.matchResources', binding.matchResources, operations, namespace, false);
|
|
300
|
+
return { policy, binding };
|
|
301
|
+
}
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
// The companion: the operator-pinned host set
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
/** The companion policy's name: the main policy's, with this appended. */
|
|
306
|
+
export const PERMITTED_HOSTS_POLICY_SUFFIX = '-hosts';
|
|
307
|
+
/**
|
|
308
|
+
* The companion's names, derived rather than configured: one more name to
|
|
309
|
+
* keep in step with an applied object is one more way for the check to read
|
|
310
|
+
* the wrong object.
|
|
311
|
+
*/
|
|
312
|
+
export function permittedHostsFenceNames(perSandbox) {
|
|
313
|
+
const policyName = `${perSandbox.admissionPolicyName}${PERMITTED_HOSTS_POLICY_SUFFIX}`;
|
|
314
|
+
return { policyName, bindingName: `${policyName}-binding` };
|
|
315
|
+
}
|
|
316
|
+
/** The companion's `spec.paramKind`. */
|
|
317
|
+
export const PERMITTED_HOSTS_PARAM_KIND = { apiVersion: 'v1', kind: 'ConfigMap' };
|
|
318
|
+
/**
|
|
319
|
+
* The companion's variables and validation, VERBATIM. Owned here, compared
|
|
320
|
+
* against the cluster's copy by {@link assertPermittedHostsFenceEnforces} and
|
|
321
|
+
* against the shipped YAML by a test, so the three cannot drift: an applied
|
|
322
|
+
* companion with an edited expression is a fence this backend cannot vouch
|
|
323
|
+
* for, and is refused as one.
|
|
324
|
+
*
|
|
325
|
+
* Read as: every egress rule that carries `toFQDNs` must carry `toPorts`, every
|
|
326
|
+
* port of which is in `data.ports`; and every `toFQDNs` entry is a `matchName`
|
|
327
|
+
* in `data.hosts` or a `matchPattern` in `data.patterns`. Lists are
|
|
328
|
+
* newline-separated strings because a ConfigMap holds strings; a key that is
|
|
329
|
+
* missing makes the expression error, and `failurePolicy: Fail` makes an
|
|
330
|
+
* error a refusal.
|
|
331
|
+
*/
|
|
332
|
+
export const PERMITTED_HOSTS_VARIABLES = [
|
|
333
|
+
{
|
|
334
|
+
name: 'egress',
|
|
335
|
+
expression: 'has(object.spec) && has(object.spec.egress) ? object.spec.egress : []',
|
|
336
|
+
},
|
|
337
|
+
{ name: 'names', expression: "params.data.hosts.split('\\n')" },
|
|
338
|
+
{ name: 'patterns', expression: "params.data.patterns.split('\\n')" },
|
|
339
|
+
{ name: 'ports', expression: "params.data.ports.split('\\n')" },
|
|
340
|
+
];
|
|
341
|
+
export const PERMITTED_HOSTS_VALIDATIONS = [
|
|
342
|
+
{
|
|
343
|
+
expression: 'variables.egress.all(r, !has(r.toFQDNs) || (has(r.toPorts) && size(r.toPorts) > 0 && ' +
|
|
344
|
+
'r.toPorts.all(p, has(p.ports) && size(p.ports) > 0 && ' +
|
|
345
|
+
'p.ports.all(q, q.port in variables.ports)) && ' +
|
|
346
|
+
'r.toFQDNs.all(f, (has(f.matchName) && f.matchName in variables.names) || ' +
|
|
347
|
+
'(has(f.matchPattern) && f.matchPattern in variables.patterns))))',
|
|
348
|
+
},
|
|
349
|
+
];
|
|
350
|
+
/**
|
|
351
|
+
* The companion policy and its binding, compared to what this version ships.
|
|
352
|
+
* Everything {@link assertFenceEnforces} proves for the main fence, over
|
|
353
|
+
* CREATE and UPDATE, plus: the parameter kind, the variables and validation
|
|
354
|
+
* verbatim, and a binding whose `paramRef` is THIS ConfigMap, by name, and
|
|
355
|
+
* denies when it is missing — a binding with no parameter, or one that lets a
|
|
356
|
+
* missing parameter through, pins nothing.
|
|
357
|
+
*/
|
|
358
|
+
export function assertPermittedHostsFenceEnforces(fence, namespace, perSandbox, param) {
|
|
359
|
+
const { policy, binding } = assertPolicyAndBinding(fence, namespace, COMPANION_OPERATIONS, perSandbox.hostIdentity);
|
|
360
|
+
const policyFail = (field, expected, actual) => {
|
|
361
|
+
throw new KubernetesAdmissionFenceInertError('ValidatingAdmissionPolicy', fence.policyName, field, expected, describe(actual));
|
|
362
|
+
};
|
|
363
|
+
if (!isDeepStrictEqual(policy.paramKind, PERMITTED_HOSTS_PARAM_KIND)) {
|
|
364
|
+
policyFail('spec.paramKind', describe(PERMITTED_HOSTS_PARAM_KIND), policy.paramKind);
|
|
365
|
+
}
|
|
366
|
+
const variables = (listOf(policy.variables) ?? []).map((v) => isRecord(v) ? { name: v.name, expression: normalizeExpression(v.expression) } : v);
|
|
367
|
+
const expectedVariables = PERMITTED_HOSTS_VARIABLES.map((v) => ({
|
|
368
|
+
name: v.name,
|
|
369
|
+
expression: normalizeExpression(v.expression),
|
|
370
|
+
}));
|
|
371
|
+
if (!isDeepStrictEqual(variables, expectedVariables)) {
|
|
372
|
+
policyFail('spec.variables', describe(expectedVariables), variables);
|
|
373
|
+
}
|
|
374
|
+
const validations = (listOf(policy.validations) ?? []).map((v) => isRecord(v) ? { expression: normalizeExpression(v.expression) } : v);
|
|
375
|
+
const expectedValidations = PERMITTED_HOSTS_VALIDATIONS.map((v) => ({
|
|
376
|
+
expression: normalizeExpression(v.expression),
|
|
377
|
+
}));
|
|
378
|
+
if (!isDeepStrictEqual(validations, expectedValidations)) {
|
|
379
|
+
policyFail('spec.validations', describe(expectedValidations), validations);
|
|
380
|
+
}
|
|
381
|
+
const bindingFail = (field, expected, actual) => {
|
|
382
|
+
throw new KubernetesAdmissionFenceInertError('ValidatingAdmissionPolicyBinding', fence.bindingName, field, expected, describe(actual));
|
|
383
|
+
};
|
|
384
|
+
const paramRef = binding.paramRef;
|
|
385
|
+
if (!isRecord(paramRef)) {
|
|
386
|
+
bindingFail('spec.paramRef', `a reference to ConfigMap ${param.namespace}/${param.name} (a binding with no parameter pins no hosts)`, paramRef);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (paramRef.name !== param.name) {
|
|
390
|
+
bindingFail('spec.paramRef.name', JSON.stringify(param.name), paramRef.name);
|
|
391
|
+
}
|
|
392
|
+
if (paramRef.namespace !== param.namespace) {
|
|
393
|
+
bindingFail('spec.paramRef.namespace', JSON.stringify(param.namespace), paramRef.namespace);
|
|
394
|
+
}
|
|
395
|
+
if (paramRef.selector !== undefined) {
|
|
396
|
+
bindingFail('spec.paramRef.selector', 'absent (the parameter is named, not selected)', paramRef.selector);
|
|
397
|
+
}
|
|
398
|
+
if (paramRef.parameterNotFoundAction !== 'Deny') {
|
|
399
|
+
bindingFail('spec.paramRef.parameterNotFoundAction', '"Deny" (an allow here admits every grant while the ConfigMap is missing)', paramRef.parameterNotFoundAction);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Split one key into its entries. The admission policy splits the same string
|
|
404
|
+
* on `\n` and compares entries byte for byte, so an entry with a stray space
|
|
405
|
+
* or a `\r` would be accepted here and never match there; it is refused as
|
|
406
|
+
* malformed instead. Empty lines are dropped, which is what lets a value end
|
|
407
|
+
* in a newline.
|
|
408
|
+
*/
|
|
409
|
+
function entriesOf(param, data, key) {
|
|
410
|
+
const value = data[key];
|
|
411
|
+
if (typeof value !== 'string') {
|
|
412
|
+
throw new KubernetesPermittedHostsParamError(param, `has no data.${key} string (found ${describe(value)}); the policy's expression reads it, so a missing key denies every grant`);
|
|
413
|
+
}
|
|
414
|
+
const entries = value.split('\n').filter((line) => line !== '');
|
|
415
|
+
for (const entry of entries) {
|
|
416
|
+
if (entry.trim() !== entry) {
|
|
417
|
+
throw new KubernetesPermittedHostsParamError(param, `has a data.${key} entry with surrounding whitespace or a carriage return (${JSON.stringify(entry)}); the policy compares entries byte for byte, so it would never match`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return entries;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Parse the ConfigMap's `data`, with the same grammar the writer's hosts go
|
|
424
|
+
* through ({@link assertUsableHost}), so a parameter cannot permit a name the
|
|
425
|
+
* writer would refuse to emit.
|
|
426
|
+
*/
|
|
427
|
+
export function parsePermittedHosts(param, configMap) {
|
|
428
|
+
const data = isRecord(configMap) ? configMap.data : undefined;
|
|
429
|
+
if (!isRecord(data)) {
|
|
430
|
+
throw new KubernetesPermittedHostsParamError(param, 'has no data');
|
|
431
|
+
}
|
|
432
|
+
const bad = (what, entry, why) => {
|
|
433
|
+
throw new KubernetesPermittedHostsParamError(param, `lists ${what} ${JSON.stringify(entry)}, which ${why}`);
|
|
434
|
+
};
|
|
435
|
+
const hosts = new Set();
|
|
436
|
+
for (const entry of entriesOf(param, data, 'hosts')) {
|
|
437
|
+
if (entry.startsWith('.')) {
|
|
438
|
+
bad('host', entry, 'is a domain entry; data.hosts holds exact names, and a domain is its name here plus a *.domain line in data.patterns');
|
|
439
|
+
}
|
|
440
|
+
try {
|
|
441
|
+
assertUsableHost(entry);
|
|
442
|
+
}
|
|
443
|
+
catch (err) {
|
|
444
|
+
bad('host', entry, `is not a usable hostname (${err instanceof Error ? err.message : String(err)})`);
|
|
445
|
+
}
|
|
446
|
+
if (entry !== entry.toLowerCase())
|
|
447
|
+
bad('host', entry, 'is not lowercase; Cilium compares the lowercase name a lookup returned');
|
|
448
|
+
hosts.add(entry);
|
|
449
|
+
}
|
|
450
|
+
const patterns = new Set();
|
|
451
|
+
for (const entry of entriesOf(param, data, 'patterns')) {
|
|
452
|
+
if (!entry.startsWith('*.'))
|
|
453
|
+
bad('pattern', entry, 'is not of the form *.<domain>');
|
|
454
|
+
const domain = entry.slice(2);
|
|
455
|
+
try {
|
|
456
|
+
assertUsableHost(`.${domain}`);
|
|
457
|
+
}
|
|
458
|
+
catch (err) {
|
|
459
|
+
bad('pattern', entry, `is not a usable domain pattern (${err instanceof Error ? err.message : String(err)})`);
|
|
460
|
+
}
|
|
461
|
+
if (domain !== domain.toLowerCase())
|
|
462
|
+
bad('pattern', entry, 'is not lowercase');
|
|
463
|
+
patterns.add(entry);
|
|
464
|
+
}
|
|
465
|
+
const ports = new Set();
|
|
466
|
+
for (const entry of entriesOf(param, data, 'ports')) {
|
|
467
|
+
const port = Number(entry);
|
|
468
|
+
if (!/^[1-9][0-9]{0,4}$/.test(entry) || port > 65535) {
|
|
469
|
+
bad('port', entry, 'is not a port number (1 to 65535, written without a sign or leading zero)');
|
|
470
|
+
}
|
|
471
|
+
ports.add(port);
|
|
472
|
+
}
|
|
473
|
+
return { hosts, patterns, ports };
|
|
474
|
+
}
|
|
475
|
+
/** GET and parse the parameter. A missing or unreadable ConfigMap refuses. */
|
|
476
|
+
export async function readPermittedHosts(client, param, signal) {
|
|
477
|
+
let configMap;
|
|
478
|
+
try {
|
|
479
|
+
configMap = await client.request('GET', configMapPath(param.namespace, param.name), undefined, signal);
|
|
480
|
+
}
|
|
481
|
+
catch (err) {
|
|
482
|
+
if (err instanceof KubernetesAlreadyGoneError) {
|
|
483
|
+
throw new KubernetesPermittedHostsParamError(param, 'does not exist, so the admission policy bound to it denies every grant');
|
|
484
|
+
}
|
|
485
|
+
if (err instanceof KubernetesCredentialError) {
|
|
486
|
+
throw new KubernetesPermittedHostsParamError(param, 'cannot be read by this host (grant it get on that one ConfigMap; the companion manifest includes the Role)', err);
|
|
487
|
+
}
|
|
488
|
+
throw err;
|
|
489
|
+
}
|
|
490
|
+
return parsePermittedHosts(param, configMap);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Refuse, locally, any host or port outside the permitted set. `hosts` are the
|
|
494
|
+
* canonical `allowedHosts` entries and `portsFor` the port list the writer's
|
|
495
|
+
* narrowing will emit for an entry (`undefined`: no restriction).
|
|
496
|
+
*
|
|
497
|
+
* A `.domain` entry becomes a `matchName` plus a `matchPattern`, and the
|
|
498
|
+
* admission rule checks each against its own list, so both must be present.
|
|
499
|
+
*/
|
|
500
|
+
export function assertWithinPermittedHosts(param, permitted, hosts, portsFor) {
|
|
501
|
+
const outside = [];
|
|
502
|
+
const outsidePorts = new Set();
|
|
503
|
+
const unrestricted = [];
|
|
504
|
+
for (const host of hosts) {
|
|
505
|
+
const exact = host.startsWith('.') ? host.slice(1) : host;
|
|
506
|
+
const allowed = permitted.hosts.has(exact) && (!host.startsWith('.') || permitted.patterns.has(`*.${exact}`));
|
|
507
|
+
if (!allowed)
|
|
508
|
+
outside.push(host);
|
|
509
|
+
const ports = portsFor(host);
|
|
510
|
+
if (ports === undefined) {
|
|
511
|
+
unrestricted.push(host);
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
for (const port of ports)
|
|
515
|
+
if (!permitted.ports.has(port))
|
|
516
|
+
outsidePorts.add(port);
|
|
517
|
+
}
|
|
518
|
+
if (outside.length > 0 || outsidePorts.size > 0 || unrestricted.length > 0) {
|
|
519
|
+
throw new KubernetesPermittedHostsError(param, outside, [...outsidePorts], unrestricted);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
//# sourceMappingURL=per-sandbox-fence.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"per-sandbox-fence.js","sourceRoot":"","sources":["../../../src/backends/kubernetes/per-sandbox-fence.ts"],"names":[],"mappings":"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"}
|
|
@@ -46,7 +46,11 @@
|
|
|
46
46
|
* operator's own baseline policy. So before the FIRST write, this module
|
|
47
47
|
* proves an operator applied a `ValidatingAdmissionPolicy` and its binding,
|
|
48
48
|
* and refuses with {@link KubernetesAdmissionFenceMissingError}, having
|
|
49
|
-
* written nothing, if either is absent
|
|
49
|
+
* written nothing, if either is absent — and with
|
|
50
|
+
* {@link KubernetesAdmissionFenceInertError}, naming the field, if either
|
|
51
|
+
* exists and would not enforce (see `per-sandbox-fence.ts`: the same
|
|
52
|
+
* standard the `no-network` baseline's named-object check holds its own
|
|
53
|
+
* object to). The shipped example
|
|
50
54
|
* (`k8s/manifests/validatingadmissionpolicy-cilium.yaml`) bounds the host
|
|
51
55
|
* identity to this name prefix, an owner reference whose uid the name has to
|
|
52
56
|
* match, ONE selector label whose VALUE is that owner's own name (the shape
|
|
@@ -56,6 +60,18 @@
|
|
|
56
60
|
* kube-dns rule on port 53, no address-based or entity-based peers and no
|
|
57
61
|
* ingress.
|
|
58
62
|
*
|
|
63
|
+
* ## Verified after every write
|
|
64
|
+
*
|
|
65
|
+
* Reading the object back proves the object is the one that was sent. It says
|
|
66
|
+
* nothing about the BOUNDARY, which is the union of every policy selecting the
|
|
67
|
+
* pod: a policy applied between this sandbox's create and its grant that
|
|
68
|
+
* allows more is invisible to a read-back of this one. So after the read-back
|
|
69
|
+
* the union is enumerated again, against the baseline translation plus the
|
|
70
|
+
* policy just written (`unionEgressAllowance`), and if anything allows more
|
|
71
|
+
* the object is deleted before the error is thrown — the per-sandbox
|
|
72
|
+
* analogue of the baseline's "no sandbox is handed back". An empty list is not
|
|
73
|
+
* re-checked: a deletion only narrows.
|
|
74
|
+
*
|
|
59
75
|
* ## Union, not replacement
|
|
60
76
|
*
|
|
61
77
|
* The cluster unions every policy selecting a pod, so what this writes ADDS
|
|
@@ -78,9 +94,10 @@
|
|
|
78
94
|
* network, and it needs a cluster that enforces plus a positive control.
|
|
79
95
|
*/
|
|
80
96
|
import type { SandboxNetworkPolicy } from '@namzu/sdk';
|
|
81
|
-
import { type KubernetesEgressConfig, KubernetesNetworkPolicyHostError, type KubernetesPerSandboxEgressConfig } from './egress-policy.js';
|
|
97
|
+
import { type KubernetesEgressConfig, KubernetesNetworkPolicyHostError, type KubernetesPerSandboxEgressConfig, type KubernetesTranslatedEgressPolicy } from './egress-policy.js';
|
|
82
98
|
import { type KubernetesClient } from './k8s-client.js';
|
|
83
99
|
import { type KubernetesOwnerReference } from './objects.js';
|
|
100
|
+
import { KubernetesAdmissionFenceInertError, KubernetesPermittedHostsError, KubernetesPermittedHostsParamError } from './per-sandbox-fence.js';
|
|
84
101
|
/**
|
|
85
102
|
* Every per-sandbox policy's name starts with this, and the shipped admission
|
|
86
103
|
* policy refuses one that does not.
|
|
@@ -162,6 +179,7 @@ export declare class KubernetesAdmissionFenceMissingError extends Error {
|
|
|
162
179
|
* package index — keeps resolving.
|
|
163
180
|
*/
|
|
164
181
|
export { KubernetesNetworkPolicyHostError };
|
|
182
|
+
export { KubernetesAdmissionFenceInertError, KubernetesPermittedHostsError, KubernetesPermittedHostsParamError, };
|
|
165
183
|
/**
|
|
166
184
|
* Raised when the object an acquire created reported no `metadata.uid`.
|
|
167
185
|
*
|
|
@@ -187,7 +205,7 @@ export declare class KubernetesOwnerUidMissingError extends Error {
|
|
|
187
205
|
export interface PerSandboxPolicyFence {
|
|
188
206
|
assertApplied(signal?: AbortSignal): Promise<void>;
|
|
189
207
|
}
|
|
190
|
-
export declare function buildAdmissionFence(client: KubernetesClient, perSandbox: KubernetesPerSandboxEgressConfig): PerSandboxPolicyFence;
|
|
208
|
+
export declare function buildAdmissionFence(client: KubernetesClient, perSandbox: KubernetesPerSandboxEgressConfig, namespace: string): PerSandboxPolicyFence;
|
|
191
209
|
/** What one sandbox's policy writer needs to know. */
|
|
192
210
|
export interface PerSandboxPolicySetterOptions {
|
|
193
211
|
readonly client: KubernetesClient;
|
|
@@ -205,6 +223,30 @@ export interface PerSandboxPolicySetterOptions {
|
|
|
205
223
|
* config, through the one resolver.
|
|
206
224
|
*/
|
|
207
225
|
readonly selectorValue: string;
|
|
226
|
+
/**
|
|
227
|
+
* The post-write union check: given the policy just written and read back,
|
|
228
|
+
* prove that the union of every policy selecting the pod is still the
|
|
229
|
+
* baseline plus that policy, and throw if any other one allows more.
|
|
230
|
+
* Absent when the deployment opted out of union verification
|
|
231
|
+
* (`egress.verify: 'named-object-only'`), exactly as the create path's
|
|
232
|
+
* union check is.
|
|
233
|
+
*/
|
|
234
|
+
readonly verifyUnion?: (written: KubernetesTranslatedEgressPolicy, signal?: AbortSignal) => Promise<void>;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Raised when a per-sandbox policy was written, FAILED a post-write check, and
|
|
238
|
+
* the attempt to remove it failed too. The refusal that triggered the removal
|
|
239
|
+
* is `cause`; the policy may still be in force, and the message says so,
|
|
240
|
+
* because the one thing a caller must not conclude is that the refusal left the
|
|
241
|
+
* cluster as it found it.
|
|
242
|
+
*/
|
|
243
|
+
export declare class KubernetesPerSandboxPolicyNotRemovedError extends Error {
|
|
244
|
+
readonly policyName: string;
|
|
245
|
+
readonly path: string;
|
|
246
|
+
readonly refusal: unknown;
|
|
247
|
+
readonly removal: unknown;
|
|
248
|
+
readonly name = "KubernetesPerSandboxPolicyNotRemovedError";
|
|
249
|
+
constructor(policyName: string, path: string, refusal: unknown, removal: unknown);
|
|
208
250
|
}
|
|
209
251
|
/**
|
|
210
252
|
* Build the `setNetworkPolicy` this backend attaches to a sandbox handle.
|