@claude-flow/plugin-agent-federation 1.0.0-alpha.9 → 1.0.1
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 +4 -0
- package/dist/a2a/agent-card.d.ts +130 -0
- package/dist/a2a/agent-card.d.ts.map +1 -0
- package/dist/a2a/agent-card.js +239 -0
- package/dist/a2a/agent-card.js.map +1 -0
- package/dist/a2a/consume.d.ts +34 -0
- package/dist/a2a/consume.d.ts.map +1 -0
- package/dist/a2a/consume.js +69 -0
- package/dist/a2a/consume.js.map +1 -0
- package/dist/a2a/planner-descriptor.d.ts +40 -0
- package/dist/a2a/planner-descriptor.d.ts.map +1 -0
- package/dist/a2a/planner-descriptor.js +100 -0
- package/dist/a2a/planner-descriptor.js.map +1 -0
- package/dist/a2a/well-known.d.ts +46 -0
- package/dist/a2a/well-known.d.ts.map +1 -0
- package/dist/a2a/well-known.js +84 -0
- package/dist/a2a/well-known.js.map +1 -0
- package/dist/application/claim-checker.d.ts +27 -0
- package/dist/application/claim-checker.d.ts.map +1 -0
- package/dist/application/claim-checker.js +46 -0
- package/dist/application/claim-checker.js.map +1 -0
- package/dist/application/federation-breaker-service.d.ts +11 -1
- package/dist/application/federation-breaker-service.d.ts.map +1 -1
- package/dist/application/federation-breaker-service.js +12 -10
- package/dist/application/federation-breaker-service.js.map +1 -1
- package/dist/application/federation-coordinator.d.ts +48 -3
- package/dist/application/federation-coordinator.d.ts.map +1 -1
- package/dist/application/federation-coordinator.js +112 -23
- package/dist/application/federation-coordinator.js.map +1 -1
- package/dist/application/inbound-dispatcher.d.ts +114 -0
- package/dist/application/inbound-dispatcher.d.ts.map +1 -0
- package/dist/application/inbound-dispatcher.js +356 -0
- package/dist/application/inbound-dispatcher.js.map +1 -0
- package/dist/application/spend-reporter.d.ts +77 -0
- package/dist/application/spend-reporter.d.ts.map +1 -1
- package/dist/application/spend-reporter.js +76 -0
- package/dist/application/spend-reporter.js.map +1 -1
- package/dist/application/trust-evaluator.d.ts +32 -0
- package/dist/application/trust-evaluator.d.ts.map +1 -1
- package/dist/application/trust-evaluator.js +47 -0
- package/dist/application/trust-evaluator.js.map +1 -1
- package/dist/bin.d.ts +1 -1
- package/dist/bin.js +2 -2
- package/dist/bin.js.map +1 -1
- package/dist/cli-commands.d.ts.map +1 -1
- package/dist/cli-commands.js +125 -6
- package/dist/cli-commands.js.map +1 -1
- package/dist/domain/services/discovery-service.d.ts +18 -1
- package/dist/domain/services/discovery-service.d.ts.map +1 -1
- package/dist/domain/services/discovery-service.js +21 -0
- package/dist/domain/services/discovery-service.js.map +1 -1
- package/dist/domain/services/handshake-service.d.ts +10 -0
- package/dist/domain/services/handshake-service.d.ts.map +1 -1
- package/dist/domain/services/handshake-service.js +28 -0
- package/dist/domain/services/handshake-service.js.map +1 -1
- package/dist/domain/services/routing-service.d.ts +3 -0
- package/dist/domain/services/routing-service.d.ts.map +1 -1
- package/dist/domain/services/routing-service.js +4 -1
- package/dist/domain/services/routing-service.js.map +1 -1
- package/dist/domain/services/wg-firewall-service.d.ts +71 -0
- package/dist/domain/services/wg-firewall-service.d.ts.map +1 -0
- package/dist/domain/services/wg-firewall-service.js +194 -0
- package/dist/domain/services/wg-firewall-service.js.map +1 -0
- package/dist/domain/services/wg-mesh-service.d.ts +156 -0
- package/dist/domain/services/wg-mesh-service.d.ts.map +1 -0
- package/dist/domain/services/wg-mesh-service.js +292 -0
- package/dist/domain/services/wg-mesh-service.js.map +1 -0
- package/dist/domain/services/wg-witness-service.d.ts +98 -0
- package/dist/domain/services/wg-witness-service.d.ts.map +1 -0
- package/dist/domain/services/wg-witness-service.js +122 -0
- package/dist/domain/services/wg-witness-service.js.map +1 -0
- package/dist/domain/value-objects/wg-config.d.ts +70 -0
- package/dist/domain/value-objects/wg-config.d.ts.map +1 -0
- package/dist/domain/value-objects/wg-config.js +104 -0
- package/dist/domain/value-objects/wg-config.js.map +1 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-tools.d.ts +3 -1
- package/dist/mcp-tools.d.ts.map +1 -1
- package/dist/mcp-tools.js +114 -18
- package/dist/mcp-tools.js.map +1 -1
- package/dist/plugin.d.ts +2 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +174 -15
- package/dist/plugin.js.map +1 -1
- package/dist/transport/midstream-aware-loader.d.ts +72 -0
- package/dist/transport/midstream-aware-loader.d.ts.map +1 -0
- package/dist/transport/midstream-aware-loader.js +269 -0
- package/dist/transport/midstream-aware-loader.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/package.json +21 -5
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-111 Phase 2 — WgMeshService.
|
|
3
|
+
*
|
|
4
|
+
* Pure-projection service: takes the federation peer registry + local
|
|
5
|
+
* WG identity and produces (a) a `wg-quick`-compatible config string,
|
|
6
|
+
* (b) per-peer `wg set` commands the operator runs to converge an
|
|
7
|
+
* already-running interface, and (c) the AllowedIPs slice that maps to
|
|
8
|
+
* each peer's trust level.
|
|
9
|
+
*
|
|
10
|
+
* Deliberately does NOT shell out. Bringing up a network interface
|
|
11
|
+
* requires root and modifies system state — per CLAUDE.md's
|
|
12
|
+
* destructive-actions guidance the service emits the commands; the
|
|
13
|
+
* operator (or a thin shell wrapper they audit) runs them.
|
|
14
|
+
*
|
|
15
|
+
* Phase 3 (breaker integration) consumes this service: peer state
|
|
16
|
+
* transitions invoke removeAllowedIPs/removePeer/restoreAllowedIPs,
|
|
17
|
+
* which produce commands without side effects.
|
|
18
|
+
*/
|
|
19
|
+
import { TrustLevel, getTrustLevelLabel } from '../entities/trust-level.js';
|
|
20
|
+
import { DEFAULT_MESH_SUBNET } from '../value-objects/wg-config.js';
|
|
21
|
+
/**
|
|
22
|
+
* Trust-level → reachability. ADR-111's `WG_NETWORK_GATES`.
|
|
23
|
+
*
|
|
24
|
+
* Note: WireGuard's `AllowedIPs` is L3 routing, not L4 ACL — these port
|
|
25
|
+
* rules describe what the L4 firewall (Phase 4: nftables/pf) should
|
|
26
|
+
* enforce. v1 (this phase) only produces the rules; Phase 4 projects
|
|
27
|
+
* them into kernel firewall syntax. Until then, callers using v1
|
|
28
|
+
* fall back to the simpler "AllowedIPs = peer's mesh IP" model (option
|
|
29
|
+
* (b) in the ADR — app-layer auth carries the access decision).
|
|
30
|
+
*/
|
|
31
|
+
export const WG_NETWORK_GATES = {
|
|
32
|
+
[TrustLevel.UNTRUSTED]: [],
|
|
33
|
+
[TrustLevel.VERIFIED]: [
|
|
34
|
+
{ proto: 'tcp', port: 9100 },
|
|
35
|
+
],
|
|
36
|
+
[TrustLevel.ATTESTED]: [
|
|
37
|
+
{ proto: 'tcp', port: 9100 },
|
|
38
|
+
{ proto: 'tcp', portRange: [9101, 9199] },
|
|
39
|
+
],
|
|
40
|
+
[TrustLevel.TRUSTED]: [
|
|
41
|
+
{ proto: 'tcp', port: 9100 },
|
|
42
|
+
{ proto: 'tcp', portRange: [9101, 9199] },
|
|
43
|
+
{ proto: 'tcp', port: 22 },
|
|
44
|
+
{ proto: 'tcp', portRange: [80, 443] },
|
|
45
|
+
],
|
|
46
|
+
[TrustLevel.PRIVILEGED]: [
|
|
47
|
+
{ proto: 'all' },
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Minimum trust level whose peers get a `[Peer]` block in the WG config.
|
|
52
|
+
* UNTRUSTED stays out of the mesh entirely — it's the explicit drop bucket.
|
|
53
|
+
*/
|
|
54
|
+
export const WG_MIN_MESH_TRUST = TrustLevel.VERIFIED;
|
|
55
|
+
/**
|
|
56
|
+
* Security: validate every field we splice into a wg-quick config string
|
|
57
|
+
* or shell command. The ADR's threat model explicitly includes "compromised
|
|
58
|
+
* federation peer with valid WG key" — that peer signs their own manifest,
|
|
59
|
+
* so the Ed25519 signature only proves origin, not content safety. A
|
|
60
|
+
* compromised peer could publish e.g. wgEndpoint = "host:51820\n[Peer]\n..."
|
|
61
|
+
* to inject extra peers into the config the operator runs via wg-quick.
|
|
62
|
+
*
|
|
63
|
+
* These regexes are intentionally narrow:
|
|
64
|
+
* - publicKey: base64 X25519, exactly 43 chars + '=' padding
|
|
65
|
+
* - meshIP: a.b.c.d/32, octets 0-255, no whitespace
|
|
66
|
+
* - endpoint: hostname-or-ipv4-or-bracketed-ipv6 + :port, no newlines/specials
|
|
67
|
+
*/
|
|
68
|
+
const WG_PUBKEY_REGEX = /^[A-Za-z0-9+/]{43}=$/;
|
|
69
|
+
const WG_MESH_IP_REGEX = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\/32$/;
|
|
70
|
+
const WG_ENDPOINT_REGEX = /^(\[[0-9a-fA-F:]+\]|[a-zA-Z0-9][a-zA-Z0-9.-]*):\d{1,5}$/;
|
|
71
|
+
/**
|
|
72
|
+
* Return the validated peer wg fields, or null if any is malformed or
|
|
73
|
+
* out-of-range. Callers treat null as "peer not eligible for mesh" — the
|
|
74
|
+
* peer stays in the federation discovery registry but is excluded from
|
|
75
|
+
* the wg config and any wg commands.
|
|
76
|
+
*/
|
|
77
|
+
export function readSafePeerWgFields(peer) {
|
|
78
|
+
const pk = peer.metadata.wgPublicKey;
|
|
79
|
+
const ip = peer.metadata.wgMeshIP;
|
|
80
|
+
const ep = peer.metadata.wgEndpoint;
|
|
81
|
+
if (typeof pk !== 'string' || !WG_PUBKEY_REGEX.test(pk))
|
|
82
|
+
return null;
|
|
83
|
+
if (typeof ip !== 'string' || !WG_MESH_IP_REGEX.test(ip))
|
|
84
|
+
return null;
|
|
85
|
+
const ipMatch = ip.match(WG_MESH_IP_REGEX);
|
|
86
|
+
if (!ipMatch || [ipMatch[1], ipMatch[2], ipMatch[3], ipMatch[4]].some(o => Number(o) > 255))
|
|
87
|
+
return null;
|
|
88
|
+
if (typeof ep !== 'string' || !WG_ENDPOINT_REGEX.test(ep))
|
|
89
|
+
return null;
|
|
90
|
+
const port = Number(ep.split(':').pop());
|
|
91
|
+
if (port < 1 || port > 65535)
|
|
92
|
+
return null;
|
|
93
|
+
return { wgPublicKey: pk, wgMeshIP: ip, wgEndpoint: ep };
|
|
94
|
+
}
|
|
95
|
+
const DEFAULT_INTERFACE = 'ruflo-fed';
|
|
96
|
+
const DEFAULT_LISTEN_PORT = 51820;
|
|
97
|
+
export class WgMeshService {
|
|
98
|
+
interfaceName;
|
|
99
|
+
listenPort;
|
|
100
|
+
meshSubnet;
|
|
101
|
+
localKey = null;
|
|
102
|
+
localMeshIP = null;
|
|
103
|
+
/** Per-peer AllowedIPs after the last applyTrustLevelToAllowedIPs() call. Drives restore from breaker. */
|
|
104
|
+
lastAppliedAllowedIPs = new Map();
|
|
105
|
+
/** Peer-pubkey → suspended flag for breaker integration. */
|
|
106
|
+
suspended = new Set();
|
|
107
|
+
/** Peer-pubkey → evicted flag. */
|
|
108
|
+
evicted = new Set();
|
|
109
|
+
constructor(config = {}) {
|
|
110
|
+
this.interfaceName = config.interfaceName ?? DEFAULT_INTERFACE;
|
|
111
|
+
this.listenPort = config.listenPort ?? DEFAULT_LISTEN_PORT;
|
|
112
|
+
this.meshSubnet = config.meshSubnet ?? DEFAULT_MESH_SUBNET;
|
|
113
|
+
}
|
|
114
|
+
/** Bind the local WG identity. Required before buildInterfaceConfig(). */
|
|
115
|
+
setLocalIdentity(key, meshIP) {
|
|
116
|
+
this.localKey = key;
|
|
117
|
+
this.localMeshIP = meshIP;
|
|
118
|
+
}
|
|
119
|
+
getInterfaceName() {
|
|
120
|
+
return this.interfaceName;
|
|
121
|
+
}
|
|
122
|
+
getMeshSubnet() {
|
|
123
|
+
return this.meshSubnet;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Build a `wg-quick`-compatible config string from current peers.
|
|
127
|
+
*
|
|
128
|
+
* Peers below WG_MIN_MESH_TRUST (UNTRUSTED) are excluded entirely.
|
|
129
|
+
* Suspended peers stay in the config but with `AllowedIPs =` empty
|
|
130
|
+
* (soft-block). Evicted peers are dropped completely.
|
|
131
|
+
*
|
|
132
|
+
* Operator writes this to `/etc/wireguard/<interface>.conf` and runs
|
|
133
|
+
* `wg-quick up <interface>`.
|
|
134
|
+
*/
|
|
135
|
+
buildInterfaceConfig(peers) {
|
|
136
|
+
if (!this.localKey || !this.localMeshIP) {
|
|
137
|
+
throw new Error('WgMeshService.buildInterfaceConfig: local identity not set; call setLocalIdentity() first');
|
|
138
|
+
}
|
|
139
|
+
const lines = [];
|
|
140
|
+
lines.push('# Generated by ruflo federation plugin — ADR-111 Phase 2.');
|
|
141
|
+
lines.push('# Operator MUST review before `wg-quick up`. Trust-graded port');
|
|
142
|
+
lines.push('# rules (WG_NETWORK_GATES) are not enforced here — Phase 4 will');
|
|
143
|
+
lines.push('# project them into nftables/pf. v1 uses mesh-IP isolation only.');
|
|
144
|
+
lines.push('[Interface]');
|
|
145
|
+
lines.push(`PrivateKey = ${this.localKey.privateKey}`);
|
|
146
|
+
lines.push(`Address = ${this.localMeshIP}`);
|
|
147
|
+
lines.push(`ListenPort = ${this.listenPort}`);
|
|
148
|
+
lines.push('');
|
|
149
|
+
for (const peer of peers) {
|
|
150
|
+
if (peer.trustLevel < WG_MIN_MESH_TRUST)
|
|
151
|
+
continue;
|
|
152
|
+
// Security: validate every spliced field. A compromised but-signed
|
|
153
|
+
// peer can otherwise inject extra [Peer] blocks via newline-laden
|
|
154
|
+
// wgEndpoint. readSafePeerWgFields enforces base64/CIDR/host:port
|
|
155
|
+
// regexes; mismatches skip the peer entirely (safer than partial-write).
|
|
156
|
+
const safe = readSafePeerWgFields(peer);
|
|
157
|
+
if (!safe)
|
|
158
|
+
continue;
|
|
159
|
+
if (this.evicted.has(safe.wgPublicKey))
|
|
160
|
+
continue;
|
|
161
|
+
const allowed = this.suspended.has(safe.wgPublicKey)
|
|
162
|
+
? ''
|
|
163
|
+
: this.computeAllowedIPs(peer, safe.wgMeshIP).join(', ');
|
|
164
|
+
lines.push(`# Peer ${peer.nodeId} — trust=${getTrustLevelLabel(peer.trustLevel)}${this.suspended.has(safe.wgPublicKey) ? ' (SUSPENDED)' : ''}`);
|
|
165
|
+
lines.push('[Peer]');
|
|
166
|
+
lines.push(`PublicKey = ${safe.wgPublicKey}`);
|
|
167
|
+
lines.push(`Endpoint = ${safe.wgEndpoint}`);
|
|
168
|
+
lines.push(`AllowedIPs = ${allowed}`);
|
|
169
|
+
lines.push('');
|
|
170
|
+
}
|
|
171
|
+
return lines.join('\n');
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Compute the AllowedIPs slice for a peer at its current trust level.
|
|
175
|
+
* v1: each peer gets its own /32 mesh IP. Phase 4 will narrow this
|
|
176
|
+
* further via firewall rules; v1 keeps L3 broad and relies on the app
|
|
177
|
+
* layer + trust gates for access decisions.
|
|
178
|
+
*/
|
|
179
|
+
computeAllowedIPs(peer, meshIP) {
|
|
180
|
+
if (peer.trustLevel === TrustLevel.UNTRUSTED)
|
|
181
|
+
return [];
|
|
182
|
+
const ips = [meshIP];
|
|
183
|
+
this.lastAppliedAllowedIPs.set(peer.nodeId, ips);
|
|
184
|
+
return ips;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Apply trust level → AllowedIPs as a `wg set` command for an
|
|
188
|
+
* already-running interface. Used when a peer joins or its trust level
|
|
189
|
+
* changes without needing a full config rewrite.
|
|
190
|
+
*/
|
|
191
|
+
applyTrustLevelToAllowedIPs(peer, meshIP, pubkey) {
|
|
192
|
+
const ips = this.computeAllowedIPs(peer, meshIP);
|
|
193
|
+
return {
|
|
194
|
+
verb: 'set-allowed-ips',
|
|
195
|
+
peerPublicKey: pubkey,
|
|
196
|
+
cmd: this.formatCmd(`peer ${pubkey} allowed-ips ${ips.join(',')}`),
|
|
197
|
+
rationale: `apply AllowedIPs for ${peer.nodeId} at trust=${getTrustLevelLabel(peer.trustLevel)}`,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Phase 3 hook — peer SUSPENDED. Clear AllowedIPs (soft-block: keeps
|
|
202
|
+
* the peer's public key registered with the interface but blocks all
|
|
203
|
+
* routing to/from it).
|
|
204
|
+
*/
|
|
205
|
+
removeAllowedIPs(peer, pubkey, reason) {
|
|
206
|
+
this.suspended.add(pubkey);
|
|
207
|
+
return {
|
|
208
|
+
verb: 'remove-allowed-ips',
|
|
209
|
+
peerPublicKey: pubkey,
|
|
210
|
+
cmd: this.formatCmd(`peer ${pubkey} allowed-ips ""`),
|
|
211
|
+
rationale: `SUSPEND ${peer.nodeId}${reason ? ` (${reason})` : ''} — clear AllowedIPs (soft-block)`,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Phase 3 hook — peer reactivated. Restore the AllowedIPs slice that
|
|
216
|
+
* was active before suspension. Returns null if the peer was never
|
|
217
|
+
* suspended (idempotent).
|
|
218
|
+
*/
|
|
219
|
+
restoreAllowedIPs(peer, meshIP, pubkey) {
|
|
220
|
+
if (!this.suspended.has(pubkey))
|
|
221
|
+
return null;
|
|
222
|
+
this.suspended.delete(pubkey);
|
|
223
|
+
const previous = this.lastAppliedAllowedIPs.get(peer.nodeId);
|
|
224
|
+
const ips = previous && previous.length > 0 ? previous : this.computeAllowedIPs(peer, meshIP);
|
|
225
|
+
return {
|
|
226
|
+
verb: 'set-allowed-ips',
|
|
227
|
+
peerPublicKey: pubkey,
|
|
228
|
+
cmd: this.formatCmd(`peer ${pubkey} allowed-ips ${ips.join(',')}`),
|
|
229
|
+
rationale: `REACTIVATE ${peer.nodeId} — restore AllowedIPs ${ips.join(',')}`,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Phase 3 hook — peer EVICTED. Terminal removal from the mesh; the
|
|
234
|
+
* peer's `[Peer]` block is dropped on next config rebuild and an
|
|
235
|
+
* immediate `wg set <iface> peer <pk> remove` is emitted to flush
|
|
236
|
+
* runtime state without a config reload.
|
|
237
|
+
*/
|
|
238
|
+
removePeer(peer, pubkey, reason) {
|
|
239
|
+
this.evicted.add(pubkey);
|
|
240
|
+
this.suspended.delete(pubkey);
|
|
241
|
+
this.lastAppliedAllowedIPs.delete(peer.nodeId);
|
|
242
|
+
return {
|
|
243
|
+
verb: 'remove-peer',
|
|
244
|
+
peerPublicKey: pubkey,
|
|
245
|
+
cmd: this.formatCmd(`peer ${pubkey} remove`),
|
|
246
|
+
rationale: `EVICT ${peer.nodeId}${reason ? ` (${reason})` : ''} — drop from mesh`,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/** Summarize the mesh state for `federation_wg_status` (Phase 6) and audit. */
|
|
250
|
+
summarize(peers) {
|
|
251
|
+
return peers
|
|
252
|
+
.filter(p => p.trustLevel >= WG_MIN_MESH_TRUST)
|
|
253
|
+
.map(peer => {
|
|
254
|
+
// Security: same validation as buildInterfaceConfig. If a peer
|
|
255
|
+
// published unsafe wg fields they get reported with empty strings —
|
|
256
|
+
// visible in operator status without becoming an injection vector.
|
|
257
|
+
const safe = readSafePeerWgFields(peer);
|
|
258
|
+
const meshIP = safe?.wgMeshIP ?? '';
|
|
259
|
+
const pubkey = safe?.wgPublicKey ?? '';
|
|
260
|
+
const endpoint = safe?.wgEndpoint ?? '';
|
|
261
|
+
let state = 'active';
|
|
262
|
+
if (pubkey && this.evicted.has(pubkey))
|
|
263
|
+
state = 'evicted';
|
|
264
|
+
else if (pubkey && this.suspended.has(pubkey))
|
|
265
|
+
state = 'suspended';
|
|
266
|
+
return {
|
|
267
|
+
nodeId: peer.nodeId,
|
|
268
|
+
trustLevel: peer.trustLevel,
|
|
269
|
+
trustLabel: getTrustLevelLabel(peer.trustLevel),
|
|
270
|
+
meshIP,
|
|
271
|
+
endpoint,
|
|
272
|
+
publicKey: pubkey,
|
|
273
|
+
state,
|
|
274
|
+
allowedIPs: safe && state === 'active' ? this.computeAllowedIPs(peer, safe.wgMeshIP) : [],
|
|
275
|
+
};
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Defense-in-depth: validate that all bits we splice into a shell
|
|
280
|
+
* command are alphanumeric / base64 / WG-allowed chars. Refuses the
|
|
281
|
+
* command rather than ship a substring that might escape its slot.
|
|
282
|
+
*/
|
|
283
|
+
formatCmd(args) {
|
|
284
|
+
// Allow: base64 pubkey chars [A-Za-z0-9+/=], IPv4 mesh chars
|
|
285
|
+
// [0-9./], plus a handful of fixed verbs. Reject everything else.
|
|
286
|
+
if (!/^[A-Za-z0-9+/=., "/-]*$/.test(args)) {
|
|
287
|
+
throw new Error(`WgMeshService.formatCmd: refusing arg with unsafe chars: ${JSON.stringify(args)}`);
|
|
288
|
+
}
|
|
289
|
+
return `wg set ${this.interfaceName} ${args}`;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
//# sourceMappingURL=wg-mesh-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wg-mesh-service.js","sourceRoot":"","sources":["../../../src/domain/services/wg-mesh-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-111 Phase 5 — Witness attestation chain for WG mesh mutations.
|
|
3
|
+
*
|
|
4
|
+
* Every coordination change (peer added/suspended/evicted/reactivated,
|
|
5
|
+
* keypair rotated) appends a signed entry to an append-only log
|
|
6
|
+
* `.claude-flow/federation/wg-changes.log`. The signature is over a
|
|
7
|
+
* canonical JSON encoding of the entry's content fields, using the
|
|
8
|
+
* operator's federation Ed25519 key — the same identity that signs
|
|
9
|
+
* federation manifests, so anyone who already trusts the manifest chain
|
|
10
|
+
* automatically trusts the WG change chain.
|
|
11
|
+
*
|
|
12
|
+
* Append-only via O_APPEND open + entries chained by prevHash (sha256 of
|
|
13
|
+
* the previous entry's canonical bytes). Tampering with history requires
|
|
14
|
+
* forging a chain of signatures with the operator's key.
|
|
15
|
+
*
|
|
16
|
+
* The periodic ruflo witness regen (plugins/ruflo-core/scripts/witness/)
|
|
17
|
+
* includes wg-changes.log as one of its watched artifacts (operator wires
|
|
18
|
+
* this via witness-fixes.json).
|
|
19
|
+
*
|
|
20
|
+
* Pure service: emits the canonical bytes and the signature to write.
|
|
21
|
+
* Caller does the I/O so this stays unit-testable without fs mocks.
|
|
22
|
+
*/
|
|
23
|
+
import type { WgCommand } from './wg-mesh-service.js';
|
|
24
|
+
export type WgWitnessEventType = 'peer-added' | 'peer-removed-suspended' | 'peer-restored' | 'peer-evicted' | 'key-rotated' | 'interface-config-applied';
|
|
25
|
+
/**
|
|
26
|
+
* Canonical fields of a witness entry. The hash + signature are computed
|
|
27
|
+
* over these in a stable JSON encoding (sorted keys, no whitespace).
|
|
28
|
+
*/
|
|
29
|
+
export interface WgWitnessContent {
|
|
30
|
+
readonly version: '1';
|
|
31
|
+
readonly type: WgWitnessEventType;
|
|
32
|
+
readonly timestamp: string;
|
|
33
|
+
readonly nodeId: string;
|
|
34
|
+
/** Public key of the affected peer, if any. */
|
|
35
|
+
readonly peerPublicKey?: string;
|
|
36
|
+
/** Mesh IP affected, if any. */
|
|
37
|
+
readonly meshIP?: string;
|
|
38
|
+
/** wg command that was emitted, if any. */
|
|
39
|
+
readonly wgCommand?: string;
|
|
40
|
+
/** Human-readable rationale for the audit log. */
|
|
41
|
+
readonly rationale: string;
|
|
42
|
+
/** Hash of the previous entry in the chain (sha256 hex), or empty for the genesis entry. */
|
|
43
|
+
readonly prevHash: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Full witness entry as it appears on disk — content + the operator's
|
|
47
|
+
* Ed25519 signature over the canonical encoding of content.
|
|
48
|
+
*/
|
|
49
|
+
export interface WgWitnessEntry {
|
|
50
|
+
readonly content: WgWitnessContent;
|
|
51
|
+
readonly hash: string;
|
|
52
|
+
readonly signature: string;
|
|
53
|
+
}
|
|
54
|
+
export interface WgWitnessSigner {
|
|
55
|
+
/** Returns an Ed25519 signature (base64) over the given canonical bytes. */
|
|
56
|
+
sign(bytes: Buffer): Promise<string>;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Stable JSON serialization for hashing/signing. Keys are sorted; no
|
|
60
|
+
* whitespace; undefined fields omitted. Same shape on every host so
|
|
61
|
+
* cross-host verifiers produce identical hashes.
|
|
62
|
+
*/
|
|
63
|
+
export declare function canonicalizeContent(content: WgWitnessContent): Buffer;
|
|
64
|
+
export declare function hashContent(content: WgWitnessContent): string;
|
|
65
|
+
export declare class WgWitnessService {
|
|
66
|
+
private readonly nodeId;
|
|
67
|
+
private readonly signer;
|
|
68
|
+
private lastHash;
|
|
69
|
+
constructor(nodeId: string, signer: WgWitnessSigner);
|
|
70
|
+
/**
|
|
71
|
+
* Override the previous-hash pointer (used when resuming from an existing
|
|
72
|
+
* log on disk). Caller reads the last entry from `wg-changes.log` and
|
|
73
|
+
* passes its `hash` here before appending more.
|
|
74
|
+
*/
|
|
75
|
+
setLastHash(hash: string): void;
|
|
76
|
+
/** Build + sign an entry. Caller is responsible for the actual append-write. */
|
|
77
|
+
build(type: WgWitnessEventType, fields: Omit<WgWitnessContent, 'version' | 'type' | 'timestamp' | 'nodeId' | 'prevHash'>): Promise<WgWitnessEntry>;
|
|
78
|
+
/**
|
|
79
|
+
* Convenience: take a WgCommand emitted by WgMeshService Phase 2/3 and
|
|
80
|
+
* produce a witness entry of the right type. Maps verb → eventType.
|
|
81
|
+
*/
|
|
82
|
+
attestWgCommand(cmd: WgCommand, meshIP?: string): Promise<WgWitnessEntry>;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Verify a single entry's signature + hash. Caller verifies the chain by
|
|
86
|
+
* iterating entries and confirming each entry's `prevHash` equals the
|
|
87
|
+
* preceding entry's `hash`.
|
|
88
|
+
*/
|
|
89
|
+
export declare function verifyWitnessEntry(entry: WgWitnessEntry, verify: (bytes: Buffer, signature: string) => Promise<boolean>): Promise<boolean>;
|
|
90
|
+
/**
|
|
91
|
+
* Verify a full chain — every entry valid + chain link unbroken.
|
|
92
|
+
*/
|
|
93
|
+
export declare function verifyWitnessChain(entries: readonly WgWitnessEntry[], verify: (bytes: Buffer, signature: string) => Promise<boolean>): Promise<{
|
|
94
|
+
ok: boolean;
|
|
95
|
+
failedAt?: number;
|
|
96
|
+
reason?: string;
|
|
97
|
+
}>;
|
|
98
|
+
//# sourceMappingURL=wg-witness-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wg-witness-service.d.ts","sourceRoot":"","sources":["../../../src/domain/services/wg-witness-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtD,MAAM,MAAM,kBAAkB,GAC1B,YAAY,GACZ,wBAAwB,GACxB,eAAe,GACf,cAAc,GACd,aAAa,GACb,0BAA0B,CAAC;AAE/B;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,GAAG,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+CAA+C;IAC/C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,gCAAgC;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,2CAA2C;IAC3C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,kDAAkD;IAClD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,4FAA4F;IAC5F,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,4EAA4E;IAC5E,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACtC;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAOrE;AAED,wBAAgB,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAE7D;AAED,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,QAAQ,CAAc;gBAElB,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe;IAKnD;;;;OAIG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAI/B,gFAAgF;IAC1E,KAAK,CACT,IAAI,EAAE,kBAAkB,EACxB,MAAM,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,GAAG,UAAU,CAAC,GACvF,OAAO,CAAC,cAAc,CAAC;IAgB1B;;;OAGG;IACG,eAAe,CAAC,GAAG,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;CAchF;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAC7D,OAAO,CAAC,OAAO,CAAC,CAKlB;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,SAAS,cAAc,EAAE,EAClC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAC7D,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAc9D"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-111 Phase 5 — Witness attestation chain for WG mesh mutations.
|
|
3
|
+
*
|
|
4
|
+
* Every coordination change (peer added/suspended/evicted/reactivated,
|
|
5
|
+
* keypair rotated) appends a signed entry to an append-only log
|
|
6
|
+
* `.claude-flow/federation/wg-changes.log`. The signature is over a
|
|
7
|
+
* canonical JSON encoding of the entry's content fields, using the
|
|
8
|
+
* operator's federation Ed25519 key — the same identity that signs
|
|
9
|
+
* federation manifests, so anyone who already trusts the manifest chain
|
|
10
|
+
* automatically trusts the WG change chain.
|
|
11
|
+
*
|
|
12
|
+
* Append-only via O_APPEND open + entries chained by prevHash (sha256 of
|
|
13
|
+
* the previous entry's canonical bytes). Tampering with history requires
|
|
14
|
+
* forging a chain of signatures with the operator's key.
|
|
15
|
+
*
|
|
16
|
+
* The periodic ruflo witness regen (plugins/ruflo-core/scripts/witness/)
|
|
17
|
+
* includes wg-changes.log as one of its watched artifacts (operator wires
|
|
18
|
+
* this via witness-fixes.json).
|
|
19
|
+
*
|
|
20
|
+
* Pure service: emits the canonical bytes and the signature to write.
|
|
21
|
+
* Caller does the I/O so this stays unit-testable without fs mocks.
|
|
22
|
+
*/
|
|
23
|
+
import { createHash } from 'node:crypto';
|
|
24
|
+
/**
|
|
25
|
+
* Stable JSON serialization for hashing/signing. Keys are sorted; no
|
|
26
|
+
* whitespace; undefined fields omitted. Same shape on every host so
|
|
27
|
+
* cross-host verifiers produce identical hashes.
|
|
28
|
+
*/
|
|
29
|
+
export function canonicalizeContent(content) {
|
|
30
|
+
const keys = Object.keys(content).sort();
|
|
31
|
+
const ordered = {};
|
|
32
|
+
for (const k of keys) {
|
|
33
|
+
if (content[k] !== undefined)
|
|
34
|
+
ordered[k] = content[k];
|
|
35
|
+
}
|
|
36
|
+
return Buffer.from(JSON.stringify(ordered), 'utf-8');
|
|
37
|
+
}
|
|
38
|
+
export function hashContent(content) {
|
|
39
|
+
return createHash('sha256').update(canonicalizeContent(content)).digest('hex');
|
|
40
|
+
}
|
|
41
|
+
export class WgWitnessService {
|
|
42
|
+
nodeId;
|
|
43
|
+
signer;
|
|
44
|
+
lastHash = '';
|
|
45
|
+
constructor(nodeId, signer) {
|
|
46
|
+
this.nodeId = nodeId;
|
|
47
|
+
this.signer = signer;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Override the previous-hash pointer (used when resuming from an existing
|
|
51
|
+
* log on disk). Caller reads the last entry from `wg-changes.log` and
|
|
52
|
+
* passes its `hash` here before appending more.
|
|
53
|
+
*/
|
|
54
|
+
setLastHash(hash) {
|
|
55
|
+
this.lastHash = hash;
|
|
56
|
+
}
|
|
57
|
+
/** Build + sign an entry. Caller is responsible for the actual append-write. */
|
|
58
|
+
async build(type, fields) {
|
|
59
|
+
const content = {
|
|
60
|
+
version: '1',
|
|
61
|
+
type,
|
|
62
|
+
timestamp: new Date().toISOString(),
|
|
63
|
+
nodeId: this.nodeId,
|
|
64
|
+
prevHash: this.lastHash,
|
|
65
|
+
...fields,
|
|
66
|
+
};
|
|
67
|
+
const canonical = canonicalizeContent(content);
|
|
68
|
+
const hash = createHash('sha256').update(canonical).digest('hex');
|
|
69
|
+
const signature = await this.signer.sign(canonical);
|
|
70
|
+
this.lastHash = hash;
|
|
71
|
+
return { content, hash, signature };
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Convenience: take a WgCommand emitted by WgMeshService Phase 2/3 and
|
|
75
|
+
* produce a witness entry of the right type. Maps verb → eventType.
|
|
76
|
+
*/
|
|
77
|
+
async attestWgCommand(cmd, meshIP) {
|
|
78
|
+
const typeMap = {
|
|
79
|
+
'add-peer': 'peer-added',
|
|
80
|
+
'set-allowed-ips': 'peer-restored',
|
|
81
|
+
'remove-allowed-ips': 'peer-removed-suspended',
|
|
82
|
+
'remove-peer': 'peer-evicted',
|
|
83
|
+
};
|
|
84
|
+
return this.build(typeMap[cmd.verb], {
|
|
85
|
+
peerPublicKey: cmd.peerPublicKey,
|
|
86
|
+
meshIP,
|
|
87
|
+
wgCommand: cmd.cmd,
|
|
88
|
+
rationale: cmd.rationale,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Verify a single entry's signature + hash. Caller verifies the chain by
|
|
94
|
+
* iterating entries and confirming each entry's `prevHash` equals the
|
|
95
|
+
* preceding entry's `hash`.
|
|
96
|
+
*/
|
|
97
|
+
export async function verifyWitnessEntry(entry, verify) {
|
|
98
|
+
const canonical = canonicalizeContent(entry.content);
|
|
99
|
+
const expectedHash = createHash('sha256').update(canonical).digest('hex');
|
|
100
|
+
if (entry.hash !== expectedHash)
|
|
101
|
+
return false;
|
|
102
|
+
return verify(canonical, entry.signature);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Verify a full chain — every entry valid + chain link unbroken.
|
|
106
|
+
*/
|
|
107
|
+
export async function verifyWitnessChain(entries, verify) {
|
|
108
|
+
let prevHash = '';
|
|
109
|
+
for (let i = 0; i < entries.length; i++) {
|
|
110
|
+
const e = entries[i];
|
|
111
|
+
if (e.content.prevHash !== prevHash) {
|
|
112
|
+
return { ok: false, failedAt: i, reason: 'broken-chain-link' };
|
|
113
|
+
}
|
|
114
|
+
const validEntry = await verifyWitnessEntry(e, verify);
|
|
115
|
+
if (!validEntry) {
|
|
116
|
+
return { ok: false, failedAt: i, reason: 'invalid-signature-or-hash' };
|
|
117
|
+
}
|
|
118
|
+
prevHash = e.hash;
|
|
119
|
+
}
|
|
120
|
+
return { ok: true };
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=wg-witness-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wg-witness-service.js","sourceRoot":"","sources":["../../../src/domain/services/wg-witness-service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA+CzC;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAyB;IAC3D,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,EAAmC,CAAC;IAC1E,MAAM,OAAO,GAA4B,EAAE,CAAC;IAC5C,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,SAAS;YAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,OAAyB;IACnD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACjF,CAAC;AAED,MAAM,OAAO,gBAAgB;IACV,MAAM,CAAS;IACf,MAAM,CAAkB;IACjC,QAAQ,GAAW,EAAE,CAAC;IAE9B,YAAY,MAAc,EAAE,MAAuB;QACjD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED;;;;OAIG;IACH,WAAW,CAAC,IAAY;QACtB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;IACvB,CAAC;IAED,gFAAgF;IAChF,KAAK,CAAC,KAAK,CACT,IAAwB,EACxB,MAAwF;QAExF,MAAM,OAAO,GAAqB;YAChC,OAAO,EAAE,GAAG;YACZ,IAAI;YACJ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,GAAG,MAAM;SACV,CAAC;QACF,MAAM,SAAS,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;QAC/C,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAClE,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACpD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IACtC,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,eAAe,CAAC,GAAc,EAAE,MAAe;QACnD,MAAM,OAAO,GAAkD;YAC7D,UAAU,EAAE,YAAY;YACxB,iBAAiB,EAAE,eAAe;YAClC,oBAAoB,EAAE,wBAAwB;YAC9C,aAAa,EAAE,cAAc;SAC9B,CAAC;QACF,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE;YACnC,aAAa,EAAE,GAAG,CAAC,aAAa;YAChC,MAAM;YACN,SAAS,EAAE,GAAG,CAAC,GAAG;YAClB,SAAS,EAAE,GAAG,CAAC,SAAS;SACzB,CAAC,CAAC;IACL,CAAC;CACF;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,KAAqB,EACrB,MAA8D;IAE9D,MAAM,SAAS,GAAG,mBAAmB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACrD,MAAM,YAAY,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC1E,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;AAC5C,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAAkC,EAClC,MAA8D;IAE9D,IAAI,QAAQ,GAAG,EAAE,CAAC;IAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACrB,IAAI,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACpC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC;QACjE,CAAC;QACD,MAAM,UAAU,GAAG,MAAM,kBAAkB,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;QACvD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,EAAE,MAAM,EAAE,2BAA2B,EAAE,CAAC;QACzE,CAAC;QACD,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC;IACpB,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ADR-111 Phase 1 — WireGuard configuration value objects.
|
|
3
|
+
*
|
|
4
|
+
* Pure types + deterministic helpers. No I/O, no shell calls. Used by
|
|
5
|
+
* WgMeshService (Phase 2) and surfaced in FederationManifest (Phase 1) so
|
|
6
|
+
* peers can publish their WG identity inside the same Ed25519-signed
|
|
7
|
+
* manifest that already carries their federation identity.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The WG section of a FederationManifest. Optional — present only when
|
|
11
|
+
* the local node has opted in via `config.wgMesh: true`.
|
|
12
|
+
*
|
|
13
|
+
* - publicKey is the X25519 public key in base64 (32-byte raw → 44 chars
|
|
14
|
+
* including '=' padding), exactly the format `wg-quick`/`wg setconf`
|
|
15
|
+
* accept. Never include the private key in the manifest.
|
|
16
|
+
* - endpoint is `host:port` reachable on UDP. May be a hostname (resolved
|
|
17
|
+
* by the consumer) or an IPv4/v6 literal. The port is WireGuard's, not
|
|
18
|
+
* federation's WS port — usually 51820.
|
|
19
|
+
* - meshIP is the assigned /32 inside the federation mesh subnet
|
|
20
|
+
* (10.50.0.0/16 by default). Stored as `a.b.c.d/32`.
|
|
21
|
+
*/
|
|
22
|
+
export interface WgManifestSection {
|
|
23
|
+
readonly publicKey: string;
|
|
24
|
+
readonly endpoint: string;
|
|
25
|
+
readonly meshIP: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A locally-generated WG keypair. Kept entirely off the wire — `publicKey`
|
|
29
|
+
* is what gets published in the manifest; `privateKey` stays on disk
|
|
30
|
+
* (mode 0600) and in this in-memory object.
|
|
31
|
+
*/
|
|
32
|
+
export interface WgLocalKey {
|
|
33
|
+
readonly publicKey: string;
|
|
34
|
+
readonly privateKey: string;
|
|
35
|
+
readonly createdAt: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Default federation mesh subnet. RFC1918 private space outside the
|
|
39
|
+
* common LAN/k8s ranges and Tailscale's 100.64.0.0/10. Documented in
|
|
40
|
+
* the ADR. Birthday-collision probability stays under 1% up to ~36
|
|
41
|
+
* peers and under 50% at ~302 peers — within the v1 ≤50-peer target.
|
|
42
|
+
*/
|
|
43
|
+
export declare const DEFAULT_MESH_SUBNET = "10.50.0.0/16";
|
|
44
|
+
/**
|
|
45
|
+
* Generate an X25519 keypair in the format WireGuard accepts.
|
|
46
|
+
*
|
|
47
|
+
* WireGuard keys are 32 raw bytes encoded base64 (44 chars with '='
|
|
48
|
+
* padding). Node's `generateKeyPairSync('x25519')` returns PKCS8/SPKI
|
|
49
|
+
* DER envelopes — we extract the trailing 32 bytes which are the raw
|
|
50
|
+
* key material (the DER prefix is constant for X25519).
|
|
51
|
+
*
|
|
52
|
+
* No new dependencies: Node 18+ has X25519 support in core crypto.
|
|
53
|
+
*/
|
|
54
|
+
export declare function generateWgKeyPair(): WgLocalKey;
|
|
55
|
+
/**
|
|
56
|
+
* Derive a deterministic mesh IP for `nodeId` inside `subnet`.
|
|
57
|
+
*
|
|
58
|
+
* Strategy: sha256(nodeId) → top bytes interpreted as host portion of the
|
|
59
|
+
* subnet, clamped to avoid the network address (.0) and broadcast (.255)
|
|
60
|
+
* of any /24 inside the subnet. This is collision-resistant in the
|
|
61
|
+
* birthday-paradox sense — see ADR for thresholds.
|
|
62
|
+
*
|
|
63
|
+
* `usedIPs` (when provided) gives previously-assigned IPs in the mesh.
|
|
64
|
+
* If the derived IP is already taken, the function probes `nodeId + '\x00'`,
|
|
65
|
+
* `nodeId + '\x01'`, … until a free slot is found. Bounded to 1024 probes;
|
|
66
|
+
* exceeding that means the subnet is functionally exhausted and the caller
|
|
67
|
+
* should jump to a larger range (see ADR `10.50.0.0/12` recommendation).
|
|
68
|
+
*/
|
|
69
|
+
export declare function deriveMeshIP(nodeId: string, subnet?: string, usedIPs?: ReadonlySet<string>): string;
|
|
70
|
+
//# sourceMappingURL=wg-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wg-config.d.ts","sourceRoot":"","sources":["../../../src/domain/value-objects/wg-config.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAElD;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,IAAI,UAAU,CAgB9C;AAuBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,MAA4B,EACpC,OAAO,GAAE,WAAW,CAAC,MAAM,CAAa,GACvC,MAAM,CAsBR"}
|