@jr2/cli 0.2.1 → 0.3.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/manifests/operator.yaml +1 -1
- package/package.json +6 -4
- package/src/build.ts +20 -21
- package/src/commands/kit.ts +24 -12
- package/src/commands/up.ts +163 -23
- package/src/commands/version.ts +1 -1
- package/src/deploy.ts +165 -73
- package/src/held-pki.ts +306 -0
- package/src/index.ts +4 -0
- package/src/kube.ts +8 -4
- package/src/sweep.ts +5 -5
package/src/deploy.ts
CHANGED
|
@@ -7,7 +7,16 @@ import { readFile } from "node:fs/promises";
|
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
8
|
import {
|
|
9
9
|
CA_CONFIGMAP,
|
|
10
|
+
custodianComposition,
|
|
11
|
+
CUSTODIAN_BOOTSTRAP_KEY,
|
|
12
|
+
CUSTODIAN_SCRIPT_KEY,
|
|
10
13
|
GIT_SSH_SECRET,
|
|
14
|
+
HELD_CA_SECRET,
|
|
15
|
+
HELD_CONFIGMAP,
|
|
16
|
+
HELD_KEY,
|
|
17
|
+
HELD_MOUNT,
|
|
18
|
+
HELD_SECRETS_SECRET,
|
|
19
|
+
HELD_TLS_SECRET,
|
|
11
20
|
HARNESS_ENV_SECRET,
|
|
12
21
|
IMAGES_CONFIGMAP,
|
|
13
22
|
IMAGES_KEY,
|
|
@@ -24,8 +33,10 @@ import {
|
|
|
24
33
|
REPO_CACHE_HOSTPATH,
|
|
25
34
|
STATE_PVC,
|
|
26
35
|
type HarnessConfig,
|
|
36
|
+
type HeldManifest,
|
|
27
37
|
type SandboxPlacement,
|
|
28
38
|
} from "@jr2/orchestrator";
|
|
39
|
+
import type { Pem } from "./held-pki.ts";
|
|
29
40
|
|
|
30
41
|
export {
|
|
31
42
|
GIT_SSH_SECRET,
|
|
@@ -48,6 +59,13 @@ export const ANNOTATION_IMAGES = "jr2.dev/images";
|
|
|
48
59
|
|
|
49
60
|
export const ORCHESTRATOR_SA = "jr2-orchestrator";
|
|
50
61
|
|
|
62
|
+
/** The HMAC over the held-secret inputs, on the Instance Harness's pod template (ADR-0059). */
|
|
63
|
+
export const ANNOTATION_HELD_DIGEST = "jr2.dev/held-digest";
|
|
64
|
+
|
|
65
|
+
/** The Instance Harness pod's `fsGroup` — a Sandbox pod's default work group (ADR-0005), for the
|
|
66
|
+
* same reason: a group every container is granted, which the Custodian's values are readable by. */
|
|
67
|
+
const INSTANCE_HARNESS_FS_GROUP = 2000;
|
|
68
|
+
|
|
51
69
|
/** The two ingress NetworkPolicies (ADR-0058) — see `harnessIngressPolicies`. */
|
|
52
70
|
export const SANDBOX_INGRESS_POLICY = "jr2-sandbox-ingress";
|
|
53
71
|
export const INSTANCE_HARNESS_INGRESS_POLICY = "jr2-instance-harness-ingress";
|
|
@@ -81,6 +99,24 @@ export function compareVersions(a: string, b: string): number {
|
|
|
81
99
|
|
|
82
100
|
type KubeManifest = Record<string, unknown>;
|
|
83
101
|
|
|
102
|
+
/**
|
|
103
|
+
* What `jr2 up` resolved about held secrets (ADR-0059), ready to become objects. Values appear in
|
|
104
|
+
* two places only: `values` (the literals, for the `jr2-held-secrets` Secret) and the CA's and the
|
|
105
|
+
* leaves' keys — each lands in a Secret no Harness container mounts.
|
|
106
|
+
*/
|
|
107
|
+
export type HeldObjects = {
|
|
108
|
+
manifest: HeldManifest;
|
|
109
|
+
/** The literal values, by name — never in the manifest, never in a ConfigMap. */
|
|
110
|
+
values: Record<string, string>;
|
|
111
|
+
/** The per-Instance CA — kept across converges; present whenever a secret is held. */
|
|
112
|
+
ca?: Pem;
|
|
113
|
+
/** One leaf per bound target, by file stem (`leafStem`). */
|
|
114
|
+
leaves: Record<string, Pem>;
|
|
115
|
+
/** The Custodian's rendered bootstrap (JSON) and script. */
|
|
116
|
+
bootstrap: string;
|
|
117
|
+
script: string;
|
|
118
|
+
};
|
|
119
|
+
|
|
84
120
|
/** Everything `jr2 up` converges inside the instance's namespace, as one apply-able List. */
|
|
85
121
|
export function instanceObjects(opts: {
|
|
86
122
|
name: string;
|
|
@@ -89,14 +125,15 @@ export function instanceObjects(opts: {
|
|
|
89
125
|
hash: string;
|
|
90
126
|
/** Secret data (token, signing key, harness env literals) — written stringData, kube encodes. */
|
|
91
127
|
secretData: Record<string, string>;
|
|
92
|
-
/** The HARNESS containers' env values
|
|
93
|
-
*
|
|
94
|
-
*
|
|
128
|
+
/** The HARNESS containers' literal `harness.env` values — a SEPARATE Secret from `secretData` by
|
|
129
|
+
* doctrine (ADR-0013): Agent code executes where this lands, so the Instance token and signing
|
|
130
|
+
* key must never share a Secret with it, and no model key rides it either (ADR-0059). */
|
|
95
131
|
harnessEnvData: Record<string, string>;
|
|
96
132
|
harness?: HarnessConfig;
|
|
97
|
-
/**
|
|
98
|
-
|
|
99
|
-
|
|
133
|
+
/** Held secrets (ADR-0059): the Custodian's objects. The `jr2-held` ConfigMap is applied always —
|
|
134
|
+
* every Harness pod runs a Custodian — and the rest only while a secret is held. */
|
|
135
|
+
held: HeldObjects;
|
|
136
|
+
/** Every image ref THIS converge resolved (ADR-0037/0038/0049): `{ harness, custodian, operator?,
|
|
100
137
|
* sandbox: { <key>: ref }, sandboxUser: { <key>: user } }`, where a key is a build context's
|
|
101
138
|
* content digest or the reserved `default`. It lands twice, deliberately as one JSON so the
|
|
102
139
|
* record `up` diffs and the map pods read can never disagree: as the `jr2-images` ConfigMap the
|
|
@@ -165,9 +202,8 @@ export function instanceObjects(opts: {
|
|
|
165
202
|
data: {
|
|
166
203
|
[HARNESS_CONFIG_KEY]: JSON.stringify(
|
|
167
204
|
{
|
|
168
|
-
// apiKey is deliberately dropped: it
|
|
169
|
-
//
|
|
170
|
-
// a place for a credential.
|
|
205
|
+
// apiKey is deliberately dropped: it is a held secret (ADR-0059) — the Custodian holds
|
|
206
|
+
// it, the Harness's env holds its Stand-in, and a ConfigMap is no place for either.
|
|
171
207
|
provider: opts.harness?.provider
|
|
172
208
|
? {
|
|
173
209
|
id: opts.harness.provider.id,
|
|
@@ -180,6 +216,8 @@ export function instanceObjects(opts: {
|
|
|
180
216
|
models: opts.harness.provider.models,
|
|
181
217
|
}
|
|
182
218
|
: undefined,
|
|
219
|
+
// Where pi's catalog providers send their calls (ADR-0059): endpoints, not keys.
|
|
220
|
+
catalog: opts.harness?.catalog,
|
|
183
221
|
},
|
|
184
222
|
null,
|
|
185
223
|
2,
|
|
@@ -197,18 +235,20 @@ export function instanceObjects(opts: {
|
|
|
197
235
|
metadata: meta(IMAGES_CONFIGMAP),
|
|
198
236
|
data: { [IMAGES_KEY]: imagesJson },
|
|
199
237
|
},
|
|
200
|
-
//
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
]
|
|
211
|
-
|
|
238
|
+
// Held secrets (ADR-0059). `held.json` is what every composition reads — the Orchestrator per
|
|
239
|
+
// provision from the mount below, `jr2 up` for the Instance Harness — beside the Custodian's
|
|
240
|
+
// bootstrap and script, which Envoy reads at start. Names, hosts, headers: never a value.
|
|
241
|
+
{
|
|
242
|
+
apiVersion: "v1",
|
|
243
|
+
kind: "ConfigMap",
|
|
244
|
+
metadata: meta(HELD_CONFIGMAP),
|
|
245
|
+
data: {
|
|
246
|
+
[HELD_KEY]: JSON.stringify(opts.held.manifest, null, 2),
|
|
247
|
+
[CUSTODIAN_BOOTSTRAP_KEY]: opts.held.bootstrap,
|
|
248
|
+
[CUSTODIAN_SCRIPT_KEY]: opts.held.script,
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
...heldSecretObjects(opts.held, meta),
|
|
212
252
|
{
|
|
213
253
|
apiVersion: "v1",
|
|
214
254
|
kind: "Secret",
|
|
@@ -269,6 +309,9 @@ export function instanceObjects(opts: {
|
|
|
269
309
|
// The image map, read per provision (ADR-0038). A mount, so `jr2 up` rewriting
|
|
270
310
|
// it costs one kubelet propagation window instead of a rollout.
|
|
271
311
|
{ name: "images", mountPath: IMAGES_MOUNT, readOnly: true },
|
|
312
|
+
// What the Custodian holds, read per provision the same way (ADR-0059): names
|
|
313
|
+
// and hosts, which this process could not derive — its `.env` values are absent.
|
|
314
|
+
{ name: "held", mountPath: HELD_MOUNT, readOnly: true },
|
|
272
315
|
],
|
|
273
316
|
// The period, not the boot, is what `up`'s rollout wait measures. Measured: the
|
|
274
317
|
// container answers `/healthz` 1.1s after it starts, and the default 10s period
|
|
@@ -293,6 +336,7 @@ export function instanceObjects(opts: {
|
|
|
293
336
|
volumes: [
|
|
294
337
|
{ name: "state", persistentVolumeClaim: { claimName: STATE_PVC } },
|
|
295
338
|
{ name: "images", configMap: { name: IMAGES_CONFIGMAP } },
|
|
339
|
+
{ name: "held", configMap: { name: HELD_CONFIGMAP, items: [{ key: HELD_KEY, path: HELD_KEY }] } },
|
|
296
340
|
],
|
|
297
341
|
},
|
|
298
342
|
},
|
|
@@ -329,8 +373,8 @@ const SANDBOX_POD_LABEL = "sandbox.jr2.dev/name";
|
|
|
329
373
|
* window. The peer is this instance's Orchestrator and no other — a `podSelector` never crosses the
|
|
330
374
|
* namespace, and the instance label pins it within one. What is NOT here, on purpose:
|
|
331
375
|
*
|
|
332
|
-
* - The Orchestrator is not selected. Every route there is token-gated, and
|
|
333
|
-
* Harness pod must reach it; a policy could not tell the
|
|
376
|
+
* - The Orchestrator is not selected. Every route there is token-gated, and a Custodian in any
|
|
377
|
+
* Harness pod must reach it; a policy could not tell the Custodian's packets from the Agent's.
|
|
334
378
|
* - No egress. What a Sandbox may reach is its own decision (ADR-0058 records it open).
|
|
335
379
|
* - No port. The Orchestrator speaks only the Harness wire to these pods, and a port here would
|
|
336
380
|
* have to track the CR's `port`; `kubectl port-forward` (the CLI, a human's shell into the User
|
|
@@ -469,23 +513,73 @@ function repoCacheObjects(opts: {
|
|
|
469
513
|
];
|
|
470
514
|
}
|
|
471
515
|
|
|
472
|
-
/**
|
|
473
|
-
*
|
|
474
|
-
|
|
516
|
+
/**
|
|
517
|
+
* The held-secret Secrets (ADR-0059), present only while a secret is held: the CA (which NO pod
|
|
518
|
+
* mounts — `jr2 up` alone reads it back), the literal values and the leaves (the Custodian's alone).
|
|
519
|
+
* `stringData` as written: no trailing newline is added to a value.
|
|
520
|
+
*/
|
|
521
|
+
function heldSecretObjects(held: HeldObjects, meta: (name: string) => KubeManifest): KubeManifest[] {
|
|
522
|
+
if (held.manifest.secrets.length === 0 || !held.ca) return [];
|
|
523
|
+
const secret = (name: string, stringData: Record<string, string>): KubeManifest => ({
|
|
524
|
+
apiVersion: "v1",
|
|
525
|
+
kind: "Secret",
|
|
526
|
+
metadata: meta(name),
|
|
527
|
+
type: "Opaque",
|
|
528
|
+
stringData,
|
|
529
|
+
});
|
|
530
|
+
const leaves: Record<string, string> = {};
|
|
531
|
+
for (const [stem, pem] of Object.entries(held.leaves)) {
|
|
532
|
+
leaves[`${stem}.crt`] = pem.cert;
|
|
533
|
+
leaves[`${stem}.key`] = pem.key;
|
|
534
|
+
}
|
|
535
|
+
return [
|
|
536
|
+
secret(HELD_CA_SECRET, { "ca.crt": held.ca.cert, "ca.key": held.ca.key }),
|
|
537
|
+
...(Object.keys(held.values).length ? [secret(HELD_SECRETS_SECRET, held.values)] : []),
|
|
538
|
+
secret(HELD_TLS_SECRET, leaves),
|
|
539
|
+
];
|
|
540
|
+
}
|
|
475
541
|
|
|
476
|
-
/**
|
|
477
|
-
|
|
542
|
+
/**
|
|
543
|
+
* The trust ConfigMap (ADR-0020, ADR-0059): the user's `caBundle` as `ca.crt`, and while a secret
|
|
544
|
+
* is held, the three bundles its path needs. Its own manifest, apart from the List, because the
|
|
545
|
+
* bundles carry every root certificate — past what client-side apply's annotation can hold — so
|
|
546
|
+
* `jr2 up` applies it server-side. Undefined when there is nothing to trust.
|
|
547
|
+
*/
|
|
548
|
+
export function trustObject(opts: {
|
|
549
|
+
name: string;
|
|
550
|
+
namespace: string;
|
|
551
|
+
caBundle?: string;
|
|
552
|
+
bundles?: { extra: string; bundle: string; upstream: string };
|
|
553
|
+
}): string | undefined {
|
|
554
|
+
if (opts.caBundle === undefined && opts.bundles === undefined) return undefined;
|
|
555
|
+
return JSON.stringify({
|
|
556
|
+
apiVersion: "v1",
|
|
557
|
+
kind: "ConfigMap",
|
|
558
|
+
metadata: {
|
|
559
|
+
name: CA_CONFIGMAP,
|
|
560
|
+
namespace: opts.namespace,
|
|
561
|
+
labels: { [LABEL_INSTANCE]: opts.name, "app.kubernetes.io/managed-by": "jr2" },
|
|
562
|
+
},
|
|
563
|
+
data: {
|
|
564
|
+
...(opts.caBundle !== undefined ? { "ca.crt": opts.caBundle } : {}),
|
|
565
|
+
...(opts.bundles
|
|
566
|
+
? { "extra.crt": opts.bundles.extra, "bundle.crt": opts.bundles.bundle, "upstream.crt": opts.bundles.upstream }
|
|
567
|
+
: {}),
|
|
568
|
+
},
|
|
569
|
+
});
|
|
570
|
+
}
|
|
478
571
|
|
|
479
572
|
/**
|
|
480
573
|
* The Instance Harness (ADR-0031): the per-instance Harness Deployment + Service `jr2 up`
|
|
481
574
|
* converges whenever an Agent a registered Machine CARRIES declares `workspace: "none"`
|
|
482
575
|
* (ADR-0049's walk) — the placement for every Menu-only Agent's Turn, regardless of any enclosing
|
|
483
|
-
* Workspace. The one Harness shape, minus the Workspace: the stock Harness image plus the
|
|
484
|
-
*
|
|
485
|
-
*
|
|
486
|
-
*
|
|
487
|
-
* (ADR-
|
|
488
|
-
* entire
|
|
576
|
+
* Workspace. The one Harness shape, minus the Workspace: the stock Harness image plus the Custodian,
|
|
577
|
+
* composed by the same function a Sandbox's is (custodian.ts, ADR-0059), the same harness-config
|
|
578
|
+
* ConfigMap and env/envFrom/trust wiring a Sandbox's Harness container gets — and NO `/work`
|
|
579
|
+
* volume, no attach step. It runs the STOCK image permanently: `workspace: "none"` withholds the
|
|
580
|
+
* whole Working toolset (ADR-0028), so there are no tools to carry and no Sandbox Image to resolve
|
|
581
|
+
* (ADR-0037). No config key names, sizes, addresses, or enables it: the Machine walk is the entire
|
|
582
|
+
* surface.
|
|
489
583
|
*/
|
|
490
584
|
export function instanceHarnessObjects(opts: {
|
|
491
585
|
name: string;
|
|
@@ -498,11 +592,18 @@ export function instanceHarnessObjects(opts: {
|
|
|
498
592
|
* a Sandbox's refs travel through the `jr2-images` ConfigMap. Both are right for what they are —
|
|
499
593
|
* this Deployment is supposed to roll when its image moves; the Orchestrator is not. */
|
|
500
594
|
harnessImage: string;
|
|
501
|
-
/** The resolved
|
|
595
|
+
/** The resolved Custodian ref: the Harness's one route to its Menu, kept even though a `"none"`
|
|
502
596
|
* Agent cannot execute code — forking the path for one pod buys a divergence ADR-0031 declines. */
|
|
503
|
-
|
|
597
|
+
custodianImage: string;
|
|
598
|
+
/** What this converge resolved about held secrets (ADR-0059) — the same manifest the
|
|
599
|
+
* Orchestrator composes every Sandbox's Custodian from. */
|
|
600
|
+
held: HeldManifest;
|
|
601
|
+
/** An HMAC over every held-secret input (`held.json`, the leaves, the literal values, the
|
|
602
|
+
* Custodian's config), keyed with the signing key so it reveals nothing — on the pod template, so
|
|
603
|
+
* a change to any of them rolls this pod (ADR-0059). */
|
|
604
|
+
heldDigest: string;
|
|
504
605
|
harness?: HarnessConfig;
|
|
505
|
-
/** The instance ships a private-CA bundle (ADR-0020)
|
|
606
|
+
/** The instance ships a private-CA bundle (ADR-0020). */
|
|
506
607
|
caBundle?: boolean;
|
|
507
608
|
/** The digest of this placement's Harness bearer (ADR-0058; `harnessTokenDigest(key,
|
|
508
609
|
* "jr2-instance-harness")`) — the wire's gate. The digest, never the bearer: the same shape
|
|
@@ -526,14 +627,24 @@ export function instanceHarnessObjects(opts: {
|
|
|
526
627
|
seccompProfile: { type: "RuntimeDefault" },
|
|
527
628
|
});
|
|
528
629
|
|
|
630
|
+
// The Custodian (ADR-0013, ADR-0059): the placement's Sandbox token — signed for this
|
|
631
|
+
// placement's name (`up.ts` mints it into the instance Secret), so it speaks only for the Turns
|
|
632
|
+
// hosted HERE (tokens.ts) — plus every held secret. Mounted into the Custodian alone; the Instance
|
|
633
|
+
// token itself never enters this pod.
|
|
634
|
+
const custodian = custodianComposition(opts.held, {
|
|
635
|
+
image: opts.custodianImage,
|
|
636
|
+
token: { secret: INSTANCE_SECRET, key: "JR2_INSTANCE_HARNESS_TOKEN" },
|
|
637
|
+
caBundle: opts.caBundle === true,
|
|
638
|
+
});
|
|
639
|
+
|
|
529
640
|
const harnessContainer = {
|
|
530
641
|
name: "harness",
|
|
531
642
|
image: opts.harnessImage,
|
|
532
643
|
imagePullPolicy: "IfNotPresent",
|
|
533
644
|
ports: [{ containerPort: INSTANCE_HARNESS_PORT }],
|
|
534
|
-
// The same asymmetry the Sandbox pod builds (ADR-0013/0020): the mounted harness config
|
|
535
|
-
// the instance's valueFrom entries ride `env` (literal values live in the jr2-harness-env
|
|
536
|
-
// Secret), the
|
|
645
|
+
// The same asymmetry the Sandbox pod builds (ADR-0013/0020/0059): the mounted harness config
|
|
646
|
+
// and the instance's valueFrom entries ride `env` (literal values live in the jr2-harness-env
|
|
647
|
+
// Secret), then the Custodian's address and every Stand-in, and no credential ever does.
|
|
537
648
|
env: [
|
|
538
649
|
{
|
|
539
650
|
name: "JR2_HARNESS_JSON",
|
|
@@ -545,13 +656,12 @@ export function instanceHarnessObjects(opts: {
|
|
|
545
656
|
// rather than asserted. The bearer (below) narrows who may try; this decides what.
|
|
546
657
|
{ name: "JR2_MENU_ONLY", value: "1" },
|
|
547
658
|
...(opts.harness?.env ?? []).filter((v) => v.valueFrom !== undefined),
|
|
548
|
-
|
|
549
|
-
...(opts.caBundle ? [{ name: "NODE_EXTRA_CA_CERTS", value: `${CA_MOUNT}/ca.crt` }] : []),
|
|
659
|
+
...custodian.harnessEnv,
|
|
550
660
|
// The wire's gate (ADR-0058), last — as on a Sandbox — so no `harness.env` entry chooses it.
|
|
551
661
|
{ name: "JR2_HARNESS_TOKEN_SHA256", value: opts.bearerSha256 },
|
|
552
662
|
],
|
|
553
663
|
envFrom: [{ secretRef: { name: HARNESS_ENV_SECRET } }, ...(opts.harness?.envFrom ?? [])],
|
|
554
|
-
...(
|
|
664
|
+
...(custodian.harnessMounts.length ? { volumeMounts: custodian.harnessMounts } : {}),
|
|
555
665
|
// The operator probes a Sandbox's Harness the same way: serving = the socket accepts.
|
|
556
666
|
// Period and threshold as reasoned on the Orchestrator above: the default 10s period is the
|
|
557
667
|
// rollout wait rather than the boot, and the threshold then has to carry the stall tolerance
|
|
@@ -565,32 +675,6 @@ export function instanceHarnessObjects(opts: {
|
|
|
565
675
|
securityContext: hardenedContainerSecurityContext(),
|
|
566
676
|
};
|
|
567
677
|
|
|
568
|
-
const adapterContainer = {
|
|
569
|
-
name: "adapter",
|
|
570
|
-
image: opts.adapterImage,
|
|
571
|
-
imagePullPolicy: "IfNotPresent",
|
|
572
|
-
securityContext: hardenedContainerSecurityContext(),
|
|
573
|
-
env: [
|
|
574
|
-
{
|
|
575
|
-
name: "JR2_ORCHESTRATOR_URL",
|
|
576
|
-
value: `http://${ORCHESTRATOR_SERVICE}.${opts.namespace}.svc:${ORCHESTRATOR_PORT}`,
|
|
577
|
-
},
|
|
578
|
-
{ name: "JR2_ADAPTER_PORT", value: String(ADAPTER_PORT) },
|
|
579
|
-
// The Adapter's bearer env, carrying a sandbox-style token SIGNED FOR THIS PLACEMENT's
|
|
580
|
-
// name (`up.ts` mints it into the instance Secret): ADR-0013's delivery doctrine, extended
|
|
581
|
-
// to the second placement — the token speaks only for registrations that record the
|
|
582
|
-
// Instance Harness as the pod hosting their Turn (tokens.ts), never a Workspace's, and the
|
|
583
|
-
// Instance token itself never enters this pod. The credential lives in this container,
|
|
584
|
-
// where no Agent can read it — and `JR2_MENU_ONLY` above is what keeps that true: only
|
|
585
|
-
// Menu-only Agents run here, so nothing in this pod executes code (ADR-0031's
|
|
586
|
-
// defense-in-depth bonus).
|
|
587
|
-
{
|
|
588
|
-
name: "JR2_SANDBOX_TOKEN",
|
|
589
|
-
valueFrom: { secretKeyRef: { name: INSTANCE_SECRET, key: "JR2_INSTANCE_HARNESS_TOKEN" } },
|
|
590
|
-
},
|
|
591
|
-
],
|
|
592
|
-
};
|
|
593
|
-
|
|
594
678
|
const items: KubeManifest[] = [
|
|
595
679
|
{
|
|
596
680
|
apiVersion: "apps/v1",
|
|
@@ -606,18 +690,26 @@ export function instanceHarnessObjects(opts: {
|
|
|
606
690
|
strategy: { type: "Recreate" },
|
|
607
691
|
selector: { matchLabels: { app: INSTANCE_HARNESS_SERVICE } },
|
|
608
692
|
template: {
|
|
609
|
-
metadata: {
|
|
693
|
+
metadata: {
|
|
694
|
+
labels: { ...labels, app: INSTANCE_HARNESS_SERVICE },
|
|
695
|
+
// A held-secret edit rolls this pod; a live Sandbox keeps what its Custodian read at
|
|
696
|
+
// start (ADR-0059, the ADR-0037 stance).
|
|
697
|
+
annotations: { [ANNOTATION_HELD_DIGEST]: opts.heldDigest },
|
|
698
|
+
},
|
|
610
699
|
spec: {
|
|
611
|
-
containers: [harnessContainer,
|
|
700
|
+
containers: [harnessContainer, custodian.custodianContainer],
|
|
612
701
|
// The operator's isolation baseline (sandbox_controller.go), mirrored: same Harness
|
|
613
702
|
// image, same "never reach the Kubernetes API" north star — JR2_MENU_ONLY makes code
|
|
614
|
-
// execution here unlikely, not unimaginable.
|
|
703
|
+
// execution here unlikely, not unimaginable. No `shareProcessNamespace`: the Harness
|
|
704
|
+
// container must not read the Custodian's `/proc`. `fsGroup` makes the Custodian's
|
|
705
|
+
// group-readable values readable by Envoy's own uid, as a Sandbox pod's does.
|
|
615
706
|
automountServiceAccountToken: false,
|
|
616
707
|
securityContext: {
|
|
617
708
|
runAsNonRoot: true,
|
|
618
709
|
seccompProfile: { type: "RuntimeDefault" },
|
|
710
|
+
fsGroup: INSTANCE_HARNESS_FS_GROUP,
|
|
619
711
|
},
|
|
620
|
-
|
|
712
|
+
volumes: custodian.volumes,
|
|
621
713
|
},
|
|
622
714
|
},
|
|
623
715
|
},
|