@namzu/sandbox 14.0.0 → 15.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 +838 -0
- package/README.md +310 -14
- package/dist/backends/aci-standby-pool/index.d.ts.map +1 -1
- package/dist/backends/aci-standby-pool/index.js +13 -1
- package/dist/backends/aci-standby-pool/index.js.map +1 -1
- package/dist/backends/docker/index.d.ts.map +1 -1
- package/dist/backends/docker/index.js +19 -1
- package/dist/backends/docker/index.js.map +1 -1
- package/dist/backends/firecracker/index.d.ts.map +1 -1
- package/dist/backends/firecracker/index.js +12 -2
- package/dist/backends/firecracker/index.js.map +1 -1
- package/dist/backends/firecracker/protocol.d.ts +459 -8
- package/dist/backends/firecracker/protocol.d.ts.map +1 -1
- package/dist/backends/firecracker/protocol.js +136 -0
- package/dist/backends/firecracker/protocol.js.map +1 -1
- package/dist/backends/firecracker/transport.d.ts +539 -6
- package/dist/backends/firecracker/transport.d.ts.map +1 -1
- package/dist/backends/firecracker/transport.js +1171 -24
- package/dist/backends/firecracker/transport.js.map +1 -1
- package/dist/backends/kubernetes/egress-policy.d.ts +1088 -11
- package/dist/backends/kubernetes/egress-policy.d.ts.map +1 -1
- package/dist/backends/kubernetes/egress-policy.js +2173 -29
- package/dist/backends/kubernetes/egress-policy.js.map +1 -1
- package/dist/backends/kubernetes/identity.d.ts +193 -0
- package/dist/backends/kubernetes/identity.d.ts.map +1 -0
- package/dist/backends/kubernetes/identity.js +147 -0
- package/dist/backends/kubernetes/identity.js.map +1 -0
- package/dist/backends/kubernetes/index.d.ts +678 -33
- package/dist/backends/kubernetes/index.d.ts.map +1 -1
- package/dist/backends/kubernetes/index.js +1180 -95
- package/dist/backends/kubernetes/index.js.map +1 -1
- package/dist/backends/kubernetes/ingress-policy.d.ts +375 -0
- package/dist/backends/kubernetes/ingress-policy.d.ts.map +1 -0
- package/dist/backends/kubernetes/ingress-policy.js +1050 -0
- package/dist/backends/kubernetes/ingress-policy.js.map +1 -0
- package/dist/backends/kubernetes/k8s-client.d.ts +213 -4
- package/dist/backends/kubernetes/k8s-client.d.ts.map +1 -1
- package/dist/backends/kubernetes/k8s-client.js +359 -52
- package/dist/backends/kubernetes/k8s-client.js.map +1 -1
- package/dist/backends/kubernetes/lease.d.ts +40 -14
- package/dist/backends/kubernetes/lease.d.ts.map +1 -1
- package/dist/backends/kubernetes/lease.js +68 -18
- package/dist/backends/kubernetes/lease.js.map +1 -1
- package/dist/backends/kubernetes/objects.d.ts +423 -3
- package/dist/backends/kubernetes/objects.d.ts.map +1 -1
- package/dist/backends/kubernetes/objects.js +364 -2
- package/dist/backends/kubernetes/objects.js.map +1 -1
- package/dist/backends/kubernetes/per-sandbox-policy.d.ts +219 -0
- package/dist/backends/kubernetes/per-sandbox-policy.d.ts.map +1 -0
- package/dist/backends/kubernetes/per-sandbox-policy.js +407 -0
- package/dist/backends/kubernetes/per-sandbox-policy.js.map +1 -0
- package/dist/backends/kubernetes/rbac.d.ts +153 -0
- package/dist/backends/kubernetes/rbac.d.ts.map +1 -0
- package/dist/backends/kubernetes/rbac.js +177 -0
- package/dist/backends/kubernetes/rbac.js.map +1 -0
- package/dist/backends/kubernetes/sandbox.d.ts +81 -14
- package/dist/backends/kubernetes/sandbox.d.ts.map +1 -1
- package/dist/backends/kubernetes/sandbox.js +149 -15
- package/dist/backends/kubernetes/sandbox.js.map +1 -1
- package/dist/backends/kubernetes/transport.d.ts +935 -9
- package/dist/backends/kubernetes/transport.d.ts.map +1 -1
- package/dist/backends/kubernetes/transport.js +1958 -62
- package/dist/backends/kubernetes/transport.js.map +1 -1
- package/dist/backends/kubernetes/workspace.d.ts +1149 -18
- package/dist/backends/kubernetes/workspace.d.ts.map +1 -1
- package/dist/backends/kubernetes/workspace.js +2825 -186
- package/dist/backends/kubernetes/workspace.js.map +1 -1
- package/dist/backends/remote-execution-controller.d.ts +14 -0
- package/dist/backends/remote-execution-controller.d.ts.map +1 -1
- package/dist/backends/remote-execution-controller.js.map +1 -1
- package/dist/index.d.ts +231 -13
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +247 -5
- package/dist/index.js.map +1 -1
- package/dist/testing/sandbox-conformance.d.ts +39 -5
- package/dist/testing/sandbox-conformance.d.ts.map +1 -1
- package/dist/testing/sandbox-conformance.js +436 -5
- package/dist/testing/sandbox-conformance.js.map +1 -1
- package/package.json +3 -3
- package/src/backends/aci-standby-pool/index.ts +16 -1
- package/src/backends/docker/index.ts +22 -1
- package/src/backends/firecracker/index.ts +14 -2
- package/src/backends/firecracker/protocol.ts +514 -6
- package/src/backends/firecracker/transport.ts +1492 -40
- package/src/backends/kubernetes/egress-policy.ts +3064 -53
- package/src/backends/kubernetes/identity.ts +261 -0
- package/src/backends/kubernetes/index.ts +1785 -127
- package/src/backends/kubernetes/ingress-policy.ts +1344 -0
- package/src/backends/kubernetes/k8s-client.ts +444 -54
- package/src/backends/kubernetes/lease.ts +75 -19
- package/src/backends/kubernetes/objects.ts +626 -6
- package/src/backends/kubernetes/per-sandbox-policy.ts +542 -0
- package/src/backends/kubernetes/rbac.ts +192 -0
- package/src/backends/kubernetes/sandbox.ts +218 -20
- package/src/backends/kubernetes/transport.ts +2733 -124
- package/src/backends/kubernetes/workspace.ts +4476 -222
- package/src/backends/remote-execution-controller.ts +14 -0
- package/src/index.ts +595 -14
- package/src/testing/sandbox-conformance.ts +540 -5
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The verbs a CLAIM-ONLY host issues — the pool-only path, written down once
|
|
3
|
+
* so `k8s/manifests/rbac-claimant.yaml`, and the test that compares it to both
|
|
4
|
+
* this list and the call sites behind it, cannot drift from the code they
|
|
5
|
+
* describe.
|
|
6
|
+
*
|
|
7
|
+
* A host that sets `warmPoolName` and never creates a `Sandbox` itself needs
|
|
8
|
+
* a strictly smaller grant than `k8s/manifests/rbac.yaml`'s, and the smaller
|
|
9
|
+
* it is the more it matters that it is EXACT. Two failure directions, and
|
|
10
|
+
* this constant is the one place both are decided:
|
|
11
|
+
*
|
|
12
|
+
* - **Too few verbs** is a `403` on a flow nobody ran in review. The four
|
|
13
|
+
* `list` verbs are the load-bearing ones, and the two a host can go a long
|
|
14
|
+
* time without noticing are the sharp ones: `sandboxclaims: list` is the
|
|
15
|
+
* crash-recovery read (`releaseKubernetesTaskSandboxes`) and the
|
|
16
|
+
* pool-membership read (`readKubernetesTaskCapacity`), and `pods: list` is
|
|
17
|
+
* `readBoundPod`'s fallback read by the bound Sandbox's own selector and
|
|
18
|
+
* `readKubernetesTaskCapacity`'s count of Pending pods. The ACQUIRE path
|
|
19
|
+
* never notices either is missing — `create()` never lists either
|
|
20
|
+
* collection, so a claim is still created, bound, renewed and released
|
|
21
|
+
* exactly as before — which makes the failure DEFERRED rather than quiet:
|
|
22
|
+
* the `await client.request` inside each of those two functions rejects
|
|
23
|
+
* with a `KubernetesCredentialError` (`403`), and neither call catches one,
|
|
24
|
+
* so the first release or capacity read a host makes fails loudly, with the
|
|
25
|
+
* refused verb and the collection's path in the error. The other two are
|
|
26
|
+
* load-bearing SOONER: `list` is what both policy checks need, and the
|
|
27
|
+
* ingress one runs by default before every create.
|
|
28
|
+
* - **One verb too many** is the vulnerability class issue #500 is about: a
|
|
29
|
+
* `sandboxes: create` grant against a namespace running a privileged
|
|
30
|
+
* workspace template is an arbitrary privileged pod for anyone holding
|
|
31
|
+
* it. Every verb below has a call site named against it in the list.
|
|
32
|
+
*
|
|
33
|
+
* The path this describes is the CLAIM path, not the task tier as a whole:
|
|
34
|
+
* with `warmPoolName` unset the same backend POSTs a `Sandbox` instead of a
|
|
35
|
+
* claim, reads its `SandboxTemplate` first, and patches and deletes that
|
|
36
|
+
* object — none of which is granted here, deliberately. See the manifest's
|
|
37
|
+
* own header for what it declines to grant and why.
|
|
38
|
+
*
|
|
39
|
+
* Kept honest by a test rather than by review, and the test reaches the CODE
|
|
40
|
+
* rather than only this list: `k8s/__tests__/manifests.test.ts` reads the
|
|
41
|
+
* pool-only path's own files — `index.ts`, `objects.ts`, `ingress-policy.ts`,
|
|
42
|
+
* `egress-policy.ts` — as text, resolves every literal `client.request(
|
|
43
|
+
* '<METHOD>', <path>, …)` in them to the `(apiGroup, resource, verb)` triple
|
|
44
|
+
* the builder that path goes through addresses, and FAILS on anything it
|
|
45
|
+
* cannot resolve — an unknown builder, a path it cannot follow, a builder
|
|
46
|
+
* call with anything appended to it (`podPath(ns, name) + '/log'` is the
|
|
47
|
+
* `pods/log` subresource, which RBAC authorizes separately), a name the source
|
|
48
|
+
* writes a second path to — the guard reads four spellings, wherever each is
|
|
49
|
+
* written rather than only where it opens a statement: a plain assignment
|
|
50
|
+
* (`path = …`, the one-line branch included), a compound one (`path += …`), a
|
|
51
|
+
* `for (path of …)` binding, and a destructuring target (`({ path } = …)`) — as
|
|
52
|
+
* a `const`/`let`/`var`-declared name, whose initializer is then not what a
|
|
53
|
+
* request through it sends, or as a wrapper's own path parameter, whose call
|
|
54
|
+
* sites then do not say what it sends — a path
|
|
55
|
+
* that is a parameter of a body whose call sites the scan has not been told
|
|
56
|
+
* about, a wrapper declared as such in a file the scan does not read — so a new
|
|
57
|
+
* request has to declare itself instead of escaping. WHICH files those are
|
|
58
|
+
* is not a list that can quietly stop covering the path: every `.ts` under
|
|
59
|
+
* `src/backends/kubernetes/` that reaches the client — calling
|
|
60
|
+
* `client.request(…)` itself in either spelling the scan reads, the literal
|
|
61
|
+
* one or a computed `client['request']`, or naming `listPolicies`, the one
|
|
62
|
+
* wrapper the scan knows, which takes its path as an argument; every half read
|
|
63
|
+
* on text with its comments blanked — is either scanned or declared off the
|
|
64
|
+
* path with its reason, so a file that reaches the client fails that test
|
|
65
|
+
* until someone decides which of the two it is. The triples it
|
|
66
|
+
* observes must be exactly this list, plus the four the pool-LESS branch
|
|
67
|
+
* issues and this grant declines by design. A request added to the pool-only
|
|
68
|
+
* path therefore fails that test naming the resource, the file and the line,
|
|
69
|
+
* instead of surfacing as a `403` in a host's log — and so does a rule here
|
|
70
|
+
* with no call site, which is a grant wider than the code needs.
|
|
71
|
+
*
|
|
72
|
+
* What that test still cannot see, so none of the above reads stronger than
|
|
73
|
+
* it is: the files it declares off the path — `workspace.ts`, `k8s-client.ts`,
|
|
74
|
+
* `transport.ts` — are never resolved to triples, a wrapper call site in one
|
|
75
|
+
* of them included, since call sites are read from the scanned files only; and
|
|
76
|
+
* that a claimant host never reaches `workspace.ts` is a claim about the
|
|
77
|
+
* DEPLOYMENT (`warmPoolName` set, no `Sandbox` created by that host), which
|
|
78
|
+
* nothing in this repository measures. A wrapper reached by ANOTHER name is outside that file predicate,
|
|
79
|
+
* which matches the wrapper's name and not its identity: a file importing a
|
|
80
|
+
* re-export of it, or calling a helper one level further out that calls it, is
|
|
81
|
+
* neither scanned nor required to be declared. A request whose path is a
|
|
82
|
+
* parameter of an expression-bodied arrow is likewise outside what the scan
|
|
83
|
+
* matches, and a body the scan could not register — a class method, or an
|
|
84
|
+
* object literal's shorthand method, declared INSIDE one of the bodies it
|
|
85
|
+
* reads — is attributed to the body that encloses it, so its own parameters
|
|
86
|
+
* are invisible. The assignment guard is by name in the file it resolves in,
|
|
87
|
+
* not by scope, so a BINDING it registers nowhere shadows nothing there: a
|
|
88
|
+
* destructuring parameter (`function f({ path })`) keeps its positional slot
|
|
89
|
+
* without a name this scan can match, a `catch (path)` is not read at all, and
|
|
90
|
+
* a write made to an exported binding from ANOTHER module is not in that file's
|
|
91
|
+
* text — which is also to say that an assignment to a same-named local in an
|
|
92
|
+
* unrelated function of the same file refuses the request too, a false refusal
|
|
93
|
+
* rather than an escape. A parameter carrying a DEFAULT is written with an `=`
|
|
94
|
+
* (`function f(path = …)`, `function f({ path } = {})`), so the guard reads it
|
|
95
|
+
* and refuses the request rather than resolving it — a refusal, where the bare
|
|
96
|
+
* pattern would have resolved or failed; and a destructuring pattern nested
|
|
97
|
+
* inside another (`({ a: { path } } = …)`) is read by neither spelling, which
|
|
98
|
+
* is measured green.
|
|
99
|
+
*
|
|
100
|
+
* **That list is the SHORT form, not the list.** It is carried in full in the
|
|
101
|
+
* header of `k8s/__tests__/manifests.test.ts`, which also holds two bullets
|
|
102
|
+
* this paragraph does not: `a path assembled by an operation the scan does not
|
|
103
|
+
* model`, and `the cluster` (nothing there contacts an API server, so no live
|
|
104
|
+
* `Role` is measured).
|
|
105
|
+
*
|
|
106
|
+
* Exported from the package root because the grant is a property of the
|
|
107
|
+
* backend, not of a file this package does not publish: `k8s/` is outside
|
|
108
|
+
* the `files` array, so a consumer cannot read the YAML out of `node_modules`
|
|
109
|
+
* — it can, however, compare its own applied `Role` against this.
|
|
110
|
+
*/
|
|
111
|
+
/** An RBAC verb this backend issues. No wildcard, and none is ever added. */
|
|
112
|
+
export type KubernetesRbacVerb = 'create' | 'get' | 'list' | 'patch' | 'delete';
|
|
113
|
+
/**
|
|
114
|
+
* One `rules[]` entry, in the shape RBAC itself uses — one resource per
|
|
115
|
+
* entry, which is also how the shipped manifests are written, so a rule here
|
|
116
|
+
* and a rule there compare field for field.
|
|
117
|
+
*/
|
|
118
|
+
export interface KubernetesRbacRule {
|
|
119
|
+
/** The `apiGroups` entry, verbatim: `''` for the core group. */
|
|
120
|
+
readonly apiGroup: string;
|
|
121
|
+
readonly resource: string;
|
|
122
|
+
readonly verbs: readonly KubernetesRbacVerb[];
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Every verb the pool-only path issues, each pinned to its call site in
|
|
126
|
+
* `./index.ts` (and, for the policy checks, `./ingress-policy.ts` /
|
|
127
|
+
* `./egress-policy.ts`):
|
|
128
|
+
*
|
|
129
|
+
* - `sandboxclaims` — `create` is the acquire's POST (`createPath`), `get`
|
|
130
|
+
* the readiness poll that waits for the controller to bind one, `patch`
|
|
131
|
+
* the lease renewal that keeps a long run's claim alive, `delete` the
|
|
132
|
+
* release `destroy()` and a failed acquire's cleanup both send, and
|
|
133
|
+
* `list` the two host-invoked reads named above.
|
|
134
|
+
* - `sandboxes: get` — read for `status.selector` when the pod-by-name GET
|
|
135
|
+
* comes back gone, which on the claim path is the ONLY way to find the
|
|
136
|
+
* bound pod: a claim has no `podSelector` of its own to fall back on.
|
|
137
|
+
* - `sandboxwarmpools: get` — `readKubernetesTaskCapacity` reads the
|
|
138
|
+
* configured pool's own `status.readyReplicas`/`spec.replicas` before a
|
|
139
|
+
* host admits more work.
|
|
140
|
+
* - `pods` — `get` by name for the agent's bind token and, under
|
|
141
|
+
* `agentAddress: 'pod-ip'`, its address, plus the whole-namespace `list`
|
|
142
|
+
* that counts in-flight scale-up. The two policy checks use NO pod
|
|
143
|
+
* subresource; the agent's protocol is a socket the guest serves, not
|
|
144
|
+
* `pods/exec`.
|
|
145
|
+
* - `networkpolicies` / `ciliumnetworkpolicies` — `list` is the ingress
|
|
146
|
+
* coverage check (on by default before every create) and the egress UNION
|
|
147
|
+
* check (on by default whenever `config.egress` is set); `get` is the
|
|
148
|
+
* egress named-object check, also only under `config.egress`. Under
|
|
149
|
+
* `engine: 'core'` the Cilium rule simply never matches, and a cluster
|
|
150
|
+
* with no Cilium CRDs installed never has an object to match it.
|
|
151
|
+
*/
|
|
152
|
+
export declare const KUBERNETES_CLAIMANT_RBAC_RULES: readonly KubernetesRbacRule[];
|
|
153
|
+
//# sourceMappingURL=rbac.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rbac.d.ts","sourceRoot":"","sources":["../../../src/backends/kubernetes/rbac.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6GG;AAEH,6EAA6E;AAC7E,MAAM,MAAM,kBAAkB,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAA;AAE/E;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IAClC,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAA;CAC7C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,8BAA8B,EAAE,SAAS,kBAAkB,EAqCtE,CAAA"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The verbs a CLAIM-ONLY host issues — the pool-only path, written down once
|
|
3
|
+
* so `k8s/manifests/rbac-claimant.yaml`, and the test that compares it to both
|
|
4
|
+
* this list and the call sites behind it, cannot drift from the code they
|
|
5
|
+
* describe.
|
|
6
|
+
*
|
|
7
|
+
* A host that sets `warmPoolName` and never creates a `Sandbox` itself needs
|
|
8
|
+
* a strictly smaller grant than `k8s/manifests/rbac.yaml`'s, and the smaller
|
|
9
|
+
* it is the more it matters that it is EXACT. Two failure directions, and
|
|
10
|
+
* this constant is the one place both are decided:
|
|
11
|
+
*
|
|
12
|
+
* - **Too few verbs** is a `403` on a flow nobody ran in review. The four
|
|
13
|
+
* `list` verbs are the load-bearing ones, and the two a host can go a long
|
|
14
|
+
* time without noticing are the sharp ones: `sandboxclaims: list` is the
|
|
15
|
+
* crash-recovery read (`releaseKubernetesTaskSandboxes`) and the
|
|
16
|
+
* pool-membership read (`readKubernetesTaskCapacity`), and `pods: list` is
|
|
17
|
+
* `readBoundPod`'s fallback read by the bound Sandbox's own selector and
|
|
18
|
+
* `readKubernetesTaskCapacity`'s count of Pending pods. The ACQUIRE path
|
|
19
|
+
* never notices either is missing — `create()` never lists either
|
|
20
|
+
* collection, so a claim is still created, bound, renewed and released
|
|
21
|
+
* exactly as before — which makes the failure DEFERRED rather than quiet:
|
|
22
|
+
* the `await client.request` inside each of those two functions rejects
|
|
23
|
+
* with a `KubernetesCredentialError` (`403`), and neither call catches one,
|
|
24
|
+
* so the first release or capacity read a host makes fails loudly, with the
|
|
25
|
+
* refused verb and the collection's path in the error. The other two are
|
|
26
|
+
* load-bearing SOONER: `list` is what both policy checks need, and the
|
|
27
|
+
* ingress one runs by default before every create.
|
|
28
|
+
* - **One verb too many** is the vulnerability class issue #500 is about: a
|
|
29
|
+
* `sandboxes: create` grant against a namespace running a privileged
|
|
30
|
+
* workspace template is an arbitrary privileged pod for anyone holding
|
|
31
|
+
* it. Every verb below has a call site named against it in the list.
|
|
32
|
+
*
|
|
33
|
+
* The path this describes is the CLAIM path, not the task tier as a whole:
|
|
34
|
+
* with `warmPoolName` unset the same backend POSTs a `Sandbox` instead of a
|
|
35
|
+
* claim, reads its `SandboxTemplate` first, and patches and deletes that
|
|
36
|
+
* object — none of which is granted here, deliberately. See the manifest's
|
|
37
|
+
* own header for what it declines to grant and why.
|
|
38
|
+
*
|
|
39
|
+
* Kept honest by a test rather than by review, and the test reaches the CODE
|
|
40
|
+
* rather than only this list: `k8s/__tests__/manifests.test.ts` reads the
|
|
41
|
+
* pool-only path's own files — `index.ts`, `objects.ts`, `ingress-policy.ts`,
|
|
42
|
+
* `egress-policy.ts` — as text, resolves every literal `client.request(
|
|
43
|
+
* '<METHOD>', <path>, …)` in them to the `(apiGroup, resource, verb)` triple
|
|
44
|
+
* the builder that path goes through addresses, and FAILS on anything it
|
|
45
|
+
* cannot resolve — an unknown builder, a path it cannot follow, a builder
|
|
46
|
+
* call with anything appended to it (`podPath(ns, name) + '/log'` is the
|
|
47
|
+
* `pods/log` subresource, which RBAC authorizes separately), a name the source
|
|
48
|
+
* writes a second path to — the guard reads four spellings, wherever each is
|
|
49
|
+
* written rather than only where it opens a statement: a plain assignment
|
|
50
|
+
* (`path = …`, the one-line branch included), a compound one (`path += …`), a
|
|
51
|
+
* `for (path of …)` binding, and a destructuring target (`({ path } = …)`) — as
|
|
52
|
+
* a `const`/`let`/`var`-declared name, whose initializer is then not what a
|
|
53
|
+
* request through it sends, or as a wrapper's own path parameter, whose call
|
|
54
|
+
* sites then do not say what it sends — a path
|
|
55
|
+
* that is a parameter of a body whose call sites the scan has not been told
|
|
56
|
+
* about, a wrapper declared as such in a file the scan does not read — so a new
|
|
57
|
+
* request has to declare itself instead of escaping. WHICH files those are
|
|
58
|
+
* is not a list that can quietly stop covering the path: every `.ts` under
|
|
59
|
+
* `src/backends/kubernetes/` that reaches the client — calling
|
|
60
|
+
* `client.request(…)` itself in either spelling the scan reads, the literal
|
|
61
|
+
* one or a computed `client['request']`, or naming `listPolicies`, the one
|
|
62
|
+
* wrapper the scan knows, which takes its path as an argument; every half read
|
|
63
|
+
* on text with its comments blanked — is either scanned or declared off the
|
|
64
|
+
* path with its reason, so a file that reaches the client fails that test
|
|
65
|
+
* until someone decides which of the two it is. The triples it
|
|
66
|
+
* observes must be exactly this list, plus the four the pool-LESS branch
|
|
67
|
+
* issues and this grant declines by design. A request added to the pool-only
|
|
68
|
+
* path therefore fails that test naming the resource, the file and the line,
|
|
69
|
+
* instead of surfacing as a `403` in a host's log — and so does a rule here
|
|
70
|
+
* with no call site, which is a grant wider than the code needs.
|
|
71
|
+
*
|
|
72
|
+
* What that test still cannot see, so none of the above reads stronger than
|
|
73
|
+
* it is: the files it declares off the path — `workspace.ts`, `k8s-client.ts`,
|
|
74
|
+
* `transport.ts` — are never resolved to triples, a wrapper call site in one
|
|
75
|
+
* of them included, since call sites are read from the scanned files only; and
|
|
76
|
+
* that a claimant host never reaches `workspace.ts` is a claim about the
|
|
77
|
+
* DEPLOYMENT (`warmPoolName` set, no `Sandbox` created by that host), which
|
|
78
|
+
* nothing in this repository measures. A wrapper reached by ANOTHER name is outside that file predicate,
|
|
79
|
+
* which matches the wrapper's name and not its identity: a file importing a
|
|
80
|
+
* re-export of it, or calling a helper one level further out that calls it, is
|
|
81
|
+
* neither scanned nor required to be declared. A request whose path is a
|
|
82
|
+
* parameter of an expression-bodied arrow is likewise outside what the scan
|
|
83
|
+
* matches, and a body the scan could not register — a class method, or an
|
|
84
|
+
* object literal's shorthand method, declared INSIDE one of the bodies it
|
|
85
|
+
* reads — is attributed to the body that encloses it, so its own parameters
|
|
86
|
+
* are invisible. The assignment guard is by name in the file it resolves in,
|
|
87
|
+
* not by scope, so a BINDING it registers nowhere shadows nothing there: a
|
|
88
|
+
* destructuring parameter (`function f({ path })`) keeps its positional slot
|
|
89
|
+
* without a name this scan can match, a `catch (path)` is not read at all, and
|
|
90
|
+
* a write made to an exported binding from ANOTHER module is not in that file's
|
|
91
|
+
* text — which is also to say that an assignment to a same-named local in an
|
|
92
|
+
* unrelated function of the same file refuses the request too, a false refusal
|
|
93
|
+
* rather than an escape. A parameter carrying a DEFAULT is written with an `=`
|
|
94
|
+
* (`function f(path = …)`, `function f({ path } = {})`), so the guard reads it
|
|
95
|
+
* and refuses the request rather than resolving it — a refusal, where the bare
|
|
96
|
+
* pattern would have resolved or failed; and a destructuring pattern nested
|
|
97
|
+
* inside another (`({ a: { path } } = …)`) is read by neither spelling, which
|
|
98
|
+
* is measured green.
|
|
99
|
+
*
|
|
100
|
+
* **That list is the SHORT form, not the list.** It is carried in full in the
|
|
101
|
+
* header of `k8s/__tests__/manifests.test.ts`, which also holds two bullets
|
|
102
|
+
* this paragraph does not: `a path assembled by an operation the scan does not
|
|
103
|
+
* model`, and `the cluster` (nothing there contacts an API server, so no live
|
|
104
|
+
* `Role` is measured).
|
|
105
|
+
*
|
|
106
|
+
* Exported from the package root because the grant is a property of the
|
|
107
|
+
* backend, not of a file this package does not publish: `k8s/` is outside
|
|
108
|
+
* the `files` array, so a consumer cannot read the YAML out of `node_modules`
|
|
109
|
+
* — it can, however, compare its own applied `Role` against this.
|
|
110
|
+
*/
|
|
111
|
+
/**
|
|
112
|
+
* Every verb the pool-only path issues, each pinned to its call site in
|
|
113
|
+
* `./index.ts` (and, for the policy checks, `./ingress-policy.ts` /
|
|
114
|
+
* `./egress-policy.ts`):
|
|
115
|
+
*
|
|
116
|
+
* - `sandboxclaims` — `create` is the acquire's POST (`createPath`), `get`
|
|
117
|
+
* the readiness poll that waits for the controller to bind one, `patch`
|
|
118
|
+
* the lease renewal that keeps a long run's claim alive, `delete` the
|
|
119
|
+
* release `destroy()` and a failed acquire's cleanup both send, and
|
|
120
|
+
* `list` the two host-invoked reads named above.
|
|
121
|
+
* - `sandboxes: get` — read for `status.selector` when the pod-by-name GET
|
|
122
|
+
* comes back gone, which on the claim path is the ONLY way to find the
|
|
123
|
+
* bound pod: a claim has no `podSelector` of its own to fall back on.
|
|
124
|
+
* - `sandboxwarmpools: get` — `readKubernetesTaskCapacity` reads the
|
|
125
|
+
* configured pool's own `status.readyReplicas`/`spec.replicas` before a
|
|
126
|
+
* host admits more work.
|
|
127
|
+
* - `pods` — `get` by name for the agent's bind token and, under
|
|
128
|
+
* `agentAddress: 'pod-ip'`, its address, plus the whole-namespace `list`
|
|
129
|
+
* that counts in-flight scale-up. The two policy checks use NO pod
|
|
130
|
+
* subresource; the agent's protocol is a socket the guest serves, not
|
|
131
|
+
* `pods/exec`.
|
|
132
|
+
* - `networkpolicies` / `ciliumnetworkpolicies` — `list` is the ingress
|
|
133
|
+
* coverage check (on by default before every create) and the egress UNION
|
|
134
|
+
* check (on by default whenever `config.egress` is set); `get` is the
|
|
135
|
+
* egress named-object check, also only under `config.egress`. Under
|
|
136
|
+
* `engine: 'core'` the Cilium rule simply never matches, and a cluster
|
|
137
|
+
* with no Cilium CRDs installed never has an object to match it.
|
|
138
|
+
*/
|
|
139
|
+
export const KUBERNETES_CLAIMANT_RBAC_RULES = Object.freeze([
|
|
140
|
+
// Every RULE is frozen too, not just the array and the verb lists: a
|
|
141
|
+
// frozen array still hands out mutable elements, so a plain-JS consumer
|
|
142
|
+
// could rewrite `rule.resource` and compare its live `Role` against a
|
|
143
|
+
// grant this module never declared. The test pins that (see
|
|
144
|
+
// `k8s/__tests__/manifests.test.ts`), because a `readonly` modifier is
|
|
145
|
+
// TypeScript's promise and not the runtime's.
|
|
146
|
+
Object.freeze({
|
|
147
|
+
apiGroup: 'extensions.agents.x-k8s.io',
|
|
148
|
+
resource: 'sandboxclaims',
|
|
149
|
+
verbs: Object.freeze(['create', 'get', 'list', 'patch', 'delete']),
|
|
150
|
+
}),
|
|
151
|
+
Object.freeze({
|
|
152
|
+
apiGroup: 'agents.x-k8s.io',
|
|
153
|
+
resource: 'sandboxes',
|
|
154
|
+
verbs: Object.freeze(['get']),
|
|
155
|
+
}),
|
|
156
|
+
Object.freeze({
|
|
157
|
+
apiGroup: 'extensions.agents.x-k8s.io',
|
|
158
|
+
resource: 'sandboxwarmpools',
|
|
159
|
+
verbs: Object.freeze(['get']),
|
|
160
|
+
}),
|
|
161
|
+
Object.freeze({
|
|
162
|
+
apiGroup: '',
|
|
163
|
+
resource: 'pods',
|
|
164
|
+
verbs: Object.freeze(['get', 'list']),
|
|
165
|
+
}),
|
|
166
|
+
Object.freeze({
|
|
167
|
+
apiGroup: 'networking.k8s.io',
|
|
168
|
+
resource: 'networkpolicies',
|
|
169
|
+
verbs: Object.freeze(['get', 'list']),
|
|
170
|
+
}),
|
|
171
|
+
Object.freeze({
|
|
172
|
+
apiGroup: 'cilium.io',
|
|
173
|
+
resource: 'ciliumnetworkpolicies',
|
|
174
|
+
verbs: Object.freeze(['get', 'list']),
|
|
175
|
+
}),
|
|
176
|
+
]);
|
|
177
|
+
//# sourceMappingURL=rbac.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rbac.js","sourceRoot":"","sources":["../../../src/backends/kubernetes/rbac.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6GG;AAiBH;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAkC,MAAM,CAAC,MAAM,CAAC;IAC1F,qEAAqE;IACrE,wEAAwE;IACxE,sEAAsE;IACtE,4DAA4D;IAC5D,uEAAuE;IACvE,8CAA8C;IAC9C,MAAM,CAAC,MAAM,CAAC;QACb,QAAQ,EAAE,4BAA4B;QACtC,QAAQ,EAAE,eAAe;QACzB,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAU,CAAC;KAC3E,CAAC;IACF,MAAM,CAAC,MAAM,CAAC;QACb,QAAQ,EAAE,iBAAiB;QAC3B,QAAQ,EAAE,WAAW;QACrB,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAU,CAAC;KACtC,CAAC;IACF,MAAM,CAAC,MAAM,CAAC;QACb,QAAQ,EAAE,4BAA4B;QACtC,QAAQ,EAAE,kBAAkB;QAC5B,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,CAAU,CAAC;KACtC,CAAC;IACF,MAAM,CAAC,MAAM,CAAC;QACb,QAAQ,EAAE,EAAE;QACZ,QAAQ,EAAE,MAAM;QAChB,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,MAAM,CAAU,CAAC;KAC9C,CAAC;IACF,MAAM,CAAC,MAAM,CAAC;QACb,QAAQ,EAAE,mBAAmB;QAC7B,QAAQ,EAAE,iBAAiB;QAC3B,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,MAAM,CAAU,CAAC;KAC9C,CAAC;IACF,MAAM,CAAC,MAAM,CAAC;QACb,QAAQ,EAAE,WAAW;QACrB,QAAQ,EAAE,uBAAuB;QACjC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,MAAM,CAAU,CAAC;KAC9C,CAAC;CACF,CAAC,CAAA"}
|
|
@@ -11,20 +11,36 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Implemented: `exec` (through the shared {@link RemoteExecutionController},
|
|
13
13
|
* so an `AbortSignal` terminates the guest process rather than abandoning
|
|
14
|
-
* the wait), `writeFile`, `readFile`, `listFiles`, `
|
|
15
|
-
* `openTcpConnection`, `destroy`.
|
|
14
|
+
* the wait), `writeFile`, `readFile`, `listFiles`, `walkFiles`,
|
|
15
|
+
* `openTerminal`, `openTcpConnection`, `destroy`.
|
|
16
|
+
*
|
|
17
|
+
* Conditionally present, on the same contract read the other way:
|
|
18
|
+
*
|
|
19
|
+
* - `setNetworkPolicy` — present on a TASK handle exactly when the backend
|
|
20
|
+
* was configured with `egress.perSandbox`, which is what gives this host
|
|
21
|
+
* an object to write (one `CiliumNetworkPolicy` per sandbox, owned by the
|
|
22
|
+
* claim), an admission fence bounding what it may write, and the RBAC to
|
|
23
|
+
* write it. Without that configuration there is still no per-running-pod
|
|
24
|
+
* knob to turn, so the method is ABSENT rather than present and throwing —
|
|
25
|
+
* the same rule, applied to a capability that now sometimes exists.
|
|
26
|
+
* Presence follows configuration alone, never a probe of the cluster. See
|
|
27
|
+
* `per-sandbox-policy.ts`.
|
|
28
|
+
*
|
|
29
|
+
* A WORKSPACE handle never carries it: `KubernetesWorkspace`'s create path
|
|
30
|
+
* builds its handle through `buildKubernetesSandbox` without this option,
|
|
31
|
+
* because it composes no per-sandbox pod label and tracks no owner uid for
|
|
32
|
+
* one. That is a property of the PATH and not of the configuration, so it
|
|
33
|
+
* is not answered by omission: `createKubernetesWorkspace` refuses a
|
|
34
|
+
* config carrying `egress.perSandbox` outright
|
|
35
|
+
* (`KubernetesWorkspacePerSandboxEgressConfigError`, before any request)
|
|
36
|
+
* rather than letting the option declare a capability the path never
|
|
37
|
+
* serves — see `workspace.ts`.
|
|
16
38
|
*
|
|
17
39
|
* Absent on purpose, because the SDK's contract says a backend that cannot
|
|
18
40
|
* honour an optional method must omit it rather than accept and ignore:
|
|
19
41
|
*
|
|
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
42
|
* - `spawnDetached` — the guest agent has no op that starts a process and
|
|
25
43
|
* 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
44
|
*
|
|
29
45
|
* ## Terminals are owned
|
|
30
46
|
*
|
|
@@ -42,7 +58,8 @@
|
|
|
42
58
|
* {@link KubernetesSandboxGoneError} (the cluster deleted it — the lease
|
|
43
59
|
* renewal found the object already gone).
|
|
44
60
|
*/
|
|
45
|
-
import type { Sandbox } from '@namzu/sdk';
|
|
61
|
+
import type { Sandbox, SandboxNetworkPolicy } from '@namzu/sdk';
|
|
62
|
+
import { RemoteCancellationUnknownError, type SandboxRetirementObservation } from '../remote-execution-controller.js';
|
|
46
63
|
import type { KubernetesAgentTransport } from './transport.js';
|
|
47
64
|
/** Thrown by any operation on a sandbox this host already destroyed. */
|
|
48
65
|
export declare class KubernetesSandboxDestroyedError extends Error {
|
|
@@ -71,6 +88,56 @@ interface KubernetesSandboxBaseOptions {
|
|
|
71
88
|
readonly transport: KubernetesAgentTransport;
|
|
72
89
|
/** DELETE the object this backend created. Already-gone counts as done. */
|
|
73
90
|
readonly release: (signal?: AbortSignal) => Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Decide what an execution whose cancellation could not be CONFIRMED
|
|
93
|
+
* does to this sandbox — called instead of retiring it, and answering
|
|
94
|
+
* the {@link SandboxRetirementObservation} that goes onto the error the
|
|
95
|
+
* caller is about to receive.
|
|
96
|
+
*
|
|
97
|
+
* Unset (the task path, and the Firecracker tier through its own
|
|
98
|
+
* transport) keeps the shared controller's rule verbatim: a command of
|
|
99
|
+
* unknown state is still in that pod, the pod stops being reusable, and
|
|
100
|
+
* the handle retires it through {@link release}. That is right for a
|
|
101
|
+
* disposable object whose disk is scratch.
|
|
102
|
+
*
|
|
103
|
+
* It is wrong for an object that is not disposable. On a workspace
|
|
104
|
+
* `release` is an `operatingMode: Suspended` patch, which makes the
|
|
105
|
+
* controller delete the pod — so eight seconds of network loss under one
|
|
106
|
+
* `exec()` would take every other holder's terminals, dev servers and
|
|
107
|
+
* running commands with it, and no host-side lock can prevent it because
|
|
108
|
+
* no caller issued it. The workspace passes a hook that keeps the pod,
|
|
109
|
+
* diagnoses the agent and says `accepted: false` with a `reason` rather
|
|
110
|
+
* than letting a decision that large be made from inside a failing call.
|
|
111
|
+
*
|
|
112
|
+
* It must not reject; one that does is reported as an unaccepted
|
|
113
|
+
* retirement carrying its own error, so a broken hook cannot replace the
|
|
114
|
+
* error the caller asked about.
|
|
115
|
+
*/
|
|
116
|
+
readonly onUnconfirmedCancellation?: (error: RemoteCancellationUnknownError) => Promise<SandboxRetirementObservation>;
|
|
117
|
+
/**
|
|
118
|
+
* Narrow this sandbox's egress while it runs — present on the handle
|
|
119
|
+
* EXACTLY when this is passed, and passed by the TASK acquire exactly when
|
|
120
|
+
* `config.egress.perSandbox` is configured.
|
|
121
|
+
*
|
|
122
|
+
* That conditional presence is what the SDK's omit-or-throw contract
|
|
123
|
+
* licenses and what makes it honest here: without the configuration there
|
|
124
|
+
* is no policy object to write, no admission fence bounding what this
|
|
125
|
+
* host may write, and no RBAC grant to write it with, so the method is
|
|
126
|
+
* ABSENT rather than present and throwing. With it, `per-sandbox-policy.ts`
|
|
127
|
+
* writes one `CiliumNetworkPolicy` per sandbox, owned by the object the
|
|
128
|
+
* acquire created.
|
|
129
|
+
*
|
|
130
|
+
* The workspace passes NO such option whatever the config says — its
|
|
131
|
+
* create path composes no per-sandbox pod label and tracks no owner uid
|
|
132
|
+
* for one — and `createKubernetesWorkspace` refuses
|
|
133
|
+
* `config.egress.perSandbox` rather than silently omitting the method the
|
|
134
|
+
* option asks for.
|
|
135
|
+
*
|
|
136
|
+
* Presence depends only on configuration — never on a runtime probe of
|
|
137
|
+
* the cluster — so a caller's capability detection cannot come out
|
|
138
|
+
* differently depending on when it asked.
|
|
139
|
+
*/
|
|
140
|
+
readonly setNetworkPolicy?: (policy: SandboxNetworkPolicy) => Promise<void>;
|
|
74
141
|
}
|
|
75
142
|
/**
|
|
76
143
|
* The lease half of the options: a way to move the expiry, and the expiry it
|
|
@@ -106,12 +173,12 @@ interface KubernetesSandboxUnleasedOptions {
|
|
|
106
173
|
}
|
|
107
174
|
export type KubernetesSandboxOptions = KubernetesSandboxBaseOptions & (KubernetesSandboxLeaseOptions | KubernetesSandboxUnleasedOptions);
|
|
108
175
|
/**
|
|
109
|
-
* What this backend hands back: the SDK contract, with the
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
176
|
+
* What this backend hands back: the SDK contract, with the optional members
|
|
177
|
+
* it DOES implement narrowed to present, so a caller that composes one —
|
|
178
|
+
* `workspace.ts` wraps this handle — does not have to re-check for a method
|
|
179
|
+
* this file always defines.
|
|
113
180
|
*/
|
|
114
|
-
export type KubernetesSandboxHandle = Sandbox & Required<Pick<Sandbox, 'openTerminal' | 'openTcpConnection'>>;
|
|
181
|
+
export type KubernetesSandboxHandle = Sandbox & Required<Pick<Sandbox, 'openTerminal' | 'openTcpConnection' | 'walkFiles' | 'readFileStream'>>;
|
|
115
182
|
/**
|
|
116
183
|
* Build the handle. It does NOT run the acquire-time privilege probe — that
|
|
117
184
|
* is `create()`'s job in `index.ts`, so that a refusal can destroy this
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../../src/backends/kubernetes/sandbox.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../../src/backends/kubernetes/sandbox.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2DG;AAEH,OAAO,KAAK,EAEX,OAAO,EAOP,oBAAoB,EAOpB,MAAM,YAAY,CAAA;AAInB,OAAO,EACN,8BAA8B,EAC9B,KAAK,4BAA4B,EACjC,MAAM,mCAAmC,CAAA;AAE1C,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;IACzD;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,QAAQ,CAAC,yBAAyB,CAAC,EAAE,CACpC,KAAK,EAAE,8BAA8B,KACjC,OAAO,CAAC,4BAA4B,CAAC,CAAA;IAC1C;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC3E;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,GAAG,WAAW,GAAG,gBAAgB,CAAC,CAAC,CAAA;AAE/F;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,wBAAwB,GAAG,uBAAuB,CAqVjG"}
|