@forgezero/agent 0.1.103 → 0.1.108
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/dist/agent-heartbeat.js +1 -1
- package/dist/bootstrap-bundle.d.ts +6 -0
- package/dist/bootstrap-bundle.js +41 -0
- package/dist/bootstrap.d.ts +1 -4
- package/dist/bootstrap.js +75 -44
- package/dist/database-auth-verify.d.ts +6 -0
- package/dist/deployment-connectivity.d.ts +21 -0
- package/dist/deployment-connectivity.js +117 -21
- package/dist/deployment-pull.d.ts +1 -1
- package/dist/deployment-topology.d.ts +2 -0
- package/dist/deployment-topology.js +2 -0
- package/dist/deployment.d.ts +18 -1
- package/dist/fz-agent.js +251 -34
- package/dist/fz.js +148 -175
- package/dist/metal-bootstrap.js +1 -1
- package/dist/operator-bootstrap.js +75 -44
- package/dist/platform-bootstrap-runtime.d.ts +4 -6
- package/dist/platform-bootstrap-runtime.js +5 -10
- package/dist/platform-fleet-verification.js +134 -37
- package/dist/platform-launch-env.d.ts +17 -0
- package/dist/platform-launch-profile.d.ts +2 -3
- package/dist/provision.js +120 -24
- package/dist/software-helper.js +117 -21
- package/dist/version.d.ts +1 -1
- package/package.json +2 -2
|
@@ -45,10 +45,9 @@ export interface ForgeZeroLaunchOwnerInput {
|
|
|
45
45
|
from: string;
|
|
46
46
|
eu: boolean;
|
|
47
47
|
};
|
|
48
|
-
|
|
48
|
+
/** Required bootstrap identity provider. Repository automation is configured after genesis. */
|
|
49
|
+
githubOAuth: {
|
|
49
50
|
clientId: string;
|
|
50
|
-
appId: string;
|
|
51
|
-
slug: string;
|
|
52
51
|
};
|
|
53
52
|
}
|
|
54
53
|
/**
|
package/dist/provision.js
CHANGED
|
@@ -1869,7 +1869,7 @@ function phasePipeline(definition, phase, profile, executeRelease = false) {
|
|
|
1869
1869
|
// src/deployment-connectivity.ts
|
|
1870
1870
|
import { createHash as createHash3 } from "node:crypto";
|
|
1871
1871
|
import { mkdirSync as mkdirSync4, renameSync as renameSync5, writeFileSync as writeFileSync4 } from "node:fs";
|
|
1872
|
-
import { createConnection } from "node:net";
|
|
1872
|
+
import { createConnection, isIP } from "node:net";
|
|
1873
1873
|
import { dirname as dirname5 } from "node:path";
|
|
1874
1874
|
|
|
1875
1875
|
// src/process-input.ts
|
|
@@ -1956,21 +1956,99 @@ function validate(request) {
|
|
|
1956
1956
|
}
|
|
1957
1957
|
} else if (request.capabilities.private)
|
|
1958
1958
|
throw new Error("unexpected private deployment capability");
|
|
1959
|
+
if (request.firewallPolicy) {
|
|
1960
|
+
const policy = request.firewallPolicy;
|
|
1961
|
+
if (!/^sha256:[a-f0-9]{64}$/.test(policy.generation) || !Array.isArray(policy.rules) || policy.rules.length > 256) {
|
|
1962
|
+
throw new Error("deployment firewall policy is malformed");
|
|
1963
|
+
}
|
|
1964
|
+
let expanded = 0;
|
|
1965
|
+
for (const rule of policy.rules) {
|
|
1966
|
+
expanded += rule.sourceAddresses.length;
|
|
1967
|
+
if (!/^[A-Za-z0-9_-]{1,64}$/.test(rule.ruleKey) || !["allow", "deny"].includes(rule.action) || !["tcp", "udp"].includes(rule.protocol) || rule.sourceAddresses.length < 1 || rule.sourceAddresses.length > 1024 || new Set(rule.sourceAddresses).size !== rule.sourceAddresses.length || rule.sourceAddresses.some((address) => isIP(address) !== 4) || !Number.isSafeInteger(rule.portFrom) || rule.portFrom < 1 || rule.portFrom > 65535 || !Number.isSafeInteger(rule.portTo) || rule.portTo < rule.portFrom || rule.portTo > 65535 || !Number.isSafeInteger(rule.priority) || rule.priority < 0 || rule.priority > 1e6) {
|
|
1968
|
+
throw new Error("deployment firewall policy is malformed");
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
if (expanded > 2048)
|
|
1972
|
+
throw new Error("deployment firewall policy expands beyond its rule limit");
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
async function replaceTaggedUfwRules(host, comment) {
|
|
1976
|
+
const status = await checked2(host, ["/usr/sbin/ufw", "status", "numbered"], "firewall inventory");
|
|
1977
|
+
const numbers = status.split(`
|
|
1978
|
+
`).flatMap((line) => {
|
|
1979
|
+
if (!line.includes(comment))
|
|
1980
|
+
return [];
|
|
1981
|
+
const match = line.match(/^\s*\[\s*(\d{1,6})\]/);
|
|
1982
|
+
return match ? [Number(match[1])] : [];
|
|
1983
|
+
}).filter((value) => Number.isSafeInteger(value) && value > 0).sort((left, right) => right - left);
|
|
1984
|
+
for (const number of numbers)
|
|
1985
|
+
await checked2(host, ["/usr/sbin/ufw", "--force", "delete", String(number)], `stale firewall rule ${number}`);
|
|
1959
1986
|
}
|
|
1960
1987
|
async function applyDeploymentConnectivity(request, host = defaultHost) {
|
|
1961
1988
|
validate(request);
|
|
1962
1989
|
const id = idFor(request.key);
|
|
1963
1990
|
const evidence = { key: request.key };
|
|
1991
|
+
if (request.firewallPolicy) {
|
|
1992
|
+
const policy = request.firewallPolicy;
|
|
1993
|
+
const comment = `fz-policy-${id}`;
|
|
1994
|
+
await replaceTaggedUfwRules(host, comment);
|
|
1995
|
+
const ordered = [...policy.rules].sort((left, right) => left.priority - right.priority || (left.action === right.action ? left.ruleKey.localeCompare(right.ruleKey) : left.action === "deny" ? -1 : 1));
|
|
1996
|
+
const commands = [];
|
|
1997
|
+
for (const rule of ordered)
|
|
1998
|
+
for (const source of rule.sourceAddresses)
|
|
1999
|
+
commands.push([
|
|
2000
|
+
"/usr/sbin/ufw",
|
|
2001
|
+
"insert",
|
|
2002
|
+
"1",
|
|
2003
|
+
rule.action,
|
|
2004
|
+
"from",
|
|
2005
|
+
source,
|
|
2006
|
+
"to",
|
|
2007
|
+
"any",
|
|
2008
|
+
"port",
|
|
2009
|
+
rule.portFrom === rule.portTo ? String(rule.portFrom) : `${rule.portFrom}:${rule.portTo}`,
|
|
2010
|
+
"proto",
|
|
2011
|
+
rule.protocol,
|
|
2012
|
+
"comment",
|
|
2013
|
+
comment
|
|
2014
|
+
]);
|
|
2015
|
+
const ranges = new Map;
|
|
2016
|
+
for (const rule of ordered)
|
|
2017
|
+
ranges.set(`${rule.protocol}:${rule.portFrom}:${rule.portTo}`, rule);
|
|
2018
|
+
for (const range of ranges.values())
|
|
2019
|
+
commands.push([
|
|
2020
|
+
"/usr/sbin/ufw",
|
|
2021
|
+
"insert",
|
|
2022
|
+
"1",
|
|
2023
|
+
"deny",
|
|
2024
|
+
"to",
|
|
2025
|
+
"any",
|
|
2026
|
+
"port",
|
|
2027
|
+
range.portFrom === range.portTo ? String(range.portFrom) : `${range.portFrom}:${range.portTo}`,
|
|
2028
|
+
"proto",
|
|
2029
|
+
range.protocol,
|
|
2030
|
+
"comment",
|
|
2031
|
+
comment
|
|
2032
|
+
]);
|
|
2033
|
+
for (const command2 of commands.toReversed())
|
|
2034
|
+
await checked2(host, command2, "deployment firewall rule");
|
|
2035
|
+
evidence.firewall = { generation: policy.generation, rules: commands.length, active: true };
|
|
2036
|
+
}
|
|
1964
2037
|
const topology = request.topology;
|
|
1965
2038
|
if (topology) {
|
|
1966
|
-
const values = [
|
|
2039
|
+
const values = [
|
|
2040
|
+
topology.localRelayAddress,
|
|
2041
|
+
...topology.localPeerAddresses,
|
|
2042
|
+
...topology.remoteRelayAddresses,
|
|
2043
|
+
...topology.remoteMemberAddresses
|
|
2044
|
+
];
|
|
1967
2045
|
const identities = [
|
|
1968
2046
|
topology.nodeIdentity,
|
|
1969
2047
|
...topology.localPeerIdentities,
|
|
1970
2048
|
...topology.remoteRelayIdentities,
|
|
1971
2049
|
...topology.memberIdentities
|
|
1972
2050
|
];
|
|
1973
|
-
if (!/^[A-Za-z0-9.-]{1,253}$/.test(topology.site) || !/^(?:\d{1,3}\.){3}0\/24$/.test(topology.siteCidr) || values.some((value) =>
|
|
2051
|
+
if (!/^[A-Za-z0-9.-]{1,253}$/.test(topology.site) || !/^(?:\d{1,3}\.){3}0\/24$/.test(topology.siteCidr) || values.some((value) => isIP(value) !== 4) || identities.some((value) => !/^[A-Za-z0-9.-]{1,253}$/.test(value)) || topology.localPeerIdentities.length !== topology.localPeerAddresses.length || topology.remoteRelayIdentities.length !== topology.remoteRelayAddresses.length || topology.remoteMemberAddresses.length > 1024 || new Set(topology.remoteMemberAddresses).size !== topology.remoteMemberAddresses.length || topology.memberIdentities.length < 1 || topology.memberIdentities.length > 1024 || new Set(topology.memberIdentities).size !== topology.memberIdentities.length || !topology.memberIdentities.includes(topology.nodeIdentity) || !/^sha256:[a-f0-9]{64}$/.test(topology.generation) || (topology.role === "member" ? topology.remoteRelayAddresses.length !== 0 : topology.remoteSiteCidrs.length !== topology.remoteRelayAddresses.length) || topology.remoteSiteCidrs.some((value) => !/^(?:\d{1,3}\.){3}0\/24$/.test(value)) || new Set(topology.remoteSiteCidrs).size !== topology.remoteSiteCidrs.length || !Number.isSafeInteger(topology.healthPort) || topology.healthPort < 1 || topology.healthPort > 65535 || topology.routedTcpPorts.length < 1 || topology.routedTcpPorts.length > 64 || new Set(topology.routedTcpPorts).size !== topology.routedTcpPorts.length || !topology.routedTcpPorts.includes(topology.healthPort) || topology.routedTcpPorts.some((port) => !Number.isSafeInteger(port) || port < 1 || port > 65535) || topology.role === "member" && topology.transport !== "private-lan") {
|
|
1974
2052
|
throw new Error("deployment topology is malformed");
|
|
1975
2053
|
}
|
|
1976
2054
|
if (request.intent.private?.mode === "cloudflare-warp" !== (topology.transport === "cloudflare-warp")) {
|
|
@@ -2097,28 +2175,46 @@ ${forwarding}${noOp}${starts}${starts ? `
|
|
|
2097
2175
|
[Install]
|
|
2098
2176
|
WantedBy=multi-user.target
|
|
2099
2177
|
`, 420);
|
|
2100
|
-
|
|
2178
|
+
const firewallComment = `fz-topology-${id}`;
|
|
2179
|
+
await replaceTaggedUfwRules(host, firewallComment);
|
|
2180
|
+
for (const source of [...new Set([...topology.localPeerAddresses, ...topology.remoteMemberAddresses])]) {
|
|
2101
2181
|
for (const port of topology.routedTcpPorts) {
|
|
2102
|
-
await checked2(host, [
|
|
2182
|
+
await checked2(host, [
|
|
2183
|
+
"/usr/sbin/ufw",
|
|
2184
|
+
"allow",
|
|
2185
|
+
"from",
|
|
2186
|
+
source,
|
|
2187
|
+
"to",
|
|
2188
|
+
"any",
|
|
2189
|
+
"port",
|
|
2190
|
+
String(port),
|
|
2191
|
+
"proto",
|
|
2192
|
+
"tcp",
|
|
2193
|
+
"comment",
|
|
2194
|
+
firewallComment
|
|
2195
|
+
], `private service ${source}:${port}`);
|
|
2103
2196
|
}
|
|
2104
2197
|
}
|
|
2105
2198
|
if (topology.role !== "member")
|
|
2106
|
-
for (const
|
|
2107
|
-
for (const
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2199
|
+
for (const remoteAddress of topology.remoteMemberAddresses) {
|
|
2200
|
+
for (const source of topology.localPeerAddresses)
|
|
2201
|
+
for (const port of topology.routedTcpPorts) {
|
|
2202
|
+
await checked2(host, [
|
|
2203
|
+
"/usr/sbin/ufw",
|
|
2204
|
+
"route",
|
|
2205
|
+
"allow",
|
|
2206
|
+
"proto",
|
|
2207
|
+
"tcp",
|
|
2208
|
+
"from",
|
|
2209
|
+
source,
|
|
2210
|
+
"to",
|
|
2211
|
+
remoteAddress,
|
|
2212
|
+
"port",
|
|
2213
|
+
String(port),
|
|
2214
|
+
"comment",
|
|
2215
|
+
firewallComment
|
|
2216
|
+
], `private routed service ${source}->${remoteAddress}:${port}`);
|
|
2217
|
+
}
|
|
2122
2218
|
}
|
|
2123
2219
|
await checked2(host, ["/usr/bin/systemctl", "daemon-reload"], "topology daemon reload");
|
|
2124
2220
|
await checked2(host, ["/usr/bin/systemctl", "enable", "--now", routeUnit], "topology routes");
|
|
@@ -2908,7 +3004,7 @@ function requestSoftware(requirements, socketPath = DEFAULT_SOFTWARE_HELPER_SOCK
|
|
|
2908
3004
|
}
|
|
2909
3005
|
|
|
2910
3006
|
// src/version.ts
|
|
2911
|
-
var VERSION3 = "0.1.
|
|
3007
|
+
var VERSION3 = "0.1.108";
|
|
2912
3008
|
|
|
2913
3009
|
// src/egress-policy.ts
|
|
2914
3010
|
import { realpathSync as realpathSync3 } from "node:fs";
|
|
@@ -2971,7 +3067,7 @@ function systemdAgentEgressDirectives(loopbackTcpPorts = []) {
|
|
|
2971
3067
|
}
|
|
2972
3068
|
|
|
2973
3069
|
// src/provision.ts
|
|
2974
|
-
import { isIP } from "node:net";
|
|
3070
|
+
import { isIP as isIP2 } from "node:net";
|
|
2975
3071
|
function atLeast(version, floor) {
|
|
2976
3072
|
const parse = (value) => (value.trim().replace(/^v/, "").match(/\d+/g) ?? []).slice(0, 3).map(Number);
|
|
2977
3073
|
const got = parse(version);
|
|
@@ -3451,7 +3547,7 @@ function agentUnit(options) {
|
|
|
3451
3547
|
throw new Error("compute telemetry endpoint must be an absolute collector URL");
|
|
3452
3548
|
}
|
|
3453
3549
|
const localCollector = endpoint.protocol === "http:" && endpoint.hostname === "127.0.0.1" && endpoint.port === "4318" && endpoint.pathname === "/";
|
|
3454
|
-
const publicCollector = endpoint.protocol === "https:" && !endpoint.username && !endpoint.password && !endpoint.search && !endpoint.hash &&
|
|
3550
|
+
const publicCollector = endpoint.protocol === "https:" && !endpoint.username && !endpoint.password && !endpoint.search && !endpoint.hash && isIP2(endpoint.hostname) === 0 && endpoint.hostname.includes(".") && endpoint.hostname !== "localhost" && !endpoint.hostname.endsWith(".local");
|
|
3455
3551
|
if (!localCollector && !publicCollector || endpoint.username || endpoint.password || endpoint.search || endpoint.hash) {
|
|
3456
3552
|
throw new Error("compute telemetry endpoint must be the supervised loopback collector or credential-free public HTTPS");
|
|
3457
3553
|
}
|
package/dist/software-helper.js
CHANGED
|
@@ -1026,7 +1026,7 @@ function phasePipeline(definition, phase, profile, executeRelease = false) {
|
|
|
1026
1026
|
// src/deployment-connectivity.ts
|
|
1027
1027
|
import { createHash as createHash2 } from "node:crypto";
|
|
1028
1028
|
import { mkdirSync as mkdirSync2, renameSync as renameSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1029
|
-
import { createConnection } from "node:net";
|
|
1029
|
+
import { createConnection, isIP } from "node:net";
|
|
1030
1030
|
import { dirname as dirname2 } from "node:path";
|
|
1031
1031
|
|
|
1032
1032
|
// src/process-input.ts
|
|
@@ -1113,21 +1113,99 @@ function validate(request) {
|
|
|
1113
1113
|
}
|
|
1114
1114
|
} else if (request.capabilities.private)
|
|
1115
1115
|
throw new Error("unexpected private deployment capability");
|
|
1116
|
+
if (request.firewallPolicy) {
|
|
1117
|
+
const policy = request.firewallPolicy;
|
|
1118
|
+
if (!/^sha256:[a-f0-9]{64}$/.test(policy.generation) || !Array.isArray(policy.rules) || policy.rules.length > 256) {
|
|
1119
|
+
throw new Error("deployment firewall policy is malformed");
|
|
1120
|
+
}
|
|
1121
|
+
let expanded = 0;
|
|
1122
|
+
for (const rule of policy.rules) {
|
|
1123
|
+
expanded += rule.sourceAddresses.length;
|
|
1124
|
+
if (!/^[A-Za-z0-9_-]{1,64}$/.test(rule.ruleKey) || !["allow", "deny"].includes(rule.action) || !["tcp", "udp"].includes(rule.protocol) || rule.sourceAddresses.length < 1 || rule.sourceAddresses.length > 1024 || new Set(rule.sourceAddresses).size !== rule.sourceAddresses.length || rule.sourceAddresses.some((address) => isIP(address) !== 4) || !Number.isSafeInteger(rule.portFrom) || rule.portFrom < 1 || rule.portFrom > 65535 || !Number.isSafeInteger(rule.portTo) || rule.portTo < rule.portFrom || rule.portTo > 65535 || !Number.isSafeInteger(rule.priority) || rule.priority < 0 || rule.priority > 1e6) {
|
|
1125
|
+
throw new Error("deployment firewall policy is malformed");
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
if (expanded > 2048)
|
|
1129
|
+
throw new Error("deployment firewall policy expands beyond its rule limit");
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
async function replaceTaggedUfwRules(host, comment) {
|
|
1133
|
+
const status = await checked(host, ["/usr/sbin/ufw", "status", "numbered"], "firewall inventory");
|
|
1134
|
+
const numbers = status.split(`
|
|
1135
|
+
`).flatMap((line) => {
|
|
1136
|
+
if (!line.includes(comment))
|
|
1137
|
+
return [];
|
|
1138
|
+
const match = line.match(/^\s*\[\s*(\d{1,6})\]/);
|
|
1139
|
+
return match ? [Number(match[1])] : [];
|
|
1140
|
+
}).filter((value) => Number.isSafeInteger(value) && value > 0).sort((left, right) => right - left);
|
|
1141
|
+
for (const number of numbers)
|
|
1142
|
+
await checked(host, ["/usr/sbin/ufw", "--force", "delete", String(number)], `stale firewall rule ${number}`);
|
|
1116
1143
|
}
|
|
1117
1144
|
async function applyDeploymentConnectivity(request, host = defaultHost) {
|
|
1118
1145
|
validate(request);
|
|
1119
1146
|
const id = idFor(request.key);
|
|
1120
1147
|
const evidence = { key: request.key };
|
|
1148
|
+
if (request.firewallPolicy) {
|
|
1149
|
+
const policy = request.firewallPolicy;
|
|
1150
|
+
const comment = `fz-policy-${id}`;
|
|
1151
|
+
await replaceTaggedUfwRules(host, comment);
|
|
1152
|
+
const ordered = [...policy.rules].sort((left, right) => left.priority - right.priority || (left.action === right.action ? left.ruleKey.localeCompare(right.ruleKey) : left.action === "deny" ? -1 : 1));
|
|
1153
|
+
const commands = [];
|
|
1154
|
+
for (const rule of ordered)
|
|
1155
|
+
for (const source of rule.sourceAddresses)
|
|
1156
|
+
commands.push([
|
|
1157
|
+
"/usr/sbin/ufw",
|
|
1158
|
+
"insert",
|
|
1159
|
+
"1",
|
|
1160
|
+
rule.action,
|
|
1161
|
+
"from",
|
|
1162
|
+
source,
|
|
1163
|
+
"to",
|
|
1164
|
+
"any",
|
|
1165
|
+
"port",
|
|
1166
|
+
rule.portFrom === rule.portTo ? String(rule.portFrom) : `${rule.portFrom}:${rule.portTo}`,
|
|
1167
|
+
"proto",
|
|
1168
|
+
rule.protocol,
|
|
1169
|
+
"comment",
|
|
1170
|
+
comment
|
|
1171
|
+
]);
|
|
1172
|
+
const ranges = new Map;
|
|
1173
|
+
for (const rule of ordered)
|
|
1174
|
+
ranges.set(`${rule.protocol}:${rule.portFrom}:${rule.portTo}`, rule);
|
|
1175
|
+
for (const range of ranges.values())
|
|
1176
|
+
commands.push([
|
|
1177
|
+
"/usr/sbin/ufw",
|
|
1178
|
+
"insert",
|
|
1179
|
+
"1",
|
|
1180
|
+
"deny",
|
|
1181
|
+
"to",
|
|
1182
|
+
"any",
|
|
1183
|
+
"port",
|
|
1184
|
+
range.portFrom === range.portTo ? String(range.portFrom) : `${range.portFrom}:${range.portTo}`,
|
|
1185
|
+
"proto",
|
|
1186
|
+
range.protocol,
|
|
1187
|
+
"comment",
|
|
1188
|
+
comment
|
|
1189
|
+
]);
|
|
1190
|
+
for (const command of commands.toReversed())
|
|
1191
|
+
await checked(host, command, "deployment firewall rule");
|
|
1192
|
+
evidence.firewall = { generation: policy.generation, rules: commands.length, active: true };
|
|
1193
|
+
}
|
|
1121
1194
|
const topology = request.topology;
|
|
1122
1195
|
if (topology) {
|
|
1123
|
-
const values = [
|
|
1196
|
+
const values = [
|
|
1197
|
+
topology.localRelayAddress,
|
|
1198
|
+
...topology.localPeerAddresses,
|
|
1199
|
+
...topology.remoteRelayAddresses,
|
|
1200
|
+
...topology.remoteMemberAddresses
|
|
1201
|
+
];
|
|
1124
1202
|
const identities = [
|
|
1125
1203
|
topology.nodeIdentity,
|
|
1126
1204
|
...topology.localPeerIdentities,
|
|
1127
1205
|
...topology.remoteRelayIdentities,
|
|
1128
1206
|
...topology.memberIdentities
|
|
1129
1207
|
];
|
|
1130
|
-
if (!/^[A-Za-z0-9.-]{1,253}$/.test(topology.site) || !/^(?:\d{1,3}\.){3}0\/24$/.test(topology.siteCidr) || values.some((value) =>
|
|
1208
|
+
if (!/^[A-Za-z0-9.-]{1,253}$/.test(topology.site) || !/^(?:\d{1,3}\.){3}0\/24$/.test(topology.siteCidr) || values.some((value) => isIP(value) !== 4) || identities.some((value) => !/^[A-Za-z0-9.-]{1,253}$/.test(value)) || topology.localPeerIdentities.length !== topology.localPeerAddresses.length || topology.remoteRelayIdentities.length !== topology.remoteRelayAddresses.length || topology.remoteMemberAddresses.length > 1024 || new Set(topology.remoteMemberAddresses).size !== topology.remoteMemberAddresses.length || topology.memberIdentities.length < 1 || topology.memberIdentities.length > 1024 || new Set(topology.memberIdentities).size !== topology.memberIdentities.length || !topology.memberIdentities.includes(topology.nodeIdentity) || !/^sha256:[a-f0-9]{64}$/.test(topology.generation) || (topology.role === "member" ? topology.remoteRelayAddresses.length !== 0 : topology.remoteSiteCidrs.length !== topology.remoteRelayAddresses.length) || topology.remoteSiteCidrs.some((value) => !/^(?:\d{1,3}\.){3}0\/24$/.test(value)) || new Set(topology.remoteSiteCidrs).size !== topology.remoteSiteCidrs.length || !Number.isSafeInteger(topology.healthPort) || topology.healthPort < 1 || topology.healthPort > 65535 || topology.routedTcpPorts.length < 1 || topology.routedTcpPorts.length > 64 || new Set(topology.routedTcpPorts).size !== topology.routedTcpPorts.length || !topology.routedTcpPorts.includes(topology.healthPort) || topology.routedTcpPorts.some((port) => !Number.isSafeInteger(port) || port < 1 || port > 65535) || topology.role === "member" && topology.transport !== "private-lan") {
|
|
1131
1209
|
throw new Error("deployment topology is malformed");
|
|
1132
1210
|
}
|
|
1133
1211
|
if (request.intent.private?.mode === "cloudflare-warp" !== (topology.transport === "cloudflare-warp")) {
|
|
@@ -1254,28 +1332,46 @@ ${forwarding}${noOp}${starts}${starts ? `
|
|
|
1254
1332
|
[Install]
|
|
1255
1333
|
WantedBy=multi-user.target
|
|
1256
1334
|
`, 420);
|
|
1257
|
-
|
|
1335
|
+
const firewallComment = `fz-topology-${id}`;
|
|
1336
|
+
await replaceTaggedUfwRules(host, firewallComment);
|
|
1337
|
+
for (const source of [...new Set([...topology.localPeerAddresses, ...topology.remoteMemberAddresses])]) {
|
|
1258
1338
|
for (const port of topology.routedTcpPorts) {
|
|
1259
|
-
await checked(host, [
|
|
1339
|
+
await checked(host, [
|
|
1340
|
+
"/usr/sbin/ufw",
|
|
1341
|
+
"allow",
|
|
1342
|
+
"from",
|
|
1343
|
+
source,
|
|
1344
|
+
"to",
|
|
1345
|
+
"any",
|
|
1346
|
+
"port",
|
|
1347
|
+
String(port),
|
|
1348
|
+
"proto",
|
|
1349
|
+
"tcp",
|
|
1350
|
+
"comment",
|
|
1351
|
+
firewallComment
|
|
1352
|
+
], `private service ${source}:${port}`);
|
|
1260
1353
|
}
|
|
1261
1354
|
}
|
|
1262
1355
|
if (topology.role !== "member")
|
|
1263
|
-
for (const
|
|
1264
|
-
for (const
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1356
|
+
for (const remoteAddress of topology.remoteMemberAddresses) {
|
|
1357
|
+
for (const source of topology.localPeerAddresses)
|
|
1358
|
+
for (const port of topology.routedTcpPorts) {
|
|
1359
|
+
await checked(host, [
|
|
1360
|
+
"/usr/sbin/ufw",
|
|
1361
|
+
"route",
|
|
1362
|
+
"allow",
|
|
1363
|
+
"proto",
|
|
1364
|
+
"tcp",
|
|
1365
|
+
"from",
|
|
1366
|
+
source,
|
|
1367
|
+
"to",
|
|
1368
|
+
remoteAddress,
|
|
1369
|
+
"port",
|
|
1370
|
+
String(port),
|
|
1371
|
+
"comment",
|
|
1372
|
+
firewallComment
|
|
1373
|
+
], `private routed service ${source}->${remoteAddress}:${port}`);
|
|
1374
|
+
}
|
|
1279
1375
|
}
|
|
1280
1376
|
await checked(host, ["/usr/bin/systemctl", "daemon-reload"], "topology daemon reload");
|
|
1281
1377
|
await checked(host, ["/usr/bin/systemctl", "enable", "--now", routeUnit], "topology routes");
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** One package version shared by both public binaries. Pinned to package.json by tests. */
|
|
2
|
-
export declare const VERSION = "0.1.
|
|
2
|
+
export declare const VERSION = "0.1.108";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgezero/agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.108",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"check": "tsc --noEmit",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@forgezero/runtime": "0.1.14",
|
|
20
|
-
"@forgezero/vault": "0.1.
|
|
20
|
+
"@forgezero/vault": "0.1.20",
|
|
21
21
|
"@noble/curves": "2.2.0",
|
|
22
22
|
"@noble/hashes": "2.2.0",
|
|
23
23
|
"@noble/post-quantum": "0.6.1",
|