@forgezero/agent 0.1.32 → 0.1.33
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/README.md +60 -5
- package/dist/agent-heartbeat.d.ts +2 -0
- package/dist/agent-heartbeat.js +27 -13
- package/dist/agent-update-helper.js +1 -0
- package/dist/attestation-client.d.ts +2 -0
- package/dist/cli/agent-install.d.ts +6 -0
- package/dist/cli/genesis.d.ts +7 -48
- package/dist/cli/index.d.ts +5 -0
- package/dist/cli/session-store.d.ts +22 -0
- package/dist/deployment-pull.d.ts +2 -0
- package/dist/deployment.d.ts +3 -0
- package/dist/egress-policy.d.ts +50 -0
- package/dist/fz-agent.js +4699 -1655
- package/dist/fz.js +10336 -7617
- package/dist/git-egress.d.ts +35 -0
- package/dist/index.d.ts +17 -0
- package/dist/metal-helper-socket.js +12 -1
- package/dist/metal-provision.d.ts +2 -0
- package/dist/metal-provision.js +12 -1
- package/dist/migration-pull.d.ts +2 -0
- package/dist/migration-pull.js +18 -6
- package/dist/node-vault.d.ts +2 -0
- package/dist/node-vault.js +2 -1
- package/dist/provision.d.ts +13 -1
- package/dist/provision.js +178 -4
- package/dist/provisioning-pull.d.ts +2 -0
- package/dist/provisioning-pull.js +20 -7
- package/dist/telemetry-runtime.d.ts +20 -0
- package/dist/telemetry.d.ts +56 -0
- package/dist/version.d.ts +1 -1
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -17,14 +17,17 @@ The one public package installs both commands. Keeping bootstrap and the daemon
|
|
|
17
17
|
in one version prevents a newly installed `fz` from provisioning a different
|
|
18
18
|
agent protocol.
|
|
19
19
|
|
|
20
|
-
The operator command
|
|
21
|
-
|
|
20
|
+
The operator command starts browser-first platform genesis, inspects status,
|
|
21
|
+
wraps processes, and installs the daemon. Genesis opens the one-use founder
|
|
22
|
+
invitation, waits for the new passkey account to approve this CLI through device
|
|
23
|
+
authorization, then continues custody in the browser. Passkey PRF output and
|
|
24
|
+
both recovery phrases never enter the terminal:
|
|
22
25
|
|
|
23
26
|
```bash
|
|
24
27
|
fz keys
|
|
25
28
|
fz status
|
|
26
|
-
fz genesis --mode 2-of-3
|
|
27
|
-
fz unlock
|
|
29
|
+
fz genesis --mode 2-of-3 --api https://api.example --app https://console.example
|
|
30
|
+
fz unlock --phrase-file /secure/offline-phrase.txt # run once per participating custodian
|
|
28
31
|
```
|
|
29
32
|
|
|
30
33
|
Install the service explicitly:
|
|
@@ -136,6 +139,44 @@ are generated adapters. They contain no independent architecture or progress.
|
|
|
136
139
|
by hand. Tools and skills remain optional execution aids; accepted decisions and
|
|
137
140
|
status live in the repository and therefore survive switching AI agents.
|
|
138
141
|
|
|
142
|
+
## Browser-authorized project control
|
|
143
|
+
|
|
144
|
+
Sign in from any checkout with the public CLI. The terminal displays a device
|
|
145
|
+
code and the browser approves it in the selected tenant realm:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
fz login --api https://api.forgezero.net --realm acme
|
|
149
|
+
fz whoami
|
|
150
|
+
fz ui routes --json
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The short session is stored by API origin and realm in an owner-only local file.
|
|
154
|
+
It is never committed to `.fz/config.json`. A protected mutation opens a browser
|
|
155
|
+
page for passkey or TOTP proof and then replays the exact request once; the CLI
|
|
156
|
+
has no privileged server mode.
|
|
157
|
+
|
|
158
|
+
Any ordinary website operation is available to developers and AI agents through
|
|
159
|
+
the same API and access matrix:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
fz ui routes pipeline
|
|
163
|
+
fz ui get /workspace/pipelines --query projectKey=my-project
|
|
164
|
+
fz api post /workspace/pipelines/status \
|
|
165
|
+
--data '{"pipelineKey":"pl_…","enabled":false}'
|
|
166
|
+
generate-request | fz api post /workspace/action --data -
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Only realm-relative paths are accepted, so the saved session cannot be forwarded
|
|
170
|
+
to another origin. Inline JSON, stdin and JSON files are supported. Use
|
|
171
|
+
`fz logout` to revoke the remote session and remove the local copy.
|
|
172
|
+
|
|
173
|
+
`fz ui routes --json` reads the action catalog from the API's enforced access
|
|
174
|
+
matrix. It lists only the current account's current realm, stage and grants, so
|
|
175
|
+
developers and AI agents can discover the available control surface without a
|
|
176
|
+
copied CLI command list or a visit to the website. Execute any listed action
|
|
177
|
+
with `fz ui <method> <path>`; sensitive actions open the same fresh-proof flow
|
|
178
|
+
as the website and replay the exact request once.
|
|
179
|
+
|
|
139
180
|
## Deployment definitions
|
|
140
181
|
|
|
141
182
|
A repository may commit `.fz/deploy.json` with its own profiles, prerequisite
|
|
@@ -155,6 +196,20 @@ fz deploy check
|
|
|
155
196
|
fz deploy sync
|
|
156
197
|
```
|
|
157
198
|
|
|
199
|
+
After sign-in, create and operate the durable pipeline from the same checkout.
|
|
200
|
+
The project defaults to `.fz/config.json`; compute/profile attachments remain
|
|
201
|
+
control-plane state rather than fields in the deploy file:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
fz deploy connect --provider github --repository acme/web \
|
|
205
|
+
--clone-url https://github.com/acme/web.git --auth public \
|
|
206
|
+
--target compute-eu@api --target compute-us@api
|
|
207
|
+
fz deploy list
|
|
208
|
+
fz deploy runs --pipeline <pipeline-key>
|
|
209
|
+
fz deploy release --pipeline <pipeline-key> \
|
|
210
|
+
--revision <full-40-character-commit> --wait
|
|
211
|
+
```
|
|
212
|
+
|
|
158
213
|
`init` refuses to invent a generic release or health check: both generated steps
|
|
159
214
|
exit non-zero until the project replaces them. `check` validates the published
|
|
160
215
|
v2 schema and active software coordinates, then prints a formatting-independent
|
|
@@ -209,7 +264,7 @@ only claims assigned to its enrolled hostname, then passes
|
|
|
209
264
|
the fixed claim over a local Unix socket to a narrowly privileged root helper.
|
|
210
265
|
The helper can materialize the audited QEMU profile; it cannot clone a project,
|
|
211
266
|
read Vault data, accept arbitrary commands, or retain tenant credentials. Root
|
|
212
|
-
SSH is an operator-only platform
|
|
267
|
+
SSH is an operator-only platform recovery path, not the normal tenant
|
|
213
268
|
dispatch mechanism.
|
|
214
269
|
|
|
215
270
|
New guests also call this same public `fz agent install --apply --enrol` path
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { NodeKeyPair } from '@forgezero/runtime/identity';
|
|
2
2
|
import { type AgentRelease } from './agent-update';
|
|
3
3
|
import { type AgentUpdateReceipt, type AgentUpdateResponse } from './agent-update-helper';
|
|
4
|
+
import type { AgentOperationTelemetry } from './telemetry-runtime';
|
|
4
5
|
export interface AgentObservation {
|
|
5
6
|
version: string;
|
|
6
7
|
os: {
|
|
@@ -42,6 +43,7 @@ export interface AgentHeartbeatOptions {
|
|
|
42
43
|
setTimer?: (callback: () => void, ms: number) => unknown;
|
|
43
44
|
clearTimer?: (handle: unknown) => void;
|
|
44
45
|
onEvent?: (event: string, detail?: unknown) => void;
|
|
46
|
+
telemetry?: AgentOperationTelemetry;
|
|
45
47
|
}
|
|
46
48
|
export declare function observeAgentHost(version?: string, mode?: AgentObservation['mode'], osRelease?: string, architecture?: NodeJS.Architecture): AgentObservation;
|
|
47
49
|
/** One PQ-authenticated observation and optional supervised update decision. */
|
package/dist/agent-heartbeat.js
CHANGED
|
@@ -229,6 +229,7 @@ var AGENT_UPDATE_JOURNAL = "/var/lib/forgezero/agent-update.json";
|
|
|
229
229
|
var AGENT_UPDATE_RECEIPT = "/var/lib/forgezero/agent-update-receipt.json";
|
|
230
230
|
var MAX_REQUEST_BYTES = 8 * 1024;
|
|
231
231
|
var COMPUTE_HELPER_UNITS = [
|
|
232
|
+
"forgezero-agent-egress.service",
|
|
232
233
|
"forgezero-deploy-runner.service",
|
|
233
234
|
"forgezero-lifecycle-helper.service",
|
|
234
235
|
"forgezero-software-helper.service"
|
|
@@ -743,10 +744,14 @@ async function postSignedNode(options, path, body) {
|
|
|
743
744
|
}
|
|
744
745
|
|
|
745
746
|
// src/version.ts
|
|
746
|
-
var VERSION3 = "0.1.
|
|
747
|
+
var VERSION3 = "0.1.33";
|
|
747
748
|
|
|
748
749
|
// src/agent-heartbeat.ts
|
|
749
750
|
var unquote = (value) => value.replace(/^['"]|['"]$/g, "");
|
|
751
|
+
var remoteOutcome = (cause) => cause instanceof SignedNodeHttpError && cause.status < 500 ? "refused" : cause instanceof SignedNodeHttpError ? "retryable" : "failed";
|
|
752
|
+
|
|
753
|
+
class AgentUpdateRefusedError extends Error {
|
|
754
|
+
}
|
|
750
755
|
function observeAgentHost(version = VERSION3, mode = "enrolled", osRelease = readFileSync3("/etc/os-release", "utf8"), architecture = process.arch) {
|
|
751
756
|
const values = Object.fromEntries(osRelease.split(`
|
|
752
757
|
`).flatMap((line) => {
|
|
@@ -794,17 +799,25 @@ async function heartbeatAgentOnce(options) {
|
|
|
794
799
|
try {
|
|
795
800
|
await options.prepareUpdate?.(release);
|
|
796
801
|
prepared = true;
|
|
797
|
-
const
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
802
|
+
const apply = async () => {
|
|
803
|
+
const applied = await (options.applyUpdate ?? ((next, current, attemptId) => requestAgentUpdate({
|
|
804
|
+
op: "apply",
|
|
805
|
+
target: options.updateTarget ?? "compute",
|
|
806
|
+
release: next,
|
|
807
|
+
currentVersion: current,
|
|
808
|
+
attemptId
|
|
809
|
+
})))(release, observation.version, desired.attemptId);
|
|
810
|
+
if (!applied.ok)
|
|
811
|
+
throw new AgentUpdateRefusedError(`agent update refused: ${applied.error.message}`);
|
|
812
|
+
if (applied.attemptId !== desired.attemptId) {
|
|
813
|
+
throw new Error("agent update helper returned the wrong rollout attempt");
|
|
814
|
+
}
|
|
815
|
+
return applied;
|
|
816
|
+
};
|
|
817
|
+
if (options.telemetry) {
|
|
818
|
+
await options.telemetry.observe("agent.update", apply, () => "success", (cause) => cause instanceof AgentUpdateRefusedError ? "refused" : "failed");
|
|
819
|
+
} else {
|
|
820
|
+
await apply();
|
|
808
821
|
}
|
|
809
822
|
options.onEvent?.("update-staged", { from: observation.version, to: release.version });
|
|
810
823
|
} catch (cause) {
|
|
@@ -826,7 +839,8 @@ function startAgentHeartbeat(options) {
|
|
|
826
839
|
if (stopped || active)
|
|
827
840
|
return;
|
|
828
841
|
let nextSeconds = 30;
|
|
829
|
-
|
|
842
|
+
const heartbeat = () => heartbeatAgentOnce(options);
|
|
843
|
+
active = (options.telemetry ? options.telemetry.observe("agent.heartbeat", heartbeat, () => "success", remoteOutcome) : heartbeat()).then((response) => {
|
|
830
844
|
nextSeconds = Math.max(5, Math.min(response.intervalSeconds, 300));
|
|
831
845
|
}).catch((cause) => options.onEvent?.("heartbeat-failed", cause)).finally(() => {
|
|
832
846
|
active = null;
|
|
@@ -229,6 +229,7 @@ var AGENT_UPDATE_JOURNAL = "/var/lib/forgezero/agent-update.json";
|
|
|
229
229
|
var AGENT_UPDATE_RECEIPT = "/var/lib/forgezero/agent-update-receipt.json";
|
|
230
230
|
var MAX_REQUEST_BYTES = 8 * 1024;
|
|
231
231
|
var COMPUTE_HELPER_UNITS = [
|
|
232
|
+
"forgezero-agent-egress.service",
|
|
232
233
|
"forgezero-deploy-runner.service",
|
|
233
234
|
"forgezero-lifecycle-helper.service",
|
|
234
235
|
"forgezero-software-helper.service"
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { NodeKeyPair } from '@forgezero/runtime/identity';
|
|
2
2
|
import type { AttestationSource } from './socket';
|
|
3
3
|
import { type SignedNodeHttpOptions } from './signed-node-http';
|
|
4
|
+
import type { AgentOperationTelemetry } from './telemetry-runtime';
|
|
4
5
|
export interface NodeAttestationOptions extends SignedNodeHttpOptions {
|
|
5
6
|
keys: NodeKeyPair;
|
|
6
7
|
source: AttestationSource;
|
|
@@ -10,6 +11,7 @@ export interface NodeAttestationOptions extends SignedNodeHttpOptions {
|
|
|
10
11
|
setTimer?: (callback: () => void, ms: number) => unknown;
|
|
11
12
|
clearTimer?: (handle: unknown) => void;
|
|
12
13
|
onEvent?: (event: string, detail?: unknown) => void;
|
|
14
|
+
telemetry?: AgentOperationTelemetry;
|
|
13
15
|
}
|
|
14
16
|
/** One challenge, one PSP report, one signed presentation. */
|
|
15
17
|
export declare function attestNodeOnce(options: NodeAttestationOptions): Promise<{
|
|
@@ -16,6 +16,8 @@ import { type Capabilities, type ProvisionPlan } from '../provision';
|
|
|
16
16
|
export type { ProvisionPlan };
|
|
17
17
|
/** Parse NAME=value pairs used only for non-secret unit coordinates and credential paths. */
|
|
18
18
|
export declare function parseAssignments(value: string | undefined): Record<string, string> | undefined;
|
|
19
|
+
/** Parse an explicit provisioning-owned TCP allowlist; repository data never reaches this input. */
|
|
20
|
+
export declare function parseTcpPorts(value: string | undefined): readonly number[] | undefined;
|
|
19
21
|
/**
|
|
20
22
|
* Answer the capability checks by running their commands.
|
|
21
23
|
*
|
|
@@ -50,6 +52,9 @@ export interface InstallOptions {
|
|
|
50
52
|
deploymentEnvironment?: Record<string, string>;
|
|
51
53
|
deploymentCredentials?: Record<string, string>;
|
|
52
54
|
pullDeployments?: boolean;
|
|
55
|
+
enforceEgress?: boolean;
|
|
56
|
+
runnerLoopbackPorts?: readonly number[];
|
|
57
|
+
runnerPublicTcpPorts?: readonly number[];
|
|
53
58
|
pullMigrations?: boolean;
|
|
54
59
|
lifecycleProfilePath?: string;
|
|
55
60
|
lifecycleHelperSocketPath?: string;
|
|
@@ -71,6 +76,7 @@ export interface InstallOptions {
|
|
|
71
76
|
enrolStatePath?: string;
|
|
72
77
|
nodeLabel?: string;
|
|
73
78
|
nodeHostname?: string;
|
|
79
|
+
telemetryEndpoint?: string;
|
|
74
80
|
}
|
|
75
81
|
export declare function planInstall(options: InstallOptions): ProvisionPlan;
|
|
76
82
|
/** Execute the same ordered plan the control plane executes over SSH. */
|
package/dist/cli/genesis.d.ts
CHANGED
|
@@ -1,19 +1,5 @@
|
|
|
1
1
|
import { type SealedShare } from '@forgezero/runtime/custody-share';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The genesis ceremony, driven from a terminal.
|
|
5
|
-
*
|
|
6
|
-
* Every value that matters is produced HERE and only sealed material crosses
|
|
7
|
-
* the wire:
|
|
8
|
-
*
|
|
9
|
-
* phrase generated locally, printed once, never transmitted or stored
|
|
10
|
-
* ssh key derived locally from an agent signature, never transmitted
|
|
11
|
-
*
|
|
12
|
-
* The API receives the derived 32-byte keys because that is what seals a share,
|
|
13
|
-
* and it holds them for exactly the length of one request. Nothing here gives
|
|
14
|
-
* the CLI a privileged path — it calls the same endpoints the browser does, and
|
|
15
|
-
* a bypass that existed for bootstrapping would exist for an attacker too.
|
|
16
|
-
*/
|
|
2
|
+
/** Transport shared by the post-genesis vault unlock routine. */
|
|
17
3
|
export interface Transport {
|
|
18
4
|
(path: string, init?: {
|
|
19
5
|
method?: string;
|
|
@@ -23,32 +9,6 @@ export interface Transport {
|
|
|
23
9
|
body: unknown;
|
|
24
10
|
}>;
|
|
25
11
|
}
|
|
26
|
-
export interface GenesisResult {
|
|
27
|
-
ceremonyKey: string;
|
|
28
|
-
phrase: string[];
|
|
29
|
-
fingerprint: string;
|
|
30
|
-
activated: boolean;
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Run a single-custodian genesis.
|
|
34
|
-
*
|
|
35
|
-
* Deliberately limited to the operator running the command. A multi-custodian
|
|
36
|
-
* genesis needs each person at their own terminal with their own agent, which
|
|
37
|
-
* is a coordination problem rather than a code one — and pretending to do it
|
|
38
|
-
* from one shell would mean one machine briefly holding every share.
|
|
39
|
-
*/
|
|
40
|
-
export declare function runGenesis(args: {
|
|
41
|
-
api: Transport;
|
|
42
|
-
modeId: string;
|
|
43
|
-
userKey: string;
|
|
44
|
-
email: string;
|
|
45
|
-
identity: UsableIdentity;
|
|
46
|
-
/** The `plt_` token setup.sh wrote. Required — see below. */
|
|
47
|
-
token: string;
|
|
48
|
-
displayName?: string;
|
|
49
|
-
socketPath?: string;
|
|
50
|
-
onStep?: (message: string) => void;
|
|
51
|
-
}): Promise<GenesisResult>;
|
|
52
12
|
/**
|
|
53
13
|
* Reconstruct the seed and unlock.
|
|
54
14
|
*
|
|
@@ -59,9 +19,9 @@ export declare function runGenesis(args: {
|
|
|
59
19
|
export declare function runUnlock(args: {
|
|
60
20
|
api: Transport;
|
|
61
21
|
userKey: string;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
22
|
+
phrase: string[];
|
|
23
|
+
ceremonyKey: string;
|
|
24
|
+
fingerprint: string;
|
|
65
25
|
/**
|
|
66
26
|
* This custodian's sealed envelope, read from the ceremony.
|
|
67
27
|
*
|
|
@@ -69,11 +29,10 @@ export declare function runUnlock(args: {
|
|
|
69
29
|
* unlocking. Opening it is a local step now, so no factor travels.
|
|
70
30
|
*/
|
|
71
31
|
sealed?: SealedShare;
|
|
72
|
-
/** The salt the phrase wrapping key was derived over. */
|
|
73
|
-
phraseSalt?: string;
|
|
74
32
|
onStep?: (message: string) => void;
|
|
75
33
|
}): Promise<{
|
|
76
34
|
fingerprint: string;
|
|
35
|
+
collected?: number;
|
|
36
|
+
required?: number;
|
|
37
|
+
unlocked?: boolean;
|
|
77
38
|
}>;
|
|
78
|
-
/** Resolve `--key` to a usable identity, with an actionable error if it cannot. */
|
|
79
|
-
export declare function resolveIdentity(selector: string | undefined, socketPath?: string): Promise<UsableIdentity>;
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -10,3 +10,8 @@ import { type AgentIdentity } from '@forgezero/runtime/ssh-agent';
|
|
|
10
10
|
*/
|
|
11
11
|
export declare function requestHeaders(apiBase: string, cookie: string | null): Record<string, string>;
|
|
12
12
|
export declare function useCustodyIdentity(identity: AgentIdentity, socketPath?: string): void;
|
|
13
|
+
export declare function safeApiPath(value: string): string;
|
|
14
|
+
export declare function targetCoordinate(value: string): {
|
|
15
|
+
computeReference: string;
|
|
16
|
+
profile: string;
|
|
17
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** A short-lived browser-authorized session used only by the public CLI. */
|
|
2
|
+
export interface CliSession {
|
|
3
|
+
api: string;
|
|
4
|
+
/** Browser application origin that approved this session and fresh actions. */
|
|
5
|
+
app?: string;
|
|
6
|
+
realm: string;
|
|
7
|
+
cookie: string;
|
|
8
|
+
createdAtTs: number;
|
|
9
|
+
lastUsedAtTs: number;
|
|
10
|
+
user?: {
|
|
11
|
+
key?: string;
|
|
12
|
+
email?: string;
|
|
13
|
+
displayName?: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export declare function canonicalApi(value: string): string;
|
|
17
|
+
export declare const sessionId: (api: string, realm: string) => string;
|
|
18
|
+
export declare function defaultSessionPath(env?: NodeJS.ProcessEnv): string;
|
|
19
|
+
export declare function activeSession(path?: string): CliSession | null;
|
|
20
|
+
export declare function sessionFor(api: string, realm: string, path?: string): CliSession | null;
|
|
21
|
+
export declare function saveSession(session: CliSession, path?: string): void;
|
|
22
|
+
export declare function removeSession(api: string, realm: string, path?: string): void;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { NodeKeyPair } from '@forgezero/runtime/identity';
|
|
2
2
|
import type { DeploymentManager, DeploymentResult, GitSourceAuth } from './deployment';
|
|
3
|
+
import type { AgentOperationTelemetry } from './telemetry-runtime';
|
|
3
4
|
export interface RemoteDeploymentClaim {
|
|
4
5
|
runKey: string;
|
|
5
6
|
pipelineKey: string;
|
|
@@ -36,6 +37,7 @@ export interface DeploymentPullOptions {
|
|
|
36
37
|
setTimer?: (callback: () => void, ms: number) => unknown;
|
|
37
38
|
clearTimer?: (handle: unknown) => void;
|
|
38
39
|
onEvent?: (event: string, detail?: unknown) => void;
|
|
40
|
+
telemetry?: AgentOperationTelemetry;
|
|
39
41
|
}
|
|
40
42
|
export type PullResult = {
|
|
41
43
|
status: 'idle';
|
package/dist/deployment.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type DrainReport, type QueueTask } from '@forgezero/runtime/queue';
|
|
2
|
+
import { type GitHostResolver } from './git-egress';
|
|
2
3
|
import type { SoftwareRequirement } from './software';
|
|
3
4
|
import { type RunResult } from './pipeline';
|
|
4
5
|
import type { SecretCache } from './cache';
|
|
@@ -58,6 +59,8 @@ export interface DeploymentOptions {
|
|
|
58
59
|
knownHostsPath?: string;
|
|
59
60
|
/** Server-owned, operator-pinned host keys for a dynamically assigned source. */
|
|
60
61
|
knownHostsContent?: string;
|
|
62
|
+
/** Test/embedding seam. Production uses the operating system resolver. */
|
|
63
|
+
resolveGitHost?: GitHostResolver;
|
|
61
64
|
cache?: Pick<SecretCache, 'get'>;
|
|
62
65
|
/** Non-secret values explicitly passed to every project phase. */
|
|
63
66
|
environment?: Record<string, string>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export declare const AGENT_EGRESS_TABLE = "forgezero_agent_egress";
|
|
2
|
+
export declare const SYSTEMD_RESOLVED_STUB = "/run/systemd/resolve/stub-resolv.conf";
|
|
3
|
+
export declare const SYSTEMD_RESOLVED_ADDRESS = "127.0.0.53";
|
|
4
|
+
export declare const BLOCKED_IPV4: readonly ["0.0.0.0/8", "10.0.0.0/8", "100.64.0.0/10", "127.0.0.0/8", "168.63.129.16/32", "169.254.0.0/16", "172.16.0.0/12", "192.0.0.0/24", "192.0.2.0/24", "192.88.99.0/24", "192.168.0.0/16", "198.18.0.0/15", "198.51.100.0/24", "203.0.113.0/24", "224.0.0.0/4", "240.0.0.0/4"];
|
|
5
|
+
export declare const BLOCKED_IPV6: readonly ["::/128", "::1/128", "::ffff:0:0/96", "64:ff9b::/96", "64:ff9b:1::/48", "100::/64", "fc00::/7", "fec0::/10", "fe80::/10", "ff00::/8", "2001::/32", "2001:2::/48", "2001:10::/28", "2001:20::/28", "2001:db8::/32", "2002::/16", "3fff::/20"];
|
|
6
|
+
export declare const normalizeEgressTcpPorts: (ports: readonly number[]) => readonly number[];
|
|
7
|
+
export interface LoopbackEgressGrant {
|
|
8
|
+
uid: number;
|
|
9
|
+
tcpPorts: readonly number[];
|
|
10
|
+
/** When present, reject every other public protocol/port for this UID. */
|
|
11
|
+
publicTcpPorts?: readonly number[];
|
|
12
|
+
}
|
|
13
|
+
/** Native cgroup filtering stays active even if an operator later reloads nftables. */
|
|
14
|
+
export declare function systemdAgentEgressDirectives(loopbackTcpPorts?: readonly number[]): string;
|
|
15
|
+
/** One atomic nftables transaction installed before systemd marks the policy ready. */
|
|
16
|
+
export declare function renderAgentEgressNft(uids: readonly number[], replace?: boolean, loopback?: LoopbackEgressGrant): string;
|
|
17
|
+
export interface EgressCommandResult {
|
|
18
|
+
exitCode: number;
|
|
19
|
+
stdout: string;
|
|
20
|
+
stderr: string;
|
|
21
|
+
}
|
|
22
|
+
export type EgressCommand = (argv: readonly string[], stdin?: string) => EgressCommandResult;
|
|
23
|
+
export declare function resolveServiceUid(user: string, command?: EgressCommand): number;
|
|
24
|
+
export declare function assertResolvedStub(realpath?: (path: string) => string): void;
|
|
25
|
+
export declare function verifyAgentEgressPolicy(uids: readonly number[], command?: EgressCommand, loopback?: LoopbackEgressGrant): boolean;
|
|
26
|
+
export declare function applyAgentEgressPolicy(options: {
|
|
27
|
+
users: readonly string[];
|
|
28
|
+
loopback?: {
|
|
29
|
+
user: string;
|
|
30
|
+
tcpPorts: readonly number[];
|
|
31
|
+
publicTcpPorts?: readonly number[];
|
|
32
|
+
};
|
|
33
|
+
command?: EgressCommand;
|
|
34
|
+
realpath?: (path: string) => string;
|
|
35
|
+
getuid?: () => number;
|
|
36
|
+
}): readonly number[];
|
|
37
|
+
export declare function superviseAgentEgressPolicy(options: {
|
|
38
|
+
users: readonly string[];
|
|
39
|
+
loopback?: {
|
|
40
|
+
user: string;
|
|
41
|
+
tcpPorts: readonly number[];
|
|
42
|
+
publicTcpPorts?: readonly number[];
|
|
43
|
+
};
|
|
44
|
+
command?: EgressCommand;
|
|
45
|
+
realpath?: (path: string) => string;
|
|
46
|
+
getuid?: () => number;
|
|
47
|
+
intervalMs?: number;
|
|
48
|
+
notifyReady?: () => void;
|
|
49
|
+
wait?: (milliseconds: number) => Promise<void>;
|
|
50
|
+
}): Promise<never>;
|