nylorun 0.29.7-beta → 0.30.0-beta
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 +23 -0
- package/README.md +15 -2
- package/dist/sandbox/cluster-file.d.ts +4 -0
- package/dist/sandbox/commands.d.ts +15 -0
- package/dist/sandbox/commands.js +55 -1
- package/dist/sandbox/substrate.d.ts +33 -0
- package/dist/sandbox/substrate.js +110 -1
- package/dist/stack/compose-file.d.ts +2 -0
- package/dist/stack/compose-file.js +13 -4
- package/dist/stack/env-file.d.ts +7 -0
- package/dist/stack/env-file.js +13 -0
- package/dist/stack/prepare.js +1 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# nylorun
|
|
2
2
|
|
|
3
|
+
## 0.30.0-beta
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c4758c0: Pods on Agent Substrate (preview): keys now reach commands through Substrate's own egress ([ADR-142](https://github.com/nylorun/factory/blob/main/decisions/platform/2026-11-13-substrate-egress-adds-keys.md)). A command sends its stand-in, with no proxy, and Substrate's egress replaces the header on each request to a host the Agent has a key for, so `curl`, `gh` and SDKs work against keyed hosts, over HTTP/2 too. A removed key now fails closed with 403 instead of sending the stand-in.
|
|
8
|
+
|
|
9
|
+
- Each host the Agent has an `environment_secret` or slot for, and each host its owner connected through the outside credential source, becomes an `https` rule in the actor's egress policy that names a reference, never a value.
|
|
10
|
+
- The keys provider (`sandboxes provider`, from the sandboxes image) runs beside Substrate's egress. For every keyed request it asks egress-gate's new decide route (`POST /nylorun/v1/egress/decide`, token `NYLORUN_EGRESS_KEYS_TOKEN`), which checks that the actor is the sandbox's current incarnation and returns the vault's header value. For a person's key it returns only who owns it, and the provider fetches the value from the outside source's new `/v1/fetch`.
|
|
11
|
+
- The outside credential source's `/v1/hosts` may name each host's header (`{ host, header }`); a plain host name still means `Authorization`.
|
|
12
|
+
- The engine on Substrate writes a trust bundle with Substrate's egress CA and names it in `SSL_CERT_FILE` and the other trust variables.
|
|
13
|
+
- `nylorun local sandbox enable --backend substrate` deploys the keys provider in `ate-system`, publishes the edge's keys port (8795 by default), and checks that Substrate's egress was installed asking it (`--credential-provider='[{"name":"keys.nylorun.dev","address":"nylorun-keys-provider.ate-system.svc:50051"}]'`). When it wasn't, keys stay off and `enable` says how to turn them on. `sandbox status` shows whether keys are on.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Pin runtime to the tested release.
|
|
18
|
+
|
|
19
|
+
## 0.29.8-beta
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- f581772: An Agent created again under a deleted Agent's id no longer takes that Agent's state. Before, it read `failing` or `input_required` from sessions whose last turn ran on the deleted Agent, before it existed. Its `state` and `attention` now count a session only once that session takes a turn on the new Agent. The sessions still keep the id and can go on with the new Agent, as before, and they still appear in its `sessions`.
|
|
24
|
+
- Pin runtime to the tested release.
|
|
25
|
+
|
|
3
26
|
## 0.29.7-beta
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -467,8 +467,21 @@ owners (a 10-minute token for `ate-system/ate-client`, deleted after use), it ma
|
|
|
467
467
|
atespace with the sandboxes service as its editor.
|
|
468
468
|
|
|
469
469
|
Each Agent's network (preset, hosts, suffixes) becomes its actors' egress policy: only the edge
|
|
470
|
-
and the allowed hosts resolve and connect.
|
|
471
|
-
|
|
470
|
+
and the allowed hosts resolve and connect.
|
|
471
|
+
|
|
472
|
+
Keys reach commands through Substrate's own egress
|
|
473
|
+
([ADR-142](https://github.com/nylorun/factory/blob/main/decisions/platform/2026-11-13-substrate-egress-adds-keys.md)):
|
|
474
|
+
a command sends its stand-in (`Authorization: Bearer nylorun_managed`) with no proxy, and
|
|
475
|
+
Substrate's egress replaces the header on each request to a host the Agent has a key for, asking
|
|
476
|
+
the keys provider, which asks the Tenant. `enable` deploys the keys provider in `ate-system`
|
|
477
|
+
(`nylorun-keys-provider`, from the sandboxes image), publishes a third edge port (8795 by
|
|
478
|
+
default) for it, and keeps its token in `docker/.env` (`NYLORUN_EGRESS_KEYS_TOKEN`). Substrate's
|
|
479
|
+
egress must be installed asking it:
|
|
480
|
+
`ate-setup deploy ate-system` and `deploy atenet` with
|
|
481
|
+
`--credential-provider='[{"name":"keys.nylorun.dev","address":"nylorun-keys-provider.ate-system.svc:50051"}]'`.
|
|
482
|
+
Without that, `enable` says so and keys stay off. A person's connected keys come from the outside
|
|
483
|
+
credential source the shell names (`NYLORUN_CREDENTIAL_PROVIDER_URL` and `_TOKEN`), which must
|
|
484
|
+
answer `/v1/fetch` and be reachable from the cluster. Builds and tags are not on Substrate yet. `nylorun local sandbox status` names the backend, the atespace, the edge and whether keys are on.
|
|
472
485
|
`disable` works as for agent-sandbox; the atespace stays until you delete it with Substrate's
|
|
473
486
|
tools.
|
|
474
487
|
|
|
@@ -38,12 +38,16 @@ export interface ClusterFile {
|
|
|
38
38
|
serviceAccount: string;
|
|
39
39
|
endpoint?: string;
|
|
40
40
|
version?: string;
|
|
41
|
+
/** keysPort: where the keys provider reaches the egress service's decide route (ADR-142). */
|
|
41
42
|
edge: {
|
|
42
43
|
host: string;
|
|
43
44
|
harnessPort: number;
|
|
44
45
|
gatesPort: number;
|
|
46
|
+
keysPort?: number;
|
|
45
47
|
};
|
|
46
48
|
workerSelector?: Record<string, string>;
|
|
49
|
+
/** The keys provider's name, once it is deployed and registered with Substrate's egress. */
|
|
50
|
+
keysProvider?: string;
|
|
47
51
|
};
|
|
48
52
|
}
|
|
49
53
|
export declare function sandboxesDir(root: string): string;
|
|
@@ -71,5 +71,20 @@ export declare function installSubstrate(deps: SandboxDeps, input: {
|
|
|
71
71
|
cluster: ClusterFile;
|
|
72
72
|
token: string;
|
|
73
73
|
}>;
|
|
74
|
+
/**
|
|
75
|
+
* Keys at Substrate's egress (ADR-142): deploys the keys provider with this Tenant's entry (the
|
|
76
|
+
* edge's keys port, its token and CA, and the Host's outside credential source when the shell
|
|
77
|
+
* names one), keeping other Tenants' entries, and checks Substrate's egress asks it. Returns
|
|
78
|
+
* whether it does: when not, keys stay off and `enable` says how to turn them on.
|
|
79
|
+
*/
|
|
80
|
+
export declare function deployKeysProvider(deps: SandboxDeps, root: string, cluster: ClusterFile, input: {
|
|
81
|
+
image: string;
|
|
82
|
+
port: number;
|
|
83
|
+
token: string;
|
|
84
|
+
outside?: {
|
|
85
|
+
url: string;
|
|
86
|
+
token: string;
|
|
87
|
+
};
|
|
88
|
+
}): Promise<boolean>;
|
|
74
89
|
/** `nylorun local sandbox <enable|disable|status>`. */
|
|
75
90
|
export declare function runSandboxCommand(args: readonly string[], deps: SandboxDeps): Promise<number>;
|
package/dist/sandbox/commands.js
CHANGED
|
@@ -10,7 +10,7 @@ import { apiRoute, readClusterFile, removeClusterFiles, writeClusterFiles, } fro
|
|
|
10
10
|
import { contexts, kube } from "./kubectl.js";
|
|
11
11
|
import { CONTROLLER_DEPLOYMENT, CONTROLLER_NAMESPACE, CONTROLLER_SHA256, CONTROLLER_URL, CONTROLLER_VERSION, networkPolicy, SANDBOX_API_VERSION, SANDBOX_CRD, tenantLabel, tenantManifests, tenantNamespace, TOKEN_SECRET, } from "./manifests.js";
|
|
12
12
|
import { assertIPv4, defaultBind, parseNslookup, parseProbePorts, runProbe } from "./probe.js";
|
|
13
|
-
import { corefileWithEdge, edgeHost, PREFERRED_EDGE_PORTS, SUBSTRATE_API_SERVICE, SUBSTRATE_AUDIENCE, SUBSTRATE_NAMESPACE, SUBSTRATE_OWNER_ACCOUNT, substrateManifests, } from "./substrate.js";
|
|
13
|
+
import { corefileWithEdge, edgeHost, KEYS_PROVIDER, KEYS_PROVIDER_FLAG, KEYS_PROVIDER_NAME, keysProviderManifests, keysProviderRegistered, PREFERRED_EDGE_PORTS, SUBSTRATE_API_SERVICE, SUBSTRATE_AUDIENCE, SUBSTRATE_NAMESPACE, SUBSTRATE_OWNER_ACCOUNT, substrateManifests, } from "./substrate.js";
|
|
14
14
|
import { PROBE_IMAGE, SERVICE_ACCOUNT } from "./manifests.js";
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
import { rm, writeFile } from "node:fs/promises";
|
|
@@ -268,6 +268,40 @@ async function prepareSubstrate(deps, root, cluster, image) {
|
|
|
268
268
|
await rm(tokenFile, { force: true });
|
|
269
269
|
}
|
|
270
270
|
}
|
|
271
|
+
/**
|
|
272
|
+
* Keys at Substrate's egress (ADR-142): deploys the keys provider with this Tenant's entry (the
|
|
273
|
+
* edge's keys port, its token and CA, and the Host's outside credential source when the shell
|
|
274
|
+
* names one), keeping other Tenants' entries, and checks Substrate's egress asks it. Returns
|
|
275
|
+
* whether it does: when not, keys stay off and `enable` says how to turn them on.
|
|
276
|
+
*/
|
|
277
|
+
export async function deployKeysProvider(deps, root, cluster, input) {
|
|
278
|
+
const k = kube(deps.kubectl, cluster.context);
|
|
279
|
+
const substrate = cluster.substrate;
|
|
280
|
+
const edgeCa = await readFile(join(sandboxesDir(root), "edge-ca.pem"), "utf8");
|
|
281
|
+
const existing = await k.getJson(["secret", KEYS_PROVIDER, "-n", SUBSTRATE_NAMESPACE]);
|
|
282
|
+
let tenants = {};
|
|
283
|
+
const raw = existing?.data?.["config.json"];
|
|
284
|
+
if (raw) {
|
|
285
|
+
try {
|
|
286
|
+
tenants = JSON.parse(Buffer.from(raw, "base64").toString("utf8")).tenants ?? {};
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
tenants = {};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
tenants[substrate.atespace] = {
|
|
293
|
+
decideUrl: `https://${substrate.edge.host}:${input.port}/nylorun/v1/egress/decide`,
|
|
294
|
+
token: input.token,
|
|
295
|
+
edgeCa,
|
|
296
|
+
...(input.outside ? { outside: input.outside } : {}),
|
|
297
|
+
};
|
|
298
|
+
await k.apply("Deploying the keys provider", keysProviderManifests(input.image, tenants));
|
|
299
|
+
const egress = await k.getJson(["deployment", "atenet-egress", "-n", SUBSTRATE_NAMESPACE]);
|
|
300
|
+
const registered = keysProviderRegistered(egress);
|
|
301
|
+
if (!registered)
|
|
302
|
+
deps.stack.err(`Keys at Substrate's egress are off: Substrate's egress does not ask ${KEYS_PROVIDER_NAME}. Install Substrate with ${KEYS_PROVIDER_FLAG} (ate-setup deploy ate-system and deploy atenet), then run enable again. Until then keyed hosts are passed through with their stand-ins.`);
|
|
303
|
+
return registered;
|
|
304
|
+
}
|
|
271
305
|
async function enable(deps, args) {
|
|
272
306
|
const { stack } = deps;
|
|
273
307
|
const flags = parseStackFlags(args, { booleans: ["--no-pull"], values: ["--context", "--tenant", "--backend", "--host-address", "--bind-address", "--probe-ports"] }, ENABLE_USAGE);
|
|
@@ -310,7 +344,9 @@ async function enable(deps, args) {
|
|
|
310
344
|
const edgePorts = {
|
|
311
345
|
harness: await pick(PREFERRED_EDGE_PORTS.harness, persisted?.edgeHarnessPort),
|
|
312
346
|
gates: await pick(PREFERRED_EDGE_PORTS.gates, persisted?.edgeGatesPort),
|
|
347
|
+
keys: await pick(PREFERRED_EDGE_PORTS.keys, persisted?.edgeKeysPort),
|
|
313
348
|
};
|
|
349
|
+
const keysToken = persisted?.keysToken ?? randomBytes(32).toString("hex");
|
|
314
350
|
const images = stackImages(stack.env, { runtime: stack.runtimeVersion, studio: stack.studioVersion });
|
|
315
351
|
const installed = await installSubstrate(deps, {
|
|
316
352
|
context,
|
|
@@ -322,6 +358,18 @@ async function enable(deps, args) {
|
|
|
322
358
|
});
|
|
323
359
|
await writeClusterFiles(tenant.paths.root, installed.cluster, installed.token);
|
|
324
360
|
await prepareSubstrate(deps, tenant.paths.root, installed.cluster, images.sandboxes);
|
|
361
|
+
const outsideUrl = stack.env.NYLORUN_CREDENTIAL_PROVIDER_URL?.trim();
|
|
362
|
+
const outsideToken = stack.env.NYLORUN_CREDENTIAL_PROVIDER_TOKEN?.trim();
|
|
363
|
+
const keysOn = await deployKeysProvider(deps, tenant.paths.root, installed.cluster, {
|
|
364
|
+
image: images.sandboxes,
|
|
365
|
+
port: edgePorts.keys,
|
|
366
|
+
token: keysToken,
|
|
367
|
+
...(outsideUrl && outsideToken ? { outside: { url: outsideUrl, token: outsideToken } } : {}),
|
|
368
|
+
});
|
|
369
|
+
installed.cluster.substrate.edge.keysPort = edgePorts.keys;
|
|
370
|
+
if (keysOn)
|
|
371
|
+
installed.cluster.substrate.keysProvider = KEYS_PROVIDER_NAME;
|
|
372
|
+
await writeClusterFiles(tenant.paths.root, installed.cluster, installed.token);
|
|
325
373
|
const settings = {
|
|
326
374
|
token: persisted?.token ?? randomBytes(32).toString("hex"),
|
|
327
375
|
image: images.sandboxes,
|
|
@@ -332,6 +380,8 @@ async function enable(deps, args) {
|
|
|
332
380
|
bind: installed.cluster.bindAddress,
|
|
333
381
|
edgeHarnessPort: edgePorts.harness,
|
|
334
382
|
edgeGatesPort: edgePorts.gates,
|
|
383
|
+
edgeKeysPort: edgePorts.keys,
|
|
384
|
+
keysToken,
|
|
335
385
|
};
|
|
336
386
|
await restartTenant(stack, { name: tenant.name, sandboxes: settings });
|
|
337
387
|
const ready = await stack.docker.stream(tenant.compose("up", "--detach", "--wait", "--wait-timeout", String(cliTuning().sandbox.composeWaitSeconds), "sandboxes"));
|
|
@@ -340,6 +390,9 @@ async function enable(deps, args) {
|
|
|
340
390
|
const c = installed.cluster;
|
|
341
391
|
stack.out(`Sandboxes ${tenant.name} enabled on ${c.context} (backend substrate, atespace ${c.substrate.atespace}; preview)`);
|
|
342
392
|
stack.out(`Actors reach the edge ${c.substrate.edge.host} (${c.hostAddress}) on ports ${edgePorts.harness} and ${edgePorts.gates}`);
|
|
393
|
+
stack.out(keysOn
|
|
394
|
+
? `Keys at Substrate's egress: on (${KEYS_PROVIDER_NAME}, edge port ${edgePorts.keys})`
|
|
395
|
+
: `Keys at Substrate's egress: off (see above)`);
|
|
343
396
|
return 0;
|
|
344
397
|
}
|
|
345
398
|
if (probePorts && [probePorts.allowed, probePorts.denied].some((port) => taken.has(port)))
|
|
@@ -440,6 +493,7 @@ async function status(deps, args) {
|
|
|
440
493
|
if (cluster.backend === "substrate" && cluster.substrate) {
|
|
441
494
|
stack.out(`Namespace ${cluster.namespace} backend substrate (preview), atespace ${cluster.substrate.atespace}`);
|
|
442
495
|
stack.out(`Edge ${cluster.substrate.edge.host} ports ${cluster.substrate.edge.harnessPort}, ${cluster.substrate.edge.gatesPort} (bound on ${cluster.bindAddress}, actors reach ${cluster.hostAddress})`);
|
|
496
|
+
stack.out(`Keys ${cluster.substrate.keysProvider ? `on: ${cluster.substrate.keysProvider}, edge port ${cluster.substrate.edge.keysPort}` : "off: Substrate's egress does not ask the keys provider (see enable's output)"}`);
|
|
443
497
|
}
|
|
444
498
|
else {
|
|
445
499
|
stack.out(`Namespace ${cluster.namespace} agent-sandbox ${cluster.controllerVersion}`);
|
|
@@ -8,6 +8,7 @@ export declare const SUBSTRATE_AUDIENCE = "api.ate-system.svc";
|
|
|
8
8
|
export declare const PREFERRED_EDGE_PORTS: {
|
|
9
9
|
readonly harness: 8793;
|
|
10
10
|
readonly gates: 8794;
|
|
11
|
+
readonly keys: 8795;
|
|
11
12
|
};
|
|
12
13
|
/** The name actors dial for the edge, unique to the Tenant's namespace. */
|
|
13
14
|
export declare function edgeHost(namespace: string): string;
|
|
@@ -23,3 +24,35 @@ export declare function substrateManifests(namespace: string): object[];
|
|
|
23
24
|
* no `.:53` block or already has a `hosts` plugin of its own.
|
|
24
25
|
*/
|
|
25
26
|
export declare function corefileWithEdge(corefile: string, host: string, address: string): string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Keys at Substrate's egress (ADR-142): the keys provider, `sandboxes provider` from the sandboxes
|
|
29
|
+
* image, runs beside Substrate's egress in its namespace. One per cluster: its config Secret holds
|
|
30
|
+
* an entry per Tenant, by atespace. Substrate learns it at its own install
|
|
31
|
+
* (`--credential-provider`); `enable` deploys it and checks that registration, never changing
|
|
32
|
+
* Substrate's install.
|
|
33
|
+
*/
|
|
34
|
+
export declare const KEYS_PROVIDER_NAME = "keys.nylorun.dev";
|
|
35
|
+
export declare const KEYS_PROVIDER = "nylorun-keys-provider";
|
|
36
|
+
export declare const KEYS_PROVIDER_PORT = 50051;
|
|
37
|
+
/** The address Substrate's egress dials, as `ate-setup`'s `--credential-provider` names it. */
|
|
38
|
+
export declare const KEYS_PROVIDER_ADDRESS = "nylorun-keys-provider.ate-system.svc:50051";
|
|
39
|
+
/** The flag to install Substrate with, so its egress asks the keys provider. */
|
|
40
|
+
export declare const KEYS_PROVIDER_FLAG = "--credential-provider='[{\"name\":\"keys.nylorun.dev\",\"address\":\"nylorun-keys-provider.ate-system.svc:50051\"}]'";
|
|
41
|
+
/** One Tenant's entry in the keys provider's config. */
|
|
42
|
+
export interface KeysProviderTenant {
|
|
43
|
+
readonly decideUrl: string;
|
|
44
|
+
readonly token: string;
|
|
45
|
+
readonly edgeCa: string;
|
|
46
|
+
readonly outside?: {
|
|
47
|
+
readonly url: string;
|
|
48
|
+
readonly token: string;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** Whether Substrate's egress was installed with the keys provider among its providers. */
|
|
52
|
+
export declare function keysProviderRegistered(deployment: unknown): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* The keys provider: its ServiceAccount, Deployment, Service, a NetworkPolicy that admits only
|
|
55
|
+
* Substrate's egress, and its config Secret. Mutual TLS as Substrate's reference provider serves
|
|
56
|
+
* it: its servicedns pod certificate, callers' podidentity certificates.
|
|
57
|
+
*/
|
|
58
|
+
export declare function keysProviderManifests(image: string, tenants: Record<string, KeysProviderTenant>): object[];
|
|
@@ -12,7 +12,7 @@ export const SUBSTRATE_OWNER_ACCOUNT = "ate-client";
|
|
|
12
12
|
/** The audience ate-api-server accepts: its Service's DNS name. */
|
|
13
13
|
export const SUBSTRATE_AUDIENCE = `${SUBSTRATE_API_SERVICE}.${SUBSTRATE_NAMESPACE}.svc`;
|
|
14
14
|
/** Preferred host ports for the edge: the Harness API's and the gates' TLS fronts. */
|
|
15
|
-
export const PREFERRED_EDGE_PORTS = { harness: 8793, gates: 8794 };
|
|
15
|
+
export const PREFERRED_EDGE_PORTS = { harness: 8793, gates: 8794, keys: 8795 };
|
|
16
16
|
/** The name actors dial for the edge, unique to the Tenant's namespace. */
|
|
17
17
|
export function edgeHost(namespace) {
|
|
18
18
|
return `${namespace}.edge.nylorun.internal`;
|
|
@@ -113,3 +113,112 @@ function block(entries) {
|
|
|
113
113
|
END,
|
|
114
114
|
];
|
|
115
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Keys at Substrate's egress (ADR-142): the keys provider, `sandboxes provider` from the sandboxes
|
|
118
|
+
* image, runs beside Substrate's egress in its namespace. One per cluster: its config Secret holds
|
|
119
|
+
* an entry per Tenant, by atespace. Substrate learns it at its own install
|
|
120
|
+
* (`--credential-provider`); `enable` deploys it and checks that registration, never changing
|
|
121
|
+
* Substrate's install.
|
|
122
|
+
*/
|
|
123
|
+
export const KEYS_PROVIDER_NAME = "keys.nylorun.dev";
|
|
124
|
+
export const KEYS_PROVIDER = "nylorun-keys-provider";
|
|
125
|
+
export const KEYS_PROVIDER_PORT = 50051;
|
|
126
|
+
/** The address Substrate's egress dials, as `ate-setup`'s `--credential-provider` names it. */
|
|
127
|
+
export const KEYS_PROVIDER_ADDRESS = `${KEYS_PROVIDER}.${SUBSTRATE_NAMESPACE}.svc:${KEYS_PROVIDER_PORT}`;
|
|
128
|
+
/** The flag to install Substrate with, so its egress asks the keys provider. */
|
|
129
|
+
export const KEYS_PROVIDER_FLAG = `--credential-provider='[{"name":"${KEYS_PROVIDER_NAME}","address":"${KEYS_PROVIDER_ADDRESS}"}]'`;
|
|
130
|
+
/** Whether Substrate's egress was installed with the keys provider among its providers. */
|
|
131
|
+
export function keysProviderRegistered(deployment) {
|
|
132
|
+
const containers = deployment?.spec?.template?.spec
|
|
133
|
+
?.containers ?? [];
|
|
134
|
+
return containers.some((c) => Array.isArray(c.args) && c.args.some((arg) => arg === `--credential-provider-name=${KEYS_PROVIDER_NAME}`));
|
|
135
|
+
}
|
|
136
|
+
const PODCERT = (name, signer) => ({
|
|
137
|
+
name,
|
|
138
|
+
projected: {
|
|
139
|
+
sources: [
|
|
140
|
+
{ podCertificate: { signerName: `${signer}/identity`, keyType: "ECDSAP256", credentialBundlePath: "credential-bundle.pem" } },
|
|
141
|
+
{
|
|
142
|
+
clusterTrustBundle: {
|
|
143
|
+
signerName: `${signer}/identity`,
|
|
144
|
+
labelSelector: { matchLabels: { "podcert.ate.dev/canarying": "live" } },
|
|
145
|
+
path: "trust-bundle.pem",
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
/**
|
|
152
|
+
* The keys provider: its ServiceAccount, Deployment, Service, a NetworkPolicy that admits only
|
|
153
|
+
* Substrate's egress, and its config Secret. Mutual TLS as Substrate's reference provider serves
|
|
154
|
+
* it: its servicedns pod certificate, callers' podidentity certificates.
|
|
155
|
+
*/
|
|
156
|
+
export function keysProviderManifests(image, tenants) {
|
|
157
|
+
const labels = { app: KEYS_PROVIDER, ...MANAGED };
|
|
158
|
+
const ns = SUBSTRATE_NAMESPACE;
|
|
159
|
+
return [
|
|
160
|
+
{ apiVersion: "v1", kind: "ServiceAccount", metadata: { name: KEYS_PROVIDER, namespace: ns, labels: MANAGED } },
|
|
161
|
+
{
|
|
162
|
+
apiVersion: "v1",
|
|
163
|
+
kind: "Secret",
|
|
164
|
+
metadata: { name: KEYS_PROVIDER, namespace: ns, labels: MANAGED },
|
|
165
|
+
type: "Opaque",
|
|
166
|
+
stringData: { "config.json": JSON.stringify({ tenants }) },
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
apiVersion: "apps/v1",
|
|
170
|
+
kind: "Deployment",
|
|
171
|
+
metadata: { name: KEYS_PROVIDER, namespace: ns, labels },
|
|
172
|
+
spec: {
|
|
173
|
+
replicas: 1,
|
|
174
|
+
selector: { matchLabels: { app: KEYS_PROVIDER } },
|
|
175
|
+
template: {
|
|
176
|
+
metadata: { labels },
|
|
177
|
+
spec: {
|
|
178
|
+
serviceAccountName: KEYS_PROVIDER,
|
|
179
|
+
securityContext: { runAsUser: 65532, runAsGroup: 65532, runAsNonRoot: true },
|
|
180
|
+
containers: [
|
|
181
|
+
{
|
|
182
|
+
name: "provider",
|
|
183
|
+
image,
|
|
184
|
+
args: ["provider", "--name", KEYS_PROVIDER_NAME, "--listen", `:${KEYS_PROVIDER_PORT}`, "--config", "/etc/nylorun-keys/config.json"],
|
|
185
|
+
ports: [
|
|
186
|
+
{ name: "grpc", containerPort: KEYS_PROVIDER_PORT },
|
|
187
|
+
{ name: "health", containerPort: 9091 },
|
|
188
|
+
],
|
|
189
|
+
readinessProbe: { httpGet: { path: "/readyz", port: "health" }, periodSeconds: 10 },
|
|
190
|
+
securityContext: { allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities: { drop: ["ALL"] } },
|
|
191
|
+
volumeMounts: [
|
|
192
|
+
{ name: "config", mountPath: "/etc/nylorun-keys", readOnly: true },
|
|
193
|
+
{ name: "servicedns", mountPath: "/run/servicedns.podcert.ate.dev", readOnly: true },
|
|
194
|
+
{ name: "podidentity", mountPath: "/run/podidentity.podcert.ate.dev", readOnly: true },
|
|
195
|
+
],
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
volumes: [
|
|
199
|
+
{ name: "config", secret: { secretName: KEYS_PROVIDER } },
|
|
200
|
+
PODCERT("servicedns", "servicedns.podcert.ate.dev"),
|
|
201
|
+
PODCERT("podidentity", "podidentity.podcert.ate.dev"),
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
apiVersion: "v1",
|
|
209
|
+
kind: "Service",
|
|
210
|
+
metadata: { name: KEYS_PROVIDER, namespace: ns, labels: MANAGED },
|
|
211
|
+
spec: { type: "ClusterIP", selector: { app: KEYS_PROVIDER }, ports: [{ name: "grpc", port: KEYS_PROVIDER_PORT, targetPort: "grpc", protocol: "TCP" }] },
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
apiVersion: "networking.k8s.io/v1",
|
|
215
|
+
kind: "NetworkPolicy",
|
|
216
|
+
metadata: { name: KEYS_PROVIDER, namespace: ns, labels: MANAGED },
|
|
217
|
+
spec: {
|
|
218
|
+
podSelector: { matchLabels: { app: KEYS_PROVIDER } },
|
|
219
|
+
policyTypes: ["Ingress"],
|
|
220
|
+
ingress: [{ from: [{ podSelector: { matchLabels: { app: "atenet-egress" } } }], ports: [{ protocol: "TCP", port: KEYS_PROVIDER_PORT }] }],
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
];
|
|
224
|
+
}
|
|
@@ -76,6 +76,8 @@ export declare function renderComposeFile(project: string, name: string, options
|
|
|
76
76
|
sandboxes?: true;
|
|
77
77
|
/** The sandboxes container serves the edge (backend substrate, ADR-138). */
|
|
78
78
|
sandboxEdge?: true;
|
|
79
|
+
/** The edge also serves the keys provider's port (ADR-142). */
|
|
80
|
+
sandboxKeys?: true;
|
|
79
81
|
harness?: "remote" | "in-process";
|
|
80
82
|
restateUi?: true;
|
|
81
83
|
identity?: true;
|
|
@@ -327,7 +327,7 @@ ${healthcheck("runtime")} restart: unless-stopped
|
|
|
327
327
|
healthcheck: # /healthz needs no session; Host studio:3000 is accepted
|
|
328
328
|
test: ["CMD", "node", "-e", "require('http').get({port:3000,path:'/healthz',headers:{host:'studio:3000'}},r=>process.exit(r.statusCode===200?0:1)).on('error',()=>process.exit(1))"]
|
|
329
329
|
${healthcheck("studio")} restart: unless-stopped
|
|
330
|
-
${remote ? harnessService(project) : ""}${sandboxes ? sandboxesService(project, options.sandboxEdge === true) : ""}
|
|
330
|
+
${remote ? harnessService(project) : ""}${sandboxes ? sandboxesService(project, options.sandboxEdge === true, options.sandboxKeys === true) : ""}
|
|
331
331
|
networks:
|
|
332
332
|
default:
|
|
333
333
|
name: ${project}
|
|
@@ -421,10 +421,19 @@ const SANDBOXES_EDGE_PORTS = `
|
|
|
421
421
|
ports:
|
|
422
422
|
- "\${NYLORUN_SANDBOX_BIND:?run nylorun local sandbox enable}:\${NYLORUN_SANDBOX_EDGE_HARNESS_PORT:?run nylorun local sandbox enable}:8443" # the edge: the Harness API, for actors
|
|
423
423
|
- "\${NYLORUN_SANDBOX_BIND:?run nylorun local sandbox enable}:\${NYLORUN_SANDBOX_EDGE_GATES_PORT:?run nylorun local sandbox enable}:8444" # the edge: the gates, for actors`;
|
|
424
|
+
/**
|
|
425
|
+
* The edge's keys port (ADR-142): Substrate's keys provider reaches egress-gate's decide route on
|
|
426
|
+
* gateway:4200 through it. No actor's egress policy allows it.
|
|
427
|
+
*/
|
|
428
|
+
const SANDBOXES_EDGE_KEYS_PORT = `
|
|
429
|
+
- "\${NYLORUN_SANDBOX_BIND:?run nylorun local sandbox enable}:\${NYLORUN_SANDBOX_EDGE_KEYS_PORT:?run nylorun local sandbox enable}:8445" # the edge: egress-gate's decide route, for the keys provider`;
|
|
424
430
|
/** egress-gate (F7.2): pods' CONNECT proxy in the gateway. */
|
|
425
431
|
const SANDBOXES_GATEWAY_ENV = `
|
|
426
432
|
# egress-gate: the only way out of a sandbox pod, to the hosts its spec allows.
|
|
427
|
-
NYLORUN_EGRESS_LISTEN_PORT: "4200"
|
|
433
|
+
NYLORUN_EGRESS_LISTEN_PORT: "4200"
|
|
434
|
+
# On Agent Substrate, the keys provider's token for egress-gate's decide route (ADR-142).
|
|
435
|
+
# Empty: the route is off.
|
|
436
|
+
NYLORUN_EGRESS_KEYS_TOKEN: \${NYLORUN_EGRESS_KEYS_TOKEN:-}`;
|
|
428
437
|
/** Empty and read-only over the cluster credentials: only the sandboxes service reads them. */
|
|
429
438
|
const SANDBOXES_RUNTIME_MOUNT = `
|
|
430
439
|
- type: tmpfs
|
|
@@ -436,7 +445,7 @@ const SANDBOXES_RUNTIME_MOUNT = `
|
|
|
436
445
|
* (NYLORUN_SANDBOX_*_PORT in .env), the Harness API's, the gates' and egress-gate's, are
|
|
437
446
|
* published by the runtime and the gateway (above).
|
|
438
447
|
*/
|
|
439
|
-
function sandboxesService(project, edge) {
|
|
448
|
+
function sandboxesService(project, edge, keys) {
|
|
440
449
|
return `
|
|
441
450
|
sandboxes: # the sandboxes driver; the only holder of the cluster credentials; not published${edge ? ", but for its edge" : ""}
|
|
442
451
|
image: \${NYLORUN_SANDBOXES_IMAGE:?run nylorun local sandbox enable}
|
|
@@ -452,7 +461,7 @@ function sandboxesService(project, edge) {
|
|
|
452
461
|
volumes:
|
|
453
462
|
# cluster.json (API server, CA, namespace) and the ServiceAccount token, read-only.
|
|
454
463
|
- \${NYLORUN_HOST_ROOT:?run nylorun start}/sandboxes:/run/nylorun/sandboxes:ro
|
|
455
|
-
networks: [default] # the cluster's API server, through host.docker.internal${edge ? SANDBOXES_EDGE_PORTS : ""}
|
|
464
|
+
networks: [default] # the cluster's API server, through host.docker.internal${edge ? SANDBOXES_EDGE_PORTS : ""}${edge && keys ? SANDBOXES_EDGE_KEYS_PORT : ""}
|
|
456
465
|
healthcheck: # /ready: informers synced and the API server answers
|
|
457
466
|
test: ["CMD", "/sandboxes", "healthcheck"]
|
|
458
467
|
${healthcheck("sandboxes")} restart: unless-stopped
|
package/dist/stack/env-file.d.ts
CHANGED
|
@@ -80,6 +80,13 @@ export interface SandboxStackEnv {
|
|
|
80
80
|
*/
|
|
81
81
|
edgeHarnessPort?: number;
|
|
82
82
|
edgeGatesPort?: number;
|
|
83
|
+
/**
|
|
84
|
+
* With the Substrate backend: the edge's keys port, where the keys provider reaches
|
|
85
|
+
* egress-gate's decide route, and the token it presents there (ADR-142).
|
|
86
|
+
*/
|
|
87
|
+
edgeKeysPort?: number;
|
|
88
|
+
/** NYLORUN_EGRESS_KEYS_TOKEN, 32 bytes as hex. */
|
|
89
|
+
keysToken?: string;
|
|
83
90
|
}
|
|
84
91
|
export type HarnessMode = "remote" | "in-process";
|
|
85
92
|
export declare function renderEnvFile(env: StackEnv): string;
|
package/dist/stack/env-file.js
CHANGED
|
@@ -15,6 +15,8 @@ const SANDBOX_KEYS = {
|
|
|
15
15
|
bind: "NYLORUN_SANDBOX_BIND",
|
|
16
16
|
edgeHarnessPort: "NYLORUN_SANDBOX_EDGE_HARNESS_PORT",
|
|
17
17
|
edgeGatesPort: "NYLORUN_SANDBOX_EDGE_GATES_PORT",
|
|
18
|
+
edgeKeysPort: "NYLORUN_SANDBOX_EDGE_KEYS_PORT",
|
|
19
|
+
keysToken: "NYLORUN_EGRESS_KEYS_TOKEN",
|
|
18
20
|
};
|
|
19
21
|
const KEYS = {
|
|
20
22
|
runtimePort: "NYLORUN_PORT",
|
|
@@ -129,6 +131,14 @@ export function renderEnvFile(env) {
|
|
|
129
131
|
sandboxLine("edgeGatesPort"),
|
|
130
132
|
]
|
|
131
133
|
: []),
|
|
134
|
+
...(sandboxes.edgeKeysPort && sandboxes.keysToken
|
|
135
|
+
? [
|
|
136
|
+
"# Keys at Substrate's egress (ADR-142): the edge's port for the keys provider,",
|
|
137
|
+
"# and the token it presents to egress-gate's decide route.",
|
|
138
|
+
sandboxLine("edgeKeysPort"),
|
|
139
|
+
sandboxLine("keysToken"),
|
|
140
|
+
]
|
|
141
|
+
: []),
|
|
132
142
|
"",
|
|
133
143
|
]
|
|
134
144
|
: []),
|
|
@@ -172,9 +182,12 @@ function parseSandboxes(values) {
|
|
|
172
182
|
return undefined;
|
|
173
183
|
const edgeHarnessPort = port(values.get(SANDBOX_KEYS.edgeHarnessPort));
|
|
174
184
|
const edgeGatesPort = port(values.get(SANDBOX_KEYS.edgeGatesPort));
|
|
185
|
+
const edgeKeysPort = port(values.get(SANDBOX_KEYS.edgeKeysPort));
|
|
186
|
+
const keysToken = values.get(SANDBOX_KEYS.keysToken);
|
|
175
187
|
return {
|
|
176
188
|
token, image, harnessPort, gatesPort, egressPort, hostAddress, bind,
|
|
177
189
|
...(edgeHarnessPort && edgeGatesPort ? { edgeHarnessPort, edgeGatesPort } : {}),
|
|
190
|
+
...(edgeKeysPort && keysToken && /^[0-9a-f]{64,}$/i.test(keysToken) ? { edgeKeysPort, keysToken } : {}),
|
|
178
191
|
};
|
|
179
192
|
}
|
|
180
193
|
export function parsePersisted(text) {
|
package/dist/stack/prepare.js
CHANGED
|
@@ -103,6 +103,7 @@ export async function prepareStack(input) {
|
|
|
103
103
|
harness: env.harness,
|
|
104
104
|
...(env.sandboxes ? { sandboxes: true } : {}),
|
|
105
105
|
...(env.sandboxes?.edgeHarnessPort ? { sandboxEdge: true } : {}),
|
|
106
|
+
...(env.sandboxes?.edgeHarnessPort && env.sandboxes.edgeKeysPort ? { sandboxKeys: true } : {}),
|
|
106
107
|
...(env.restateUi ? { restateUi: true } : {}),
|
|
107
108
|
// The trusted issuers, when the operator wrote an identity file; a change takes a restart.
|
|
108
109
|
...(existsSync(paths.identity) ? { identity: true } : {}),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nylorun",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0-beta",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"description": "Set up and run local Nylorun Tenants (Runtime and Studio) with Docker Compose, and act on a Tenant through its Runtime and Management APIs",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"nylo": "dist/nylo.js"
|
|
47
47
|
},
|
|
48
48
|
"nylorun": {
|
|
49
|
-
"runtime": "0.
|
|
49
|
+
"runtime": "0.49.0-beta",
|
|
50
50
|
"studio": "0.35.11-beta"
|
|
51
51
|
}
|
|
52
52
|
}
|