@camstack/system 1.2.419 → 1.2.420
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/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +136 -16
- package/dist/builtins/system-config/system-config.addon.mjs +413 -294
- package/dist/index.js +183 -15
- package/dist/index.mjs +180 -18
- package/dist/kernel/index.d.ts +5 -1
- package/dist/kernel/moleculer/agent-event-scope.d.ts +49 -0
- package/dist/kernel/moleculer/agent-mesh-authenticator.d.ts +15 -3
- package/dist/kernel/moleculer/broker-factory.d.ts +6 -0
- package/dist/kernel/moleculer/mesh-access-middleware.d.ts +8 -4
- package/dist/kernel/moleculer/mesh-join-hold.d.ts +85 -0
- package/dist/kernel/moleculer/mesh-mtls/index.d.ts +1 -1
- package/dist/kernel/moleculer/mesh-mtls/tls-tcp-transporter.d.ts +65 -4
- package/dist/{restore-archive-gGHgAZsz.mjs → restore-archive-Bt65Em24.mjs} +76 -76
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Pn as storageOccupancyCapability, Y as backupCapability, Zr as toNodeId, cr as BaseAddon, ei as EventCategory, en as mayWriteToLocation, zr as nodePin } from "../../dist-C0wYvAHC.mjs";
|
|
2
|
-
import { a as BackupSnapshotRefusedError, c as BackupKeyFormatError, d as generateBackupKeyPair, f as parseBackupPublicKey, i as BackupCancelledError, n as precheckRestoreKey, o as SystemBackupService, p as parseBackupSecretKey, r as prepareRestoreArchive, u as ENCRYPTED_BACKUP_SUFFIX } from "../../restore-archive-
|
|
2
|
+
import { a as BackupSnapshotRefusedError, c as BackupKeyFormatError, d as generateBackupKeyPair, f as parseBackupPublicKey, i as BackupCancelledError, n as precheckRestoreKey, o as SystemBackupService, p as parseBackupSecretKey, r as prepareRestoreArchive, u as ENCRYPTED_BACKUP_SUFFIX } from "../../restore-archive-Bt65Em24.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import * as fsp from "node:fs/promises";
|
|
5
5
|
import * as path$1 from "node:path";
|
|
@@ -86720,15 +86720,66 @@ import_moleculer.default.Validators;
|
|
|
86720
86720
|
//#endregion
|
|
86721
86721
|
//#region src/kernel/moleculer/mesh-mtls/tls-tcp-transporter.ts
|
|
86722
86722
|
/**
|
|
86723
|
-
* Moleculer TCP transporter with a mutual-TLS listener (D826).
|
|
86723
|
+
* Moleculer TCP transporter with a mutual-TLS listener (D826, D848).
|
|
86724
86724
|
*
|
|
86725
86725
|
* Hub: `hubTlsOnly` — no clear TCP reader and no UDP discovery; TLS 1.3 on
|
|
86726
|
-
* `tlsPort` (default 6443) once mesh material is ready
|
|
86727
|
-
*
|
|
86728
|
-
*
|
|
86729
|
-
* is
|
|
86726
|
+
* `tlsPort` (default 6443) once mesh material is ready, and every peer is
|
|
86727
|
+
* dialled on mutual TLS too (a TLS-only hub has no clear peer to talk to).
|
|
86728
|
+
*
|
|
86729
|
+
* Enrolled agent: `hubOnlyPeers` (D848) — the hub is its ONLY mesh peer. No
|
|
86730
|
+
* clear TCP reader; the TLS listener accepts the hub's certificate only; the
|
|
86731
|
+
* writer dials the hub only; and the nodes the hub's gossip names are not
|
|
86732
|
+
* adopted, so the agent never learns, dials or broadcasts to another agent.
|
|
86733
|
+
* An agent without mesh material keeps clear TCP (it can reach only a
|
|
86734
|
+
* pre-D826 hub).
|
|
86735
|
+
*
|
|
86736
|
+
* On a TLS socket, any packet whose `sender` ≠ the peer certificate CN is
|
|
86737
|
+
* dropped. A TLS-listening node advertises its listen port in the gossip
|
|
86738
|
+
* HELLO it sends on every connection (`meshTlsPort`,
|
|
86739
|
+
* {@link MESH_TLS_PORT_HELLO_FIELD}); a peer dials it there, else on the
|
|
86740
|
+
* cluster-wide default (6443) — so two nodes on one host need not collide.
|
|
86730
86741
|
*/
|
|
86742
|
+
/** Field of the gossip HELLO payload carrying the sender's mesh-TLS listen port. */
|
|
86743
|
+
var MESH_TLS_PORT_HELLO_FIELD = "meshTlsPort";
|
|
86744
|
+
/** Moleculer packet types (`packets.PACKET_GOSSIP_*`). */
|
|
86745
|
+
var PACKET_GOSSIP_RES = "GOSSIP_RES";
|
|
86746
|
+
var PACKET_GOSSIP_HELLO = "GOSSIP_HELLO";
|
|
86747
|
+
/** The hub's node id — the only peer of a `hubOnlyPeers` agent. */
|
|
86748
|
+
var DEFAULT_HUB_NODE_ID = "hub";
|
|
86731
86749
|
var TcpTransporterBase = Transporters.TCP;
|
|
86750
|
+
/** The mesh-TLS port a gossip HELLO payload advertises, or null. */
|
|
86751
|
+
function helloMeshTlsPort(payload) {
|
|
86752
|
+
if (payload === null || typeof payload !== "object") return null;
|
|
86753
|
+
const port = Reflect.get(payload, MESH_TLS_PORT_HELLO_FIELD);
|
|
86754
|
+
return typeof port === "number" && Number.isInteger(port) && port > 0 && port < 65536 ? port : null;
|
|
86755
|
+
}
|
|
86756
|
+
/**
|
|
86757
|
+
* A gossip response with every node but `keep` removed. With nothing to
|
|
86758
|
+
* remove the payload is returned unchanged.
|
|
86759
|
+
*/
|
|
86760
|
+
function keepOnlyGossipNodes(payload, keep) {
|
|
86761
|
+
if (payload === null || typeof payload !== "object") return {
|
|
86762
|
+
payload,
|
|
86763
|
+
removed: []
|
|
86764
|
+
};
|
|
86765
|
+
const removed = [];
|
|
86766
|
+
const filtered = { ...payload };
|
|
86767
|
+
for (const section of ["online", "offline"]) {
|
|
86768
|
+
const entries = Reflect.get(payload, section);
|
|
86769
|
+
if (entries === null || typeof entries !== "object") continue;
|
|
86770
|
+
const kept = {};
|
|
86771
|
+
for (const [nodeId, value] of Object.entries(entries)) if (keep.has(nodeId)) kept[nodeId] = value;
|
|
86772
|
+
else removed.push(nodeId);
|
|
86773
|
+
filtered[section] = kept;
|
|
86774
|
+
}
|
|
86775
|
+
return removed.length === 0 ? {
|
|
86776
|
+
payload,
|
|
86777
|
+
removed
|
|
86778
|
+
} : {
|
|
86779
|
+
payload: filtered,
|
|
86780
|
+
removed
|
|
86781
|
+
};
|
|
86782
|
+
}
|
|
86732
86783
|
/**
|
|
86733
86784
|
* Extract `sender` from a Moleculer TCP packet without fully trusting it —
|
|
86734
86785
|
* used only to compare against the TLS peer CN.
|
|
@@ -86770,6 +86821,13 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86770
86821
|
tlsPeers = /* @__PURE__ */ new Map();
|
|
86771
86822
|
clearDisabled = false;
|
|
86772
86823
|
hubTlsOnly;
|
|
86824
|
+
hubOnlyPeers;
|
|
86825
|
+
hubNodeId;
|
|
86826
|
+
peerTlsPortOption;
|
|
86827
|
+
/** Peers already reported as not adopted (D848) — one line each. */
|
|
86828
|
+
ignoredPeers = /* @__PURE__ */ new Set();
|
|
86829
|
+
/** Node id → the mesh-TLS port its last HELLO advertised. */
|
|
86830
|
+
helloTlsPorts = /* @__PURE__ */ new Map();
|
|
86773
86831
|
/** Clear-text `TcpWriter.connect` captured once before the TLS wrap (A→B). */
|
|
86774
86832
|
clearWriterConnect = null;
|
|
86775
86833
|
writerTlsPatched = false;
|
|
@@ -86778,8 +86836,15 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86778
86836
|
this.meshTls = opts.meshTls ?? null;
|
|
86779
86837
|
this.crl = opts.crl ?? processMeshCrl();
|
|
86780
86838
|
this.hubTlsOnly = opts.hubTlsOnly === true;
|
|
86839
|
+
this.hubOnlyPeers = opts.hubOnlyPeers === true;
|
|
86840
|
+
this.hubNodeId = opts.hubNodeId ?? DEFAULT_HUB_NODE_ID;
|
|
86841
|
+
this.peerTlsPortOption = opts.peerTlsPort;
|
|
86781
86842
|
if (this.opts.tlsPort === void 0) this.opts.tlsPort = require_constants$8.DEFAULT_HUB_TLS_TCP_PORT;
|
|
86782
86843
|
}
|
|
86844
|
+
/** Clear TCP is never served: the TLS-only hub, or an agent whose only peer is the hub. */
|
|
86845
|
+
get tlsOnly() {
|
|
86846
|
+
return this.hubTlsOnly || this.hubOnlyPeers;
|
|
86847
|
+
}
|
|
86783
86848
|
startTcpServer() {
|
|
86784
86849
|
const started = super.startTcpServer();
|
|
86785
86850
|
if (this.meshTls !== null) this.patchWriterForTlsClient();
|
|
@@ -86789,8 +86854,8 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86789
86854
|
return this.Promise.resolve().then(() => {
|
|
86790
86855
|
if (this.opts.urls) return this.loadUrls();
|
|
86791
86856
|
}).then(() => this.startTcpServer()).then(() => {
|
|
86792
|
-
if (this.
|
|
86793
|
-
}).then(() => this.startTlsServerIfReady()).then(() => this.
|
|
86857
|
+
if (this.tlsOnly) this.disableClearTransport();
|
|
86858
|
+
}).then(() => this.startTlsServerIfReady()).then(() => this.tlsOnly ? this.Promise.resolve() : this.startUdpServer()).then(() => this.startTimers()).then(() => {
|
|
86794
86859
|
this.logger.info("TCP Transporter started.");
|
|
86795
86860
|
this.connected = true;
|
|
86796
86861
|
this.nodes.localNode.port = this.opts.port;
|
|
@@ -86798,17 +86863,19 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86798
86863
|
}).then(() => this.onConnected());
|
|
86799
86864
|
}
|
|
86800
86865
|
/**
|
|
86801
|
-
* Outbound dial uses mTLS when the
|
|
86802
|
-
*
|
|
86803
|
-
*
|
|
86804
|
-
* TLS
|
|
86866
|
+
* Outbound dial uses mTLS when dialling the hub node id (agent → hub), on a
|
|
86867
|
+
* TLS-only hub (it has no clear listener, so no clear peer exists to dial —
|
|
86868
|
+
* D848 closes the hub → agent clear leg), or when the peer is already known
|
|
86869
|
+
* on TLS (inbound handshake).
|
|
86805
86870
|
*/
|
|
86806
86871
|
shouldDialPeerWithTls(nodeID) {
|
|
86807
|
-
if (nodeID ===
|
|
86872
|
+
if (nodeID === this.hubNodeId) return true;
|
|
86873
|
+
if (this.hubTlsOnly) return true;
|
|
86808
86874
|
return this.tlsPeers.has(nodeID);
|
|
86809
86875
|
}
|
|
86810
|
-
|
|
86811
|
-
|
|
86876
|
+
/** The peer's advertised mesh-TLS port, else the configured dial port, else 6443. */
|
|
86877
|
+
peerTlsDialPort(nodeID) {
|
|
86878
|
+
return this.helloTlsPorts.get(nodeID) ?? this.peerTlsPortOption ?? this.opts.tlsPort ?? 6443;
|
|
86812
86879
|
}
|
|
86813
86880
|
/**
|
|
86814
86881
|
* Wrap `TcpWriter.connect` so TLS peers are dialed with mutual TLS on the
|
|
@@ -86828,13 +86895,14 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86828
86895
|
const crl = this.crl;
|
|
86829
86896
|
const self = this;
|
|
86830
86897
|
writer.connect = (nodeID) => {
|
|
86898
|
+
if (self.hubOnlyPeers && nodeID !== self.hubNodeId) return writer.Promise.reject(/* @__PURE__ */ new Error(`mesh: an enrolled agent dials only "${self.hubNodeId}", not "${nodeID}"`));
|
|
86831
86899
|
if (!self.shouldDialPeerWithTls(nodeID)) return clearConnect(nodeID);
|
|
86832
86900
|
const material = self.meshTls;
|
|
86833
86901
|
if (material === null) return clearConnect(nodeID);
|
|
86834
86902
|
const node = writer.transporter.getNode(nodeID);
|
|
86835
86903
|
if (!node) return writer.Promise.reject(/* @__PURE__ */ new Error(`Missing node info for '${nodeID}'!`));
|
|
86836
86904
|
const host = writer.transporter.getNodeAddress(node);
|
|
86837
|
-
const port = self.peerTlsDialPort();
|
|
86905
|
+
const port = self.peerTlsDialPort(nodeID);
|
|
86838
86906
|
writer.logger.debug(`Connecting (TLS) to '${nodeID}' via ${host}:${port}`);
|
|
86839
86907
|
return new writer.Promise((resolve, reject) => {
|
|
86840
86908
|
const socket = node_tls.connect({
|
|
@@ -86940,7 +87008,7 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86940
87008
|
}
|
|
86941
87009
|
/** The switch back: listen on the clear port again and restart UDP discovery. Idempotent. */
|
|
86942
87010
|
async enableClearTransport() {
|
|
86943
|
-
if (this.
|
|
87011
|
+
if (this.tlsOnly) return;
|
|
86944
87012
|
if (!this.clearDisabled) return;
|
|
86945
87013
|
this.clearDisabled = false;
|
|
86946
87014
|
if (this.reader !== null && this.reader.server?.listening !== true) await this.reader.listen();
|
|
@@ -86972,7 +87040,53 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
86972
87040
|
this.tlsPeers.clear();
|
|
86973
87041
|
return super.disconnect();
|
|
86974
87042
|
}
|
|
87043
|
+
/**
|
|
87044
|
+
* D848: an enrolled agent adopts no node from the hub's gossip but the hub
|
|
87045
|
+
* itself, so it never learns — and therefore never dials or broadcasts to —
|
|
87046
|
+
* another agent. Everything else is Moleculer's own handling.
|
|
87047
|
+
*/
|
|
87048
|
+
receive(cmd, data, socket) {
|
|
87049
|
+
if (cmd === PACKET_GOSSIP_HELLO) this.noteHelloTlsPort(data);
|
|
87050
|
+
if (!this.hubOnlyPeers || cmd !== PACKET_GOSSIP_RES) return super.receive(cmd, data, socket);
|
|
87051
|
+
let payload;
|
|
87052
|
+
try {
|
|
87053
|
+
payload = this.broker.serializer.deserialize(data, cmd);
|
|
87054
|
+
} catch {
|
|
87055
|
+
return super.receive(cmd, data, socket);
|
|
87056
|
+
}
|
|
87057
|
+
const filtered = keepOnlyGossipNodes(payload, new Set([this.hubNodeId, this.nodeID]));
|
|
87058
|
+
if (filtered.removed.length === 0) return super.receive(cmd, data, socket);
|
|
87059
|
+
for (const nodeId of filtered.removed) {
|
|
87060
|
+
if (this.ignoredPeers.has(nodeId)) continue;
|
|
87061
|
+
this.ignoredPeers.add(nodeId);
|
|
87062
|
+
this.logger.info(`Mesh: not adopting node '${nodeId}' from the hub's gossip — an enrolled agent's only peer is the hub (D848)`);
|
|
87063
|
+
}
|
|
87064
|
+
return super.receive(cmd, this.broker.serializer.serialize(filtered.payload, cmd), socket);
|
|
87065
|
+
}
|
|
87066
|
+
/** Remember the TLS port a peer's HELLO advertises (absent: an older node → the default). */
|
|
87067
|
+
noteHelloTlsPort(data) {
|
|
87068
|
+
let payload;
|
|
87069
|
+
try {
|
|
87070
|
+
payload = this.broker.serializer.deserialize(data, PACKET_GOSSIP_HELLO);
|
|
87071
|
+
} catch {
|
|
87072
|
+
return;
|
|
87073
|
+
}
|
|
87074
|
+
const sender = payload !== null && typeof payload === "object" ? Reflect.get(payload, "sender") : null;
|
|
87075
|
+
if (typeof sender !== "string") return;
|
|
87076
|
+
const port = helloMeshTlsPort(payload);
|
|
87077
|
+
if (port === null) this.helloTlsPorts.delete(sender);
|
|
87078
|
+
else this.helloTlsPorts.set(sender, port);
|
|
87079
|
+
}
|
|
87080
|
+
/** A TLS-listening node says where in every HELLO it sends. */
|
|
87081
|
+
publish(packet) {
|
|
87082
|
+
if (packet.type === PACKET_GOSSIP_HELLO && this.tlsServer !== null) packet.payload[MESH_TLS_PORT_HELLO_FIELD] = this.opts.tlsPort;
|
|
87083
|
+
return super.publish(packet);
|
|
87084
|
+
}
|
|
86975
87085
|
onIncomingMessage(type, message, socket) {
|
|
87086
|
+
if (this.hubOnlyPeers && (socket === void 0 || !isTlsSocket(socket))) {
|
|
87087
|
+
this.logger.warn("Dropping mesh packet — an enrolled agent accepts TLS from the hub only");
|
|
87088
|
+
return;
|
|
87089
|
+
}
|
|
86976
87090
|
if (socket !== void 0 && isTlsSocket(socket)) {
|
|
86977
87091
|
const tlsSock = socket;
|
|
86978
87092
|
const identity = tlsSock.meshPeerCn ? {
|
|
@@ -87043,6 +87157,11 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
|
|
|
87043
87157
|
socket.destroy();
|
|
87044
87158
|
return;
|
|
87045
87159
|
}
|
|
87160
|
+
if (this.hubOnlyPeers && identity.cn !== this.hubNodeId) {
|
|
87161
|
+
this.logger.warn(`Mesh TLS client rejected — an enrolled agent accepts only '${this.hubNodeId}', not CN=${identity.cn} (D848)`);
|
|
87162
|
+
socket.destroy();
|
|
87163
|
+
return;
|
|
87164
|
+
}
|
|
87046
87165
|
socket.meshPeerCn = identity.cn;
|
|
87047
87166
|
socket.meshPeerSerial = identity.serial;
|
|
87048
87167
|
this.tlsSockets.add(socket);
|
|
@@ -87745,6 +87864,7 @@ exports.AGENT_UI_UNLOCK_VERSION = AGENT_UI_UNLOCK_VERSION;
|
|
|
87745
87864
|
exports.Errors = Errors;
|
|
87746
87865
|
exports.JoinTokenJtiStore = JoinTokenJtiStore;
|
|
87747
87866
|
exports.MESH_AUTH_MODE_FIELD = MESH_AUTH_MODE_FIELD;
|
|
87867
|
+
exports.MESH_TLS_PORT_HELLO_FIELD = MESH_TLS_PORT_HELLO_FIELD;
|
|
87748
87868
|
exports.MeshCaKeeper = MeshCaKeeper;
|
|
87749
87869
|
exports.MeshCrl = MeshCrl;
|
|
87750
87870
|
exports.SYSTEM_CONFIG_CLUSTER_SECTION_ID = SYSTEM_CONFIG_CLUSTER_SECTION_ID;
|