@camstack/system 1.2.383 → 1.2.385
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/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/addon-utils.js +1 -1
- package/dist/addon-utils.mjs +1 -1
- package/dist/auth/auth-audit-log.d.ts +2 -2
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
- package/dist/builtins/calendars/calendars.addon.js +1 -1
- package/dist/builtins/calendars/calendars.addon.mjs +1 -1
- package/dist/builtins/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -1
- package/dist/builtins/composer/composer.addon.js +2 -2
- package/dist/builtins/composer/composer.addon.mjs +2 -2
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
- package/dist/builtins/device-manager/device-manager.addon.js +2 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/keychain/index.js +3 -3
- package/dist/builtins/keychain/index.mjs +3 -3
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +5 -4
- package/dist/builtins/local-auth/local-auth.addon.mjs +5 -4
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +0 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +0 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.d.ts +13 -1
- package/dist/builtins/system-config/system-config.addon.js +201 -8
- package/dist/builtins/system-config/system-config.addon.mjs +195 -9
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{child-cap-dispatch-C68N2a6Y.mjs → child-cap-dispatch-B1gyE35b.mjs} +1 -1
- package/dist/{child-cap-dispatch-BD8fhJ1b.js → child-cap-dispatch-nURMuVTX.js} +1 -1
- package/dist/{composition-sources-ChpT_Yw4.js → composition-sources-wdAxs-uY.js} +1 -1
- package/dist/{composition-sources-DM7Ft_tp.mjs → composition-sources-xqhm2Ygr.mjs} +1 -1
- package/dist/{constants-CDpry1_T.js → constants-Ca-Sgcl8.js} +1 -1
- package/dist/{constants-e3mCa1jG.mjs → constants-D5z4EwY8.mjs} +1 -1
- package/dist/{dist-CUNGH0fs.js → dist-8LQ4lDEB.js} +9 -1
- package/dist/{dist-osl3bkF0.mjs → dist-Biqn3TEn.mjs} +9 -1
- package/dist/{event-loop-stall-monitor-BeeWMnyT.mjs → event-loop-stall-monitor-BtK_0Pjc.mjs} +1 -1
- package/dist/{event-loop-stall-monitor-Dk28EF8B.js → event-loop-stall-monitor-CNj73d4D.js} +1 -1
- package/dist/index.js +13 -6
- package/dist/index.mjs +8 -8
- package/dist/{jwt-signing-key-WI6z7JBo.js → jwt-signing-key-DxV2ucvJ.js} +1 -1
- package/dist/{jwt-signing-key-MQLxLfgF.mjs → jwt-signing-key-SnDgvVUQ.mjs} +1 -1
- package/dist/kernel/index.d.ts +2 -2
- package/dist/kernel/moleculer/mesh-mtls/index.d.ts +1 -0
- package/dist/kernel/moleculer/mesh-mtls/mesh-clear-port.d.ts +56 -0
- package/dist/kernel/moleculer/mesh-mtls/tls-tcp-transporter.d.ts +33 -0
- package/dist/{restore-archive-CZRtI1k9.mjs → restore-archive-D4cmLoNe.mjs} +1 -1
- package/dist/{restore-archive-C5f9yX90.js → restore-archive-Do_Yu5rC.js} +1 -1
- package/dist/{retired-settings-keys-LuDd4y3M.mjs → retired-settings-keys-BsgVxh8-.mjs} +1 -1
- package/dist/{retired-settings-keys-WLrbzdPR.js → retired-settings-keys-Cyn3OD0K.js} +1 -1
- package/package.json +1 -1
|
@@ -3,9 +3,9 @@ Object.defineProperties(exports, {
|
|
|
3
3
|
[Symbol.toStringTag]: { value: "Module" }
|
|
4
4
|
});
|
|
5
5
|
const require_chunk = require("../../chunk-Cek0wNdY.js");
|
|
6
|
-
const require_dist = require("../../dist-
|
|
6
|
+
const require_dist = require("../../dist-8LQ4lDEB.js");
|
|
7
7
|
const require_ms$1 = require("../../ms-t68_FaqE.js");
|
|
8
|
-
const require_constants$8 = require("../../constants-
|
|
8
|
+
const require_constants$8 = require("../../constants-Ca-Sgcl8.js");
|
|
9
9
|
const require_x509_es = require("../../x509.es-BhTzcFhZ.js");
|
|
10
10
|
let zod = require("zod");
|
|
11
11
|
let node_crypto = require("node:crypto");
|
|
@@ -280,6 +280,9 @@ async function createMeshCsrPem(keys, commonName) {
|
|
|
280
280
|
async function generateMeshKeyPair() {
|
|
281
281
|
return node_crypto.webcrypto.subtle.generateKey(MESH_KEY_ALGORITHM, true, ["sign", "verify"]);
|
|
282
282
|
}
|
|
283
|
+
async function exportMeshPrivateKeyPem(key) {
|
|
284
|
+
return exportKeyPem(key);
|
|
285
|
+
}
|
|
283
286
|
//#endregion
|
|
284
287
|
//#region src/kernel/moleculer/mesh-mtls/mesh-ca-keeper.ts
|
|
285
288
|
var SLOT$2 = Symbol.for("camstack.mesh.ca");
|
|
@@ -465,6 +468,69 @@ function processMeshCrl() {
|
|
|
465
468
|
return crl;
|
|
466
469
|
}
|
|
467
470
|
//#endregion
|
|
471
|
+
//#region src/kernel/moleculer/mesh-mtls/mesh-clear-port.ts
|
|
472
|
+
/** The field key inside the `cluster` section. */
|
|
473
|
+
var MESH_CLEAR_PORT_FIELD = "meshClearPort";
|
|
474
|
+
function isMeshClearPortMode(value) {
|
|
475
|
+
return value === "on" || value === "off";
|
|
476
|
+
}
|
|
477
|
+
/** A stored value that is not a mode reads as `on`: never cut the mesh on a typo. */
|
|
478
|
+
function parseMeshClearPortMode(value) {
|
|
479
|
+
return isMeshClearPortMode(value) ? value : "on";
|
|
480
|
+
}
|
|
481
|
+
/** Pure: may the clear port be switched off right now? */
|
|
482
|
+
function decideMeshClearPortOff(control) {
|
|
483
|
+
if (control === null) return {
|
|
484
|
+
allowed: false,
|
|
485
|
+
blocking: [],
|
|
486
|
+
error: "The clear mesh port can only be switched off on the hub (no mesh transport here)."
|
|
487
|
+
};
|
|
488
|
+
if (!control.isTlsListening()) return {
|
|
489
|
+
allowed: false,
|
|
490
|
+
blocking: [],
|
|
491
|
+
error: "Refusing to close the clear mesh port: the TLS listener (:6443) is not up yet, so no node could reach the hub."
|
|
492
|
+
};
|
|
493
|
+
const blocking = control.listOnlineAgents().filter((agent) => agent.transport !== "tls").map((agent) => agent.nodeId).toSorted();
|
|
494
|
+
if (blocking.length > 0) return {
|
|
495
|
+
allowed: false,
|
|
496
|
+
blocking,
|
|
497
|
+
error: `Refusing to close the clear mesh port: ${blocking.join(", ")} still ${blocking.length === 1 ? "connects" : "connect"} in clear. Enroll ${blocking.length === 1 ? "it" : "each"} with a join token first (System → Cluster → Add agent).`
|
|
498
|
+
};
|
|
499
|
+
return { allowed: true };
|
|
500
|
+
}
|
|
501
|
+
var CONTROL_SLOT = Symbol.for("camstack.cluster.mesh-clear-port-control");
|
|
502
|
+
/** Published by the hub's broker owner; cleared on shutdown. */
|
|
503
|
+
function setProcessMeshClearPortControl(control) {
|
|
504
|
+
if (control === null) Reflect.deleteProperty(globalThis, CONTROL_SLOT);
|
|
505
|
+
else Reflect.set(globalThis, CONTROL_SLOT, control);
|
|
506
|
+
}
|
|
507
|
+
function isMeshClearPortControl(value) {
|
|
508
|
+
return value !== null && typeof value === "object" && typeof Reflect.get(value, "listOnlineAgents") === "function" && typeof Reflect.get(value, "isTlsListening") === "function" && typeof Reflect.get(value, "apply") === "function" && typeof Reflect.get(value, "transportOf") === "function";
|
|
509
|
+
}
|
|
510
|
+
function getProcessMeshClearPortControl() {
|
|
511
|
+
const value = Reflect.get(globalThis, CONTROL_SLOT);
|
|
512
|
+
return isMeshClearPortControl(value) ? value : null;
|
|
513
|
+
}
|
|
514
|
+
/** `tls` / `clear` for an agent as the hub sees it; `undefined` off the hub. */
|
|
515
|
+
function getProcessNodeMeshTransport(nodeId) {
|
|
516
|
+
return getProcessMeshClearPortControl()?.transportOf(nodeId);
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Validate a requested mode against the live mesh. `on` is always allowed; `off`
|
|
520
|
+
* only when {@link decideMeshClearPortOff} allows. Does NOT apply it.
|
|
521
|
+
*/
|
|
522
|
+
function checkProcessMeshClearPort(mode) {
|
|
523
|
+
if (mode === "on") return { allowed: true };
|
|
524
|
+
return decideMeshClearPortOff(getProcessMeshClearPortControl());
|
|
525
|
+
}
|
|
526
|
+
/** Apply a mode that {@link checkProcessMeshClearPort} allowed. No-op where nobody can. */
|
|
527
|
+
async function applyProcessMeshClearPort(mode) {
|
|
528
|
+
const control = getProcessMeshClearPortControl();
|
|
529
|
+
if (control === null) return false;
|
|
530
|
+
await control.apply(mode);
|
|
531
|
+
return true;
|
|
532
|
+
}
|
|
533
|
+
//#endregion
|
|
468
534
|
//#region ../../node_modules/jose/dist/webapi/lib/buffer_utils.js
|
|
469
535
|
var encoder = new TextEncoder(), decoder = new TextDecoder(), strictDecoder = new TextDecoder("utf-8", { fatal: !0 });
|
|
470
536
|
function concat(...buffers) {
|
|
@@ -87309,6 +87375,9 @@ var TlsTcpTransporter = class extends TcpTransporter {
|
|
|
87309
87375
|
meshTls;
|
|
87310
87376
|
crl;
|
|
87311
87377
|
tlsSockets = /* @__PURE__ */ new Set();
|
|
87378
|
+
/** Node id (certificate CN) → serial of every peer that completed a mutual-TLS handshake. */
|
|
87379
|
+
tlsPeers = /* @__PURE__ */ new Map();
|
|
87380
|
+
clearDisabled = false;
|
|
87312
87381
|
constructor(opts = {}) {
|
|
87313
87382
|
super({ ...opts });
|
|
87314
87383
|
this.meshTls = opts.meshTls ?? null;
|
|
@@ -87401,6 +87470,54 @@ var TlsTcpTransporter = class extends TcpTransporter {
|
|
|
87401
87470
|
this.meshTls = material;
|
|
87402
87471
|
await this.startTlsServerIfReady();
|
|
87403
87472
|
}
|
|
87473
|
+
/**
|
|
87474
|
+
* How `nodeId` reaches this transporter: `tls` once it completed a mutual-TLS
|
|
87475
|
+
* handshake (certificate CN = node id, issued by the mesh CA — so this cannot
|
|
87476
|
+
* be claimed by a clear-text peer), otherwise `clear`. Sticky until
|
|
87477
|
+
* {@link forgetPeer}: Moleculer closes idle sockets while the node is still
|
|
87478
|
+
* connected, and a closed socket is not a downgrade.
|
|
87479
|
+
*/
|
|
87480
|
+
peerTransport(nodeId) {
|
|
87481
|
+
return this.tlsPeers.has(nodeId) ? "tls" : "clear";
|
|
87482
|
+
}
|
|
87483
|
+
/**
|
|
87484
|
+
* The node disconnected: its next connection decides its transport again —
|
|
87485
|
+
* unless it already re-handshook (the disconnect can be noticed after the
|
|
87486
|
+
* restarted node is back), in which case its live TLS socket keeps it `tls`.
|
|
87487
|
+
*/
|
|
87488
|
+
forgetPeer(nodeId) {
|
|
87489
|
+
for (const socket of this.tlsSockets) if (socket.meshPeerCn === nodeId && !socket.destroyed) return;
|
|
87490
|
+
this.tlsPeers.delete(nodeId);
|
|
87491
|
+
}
|
|
87492
|
+
/** True once the mutual-TLS listener is bound. */
|
|
87493
|
+
isTlsListening() {
|
|
87494
|
+
return this.tlsServer !== null;
|
|
87495
|
+
}
|
|
87496
|
+
isClearTransportEnabled() {
|
|
87497
|
+
return !this.clearDisabled;
|
|
87498
|
+
}
|
|
87499
|
+
/**
|
|
87500
|
+
* Cutover: stop listening on the clear TCP port, drop the clear-text sockets
|
|
87501
|
+
* already open on it and stop UDP discovery. TLS sockets (own listener, own
|
|
87502
|
+
* port) are untouched. Idempotent.
|
|
87503
|
+
*/
|
|
87504
|
+
disableClearTransport() {
|
|
87505
|
+
this.clearDisabled = true;
|
|
87506
|
+
const reader = this.reader;
|
|
87507
|
+
if (reader?.server?.listening) reader.server.close();
|
|
87508
|
+
for (const socket of [...reader?.sockets ?? []]) if (!isTlsSocket(socket)) socket.destroy();
|
|
87509
|
+
this.udpServer?.close();
|
|
87510
|
+
this.udpServer = null;
|
|
87511
|
+
this.logger.info("Mesh clear transport disabled (clear TCP reader and UDP discovery stopped).");
|
|
87512
|
+
}
|
|
87513
|
+
/** The switch back: listen on the clear port again and restart UDP discovery. Idempotent. */
|
|
87514
|
+
async enableClearTransport() {
|
|
87515
|
+
if (!this.clearDisabled) return;
|
|
87516
|
+
this.clearDisabled = false;
|
|
87517
|
+
if (this.reader !== null && this.reader.server?.listening !== true) await this.reader.listen();
|
|
87518
|
+
if (this.udpServer === null) await this.startUdpServer();
|
|
87519
|
+
this.logger.info("Mesh clear transport re-enabled.");
|
|
87520
|
+
}
|
|
87404
87521
|
/** Close sockets whose peer serial was just revoked. */
|
|
87405
87522
|
dropRevokedSockets() {
|
|
87406
87523
|
for (const socket of [...this.tlsSockets]) {
|
|
@@ -87423,6 +87540,7 @@ var TlsTcpTransporter = class extends TcpTransporter {
|
|
|
87423
87540
|
s.destroy();
|
|
87424
87541
|
} catch {}
|
|
87425
87542
|
this.tlsSockets.clear();
|
|
87543
|
+
this.tlsPeers.clear();
|
|
87426
87544
|
return super.disconnect();
|
|
87427
87545
|
}
|
|
87428
87546
|
onIncomingMessage(type, message, socket) {
|
|
@@ -87499,6 +87617,7 @@ var TlsTcpTransporter = class extends TcpTransporter {
|
|
|
87499
87617
|
socket.meshPeerCn = identity.cn;
|
|
87500
87618
|
socket.meshPeerSerial = identity.serial;
|
|
87501
87619
|
this.tlsSockets.add(socket);
|
|
87620
|
+
this.tlsPeers.set(identity.cn, identity.serial);
|
|
87502
87621
|
const reader = this.reader;
|
|
87503
87622
|
socket.on("close", () => {
|
|
87504
87623
|
this.tlsSockets.delete(socket);
|
|
@@ -87757,7 +87876,8 @@ var KEY_TO_SECTION = {
|
|
|
87757
87876
|
trustedProxies: "auth",
|
|
87758
87877
|
trustedNetworks: "auth",
|
|
87759
87878
|
[CLUSTER_SECRET_FIELD]: "cluster",
|
|
87760
|
-
[MESH_AUTH_MODE_FIELD]: "cluster"
|
|
87879
|
+
[MESH_AUTH_MODE_FIELD]: "cluster",
|
|
87880
|
+
[MESH_CLEAR_PORT_FIELD]: "cluster"
|
|
87761
87881
|
};
|
|
87762
87882
|
/** Bytes of a generated cluster secret (D802) — 256 bits, base64url (43 chars). */
|
|
87763
87883
|
var GENERATED_SECRET_BYTES = 32;
|
|
@@ -87810,10 +87930,11 @@ var SystemConfigAddon = class extends require_dist.BaseAddon {
|
|
|
87810
87930
|
onReady: (ca) => this.onMeshCaReady(ca)
|
|
87811
87931
|
});
|
|
87812
87932
|
await this.meshCaKeeper.start();
|
|
87933
|
+
await this.applyStoredClearPort();
|
|
87813
87934
|
this.ctx.logger.info("Initialized — exposes yml-backed sections via getGlobalSettings");
|
|
87814
87935
|
return {
|
|
87815
87936
|
customActions: systemConfigMeshActions,
|
|
87816
|
-
actionHandlers: { mintJoinToken: async (input) => this.mintJoinToken(input
|
|
87937
|
+
actionHandlers: { mintJoinToken: async (input) => this.mintJoinToken(parseMintJoinTokenInput(input)) }
|
|
87817
87938
|
};
|
|
87818
87939
|
}
|
|
87819
87940
|
async onShutdown() {
|
|
@@ -87823,6 +87944,20 @@ var SystemConfigAddon = class extends require_dist.BaseAddon {
|
|
|
87823
87944
|
this.clusterSecretKeeper?.stop();
|
|
87824
87945
|
this.clusterSecretKeeper = null;
|
|
87825
87946
|
}
|
|
87947
|
+
/**
|
|
87948
|
+
* Boot: a stored `meshClearPort: off` is the operator's standing choice, so it
|
|
87949
|
+
* is applied WITHOUT the agents gate (nobody is connected yet to be checked).
|
|
87950
|
+
* Default `on` changes nothing.
|
|
87951
|
+
*/
|
|
87952
|
+
async applyStoredClearPort() {
|
|
87953
|
+
try {
|
|
87954
|
+
const stored = (await this.ctx.settings?.getSection("cluster"))?.[MESH_CLEAR_PORT_FIELD];
|
|
87955
|
+
if (parseMeshClearPortMode(stored) !== "off") return;
|
|
87956
|
+
if (await applyProcessMeshClearPort("off")) this.ctx.logger.info("Mesh clear port is OFF (cluster.meshClearPort)");
|
|
87957
|
+
} catch (err) {
|
|
87958
|
+
this.ctx.logger.warn("could not apply cluster.meshClearPort at boot", { meta: { error: require_dist.errMsg(err) } });
|
|
87959
|
+
}
|
|
87960
|
+
}
|
|
87826
87961
|
async onMeshCaReady(ca) {
|
|
87827
87962
|
const hubLeaf = await issueMeshLeafForNode(ca, "hub");
|
|
87828
87963
|
await requestProcessMeshTlsEnable({
|
|
@@ -87854,7 +87989,7 @@ var SystemConfigAddon = class extends require_dist.BaseAddon {
|
|
|
87854
87989
|
}
|
|
87855
87990
|
async resolveHubUrl() {
|
|
87856
87991
|
try {
|
|
87857
|
-
const book = await this.ctx.api?.localNetwork?.getAddresses?.({ scheme: "https" });
|
|
87992
|
+
const book = await this.ctx.api?.localNetwork?.getAddresses?.query({ scheme: "https" });
|
|
87858
87993
|
const url = book?.roles?.publicEndpoint?.resolved ?? book?.roles?.internal?.resolved ?? null;
|
|
87859
87994
|
if (typeof url === "string" && url.length > 0) return url;
|
|
87860
87995
|
} catch {}
|
|
@@ -87864,7 +87999,7 @@ var SystemConfigAddon = class extends require_dist.BaseAddon {
|
|
|
87864
87999
|
}
|
|
87865
88000
|
async resolveHubMeshAddress() {
|
|
87866
88001
|
try {
|
|
87867
|
-
const base = (await this.ctx.api?.localNetwork?.getAddresses?.({}))?.roles?.internal?.resolved;
|
|
88002
|
+
const base = (await this.ctx.api?.localNetwork?.getAddresses?.query({}))?.roles?.internal?.resolved;
|
|
87868
88003
|
if (typeof base === "string" && base.length > 0) return `${new URL(base).hostname}:${require_constants$8.DEFAULT_HUB_TCP_PORT}`;
|
|
87869
88004
|
} catch {}
|
|
87870
88005
|
const host = (await this.ctx.settings?.getSection("server"))?.["host"];
|
|
@@ -87985,6 +88120,20 @@ var SystemConfigAddon = class extends require_dist.BaseAddon {
|
|
|
87985
88120
|
label: "Enforced — refuse"
|
|
87986
88121
|
}]
|
|
87987
88122
|
},
|
|
88123
|
+
{
|
|
88124
|
+
type: "select",
|
|
88125
|
+
key: MESH_CLEAR_PORT_FIELD,
|
|
88126
|
+
label: "Clear mesh port (:6000)",
|
|
88127
|
+
description: "On keeps the clear TCP port and UDP discovery next to the TLS port (:6443). Off closes both — refused while any connected agent has not enrolled with a join token (the Cluster page shows TLS / clear per node).",
|
|
88128
|
+
default: "on",
|
|
88129
|
+
options: [{
|
|
88130
|
+
value: "on",
|
|
88131
|
+
label: "On — accept clear agents"
|
|
88132
|
+
}, {
|
|
88133
|
+
value: "off",
|
|
88134
|
+
label: "Off — mutual TLS only"
|
|
88135
|
+
}]
|
|
88136
|
+
},
|
|
87988
88137
|
{
|
|
87989
88138
|
type: "info",
|
|
87990
88139
|
key: "meshAuthStatus",
|
|
@@ -88067,7 +88216,8 @@ var SystemConfigAddon = class extends require_dist.BaseAddon {
|
|
|
88067
88216
|
* is REFUSED — never written inline (D787, P5). `meshAuth` is a plain value.
|
|
88068
88217
|
*/
|
|
88069
88218
|
async writeClusterSection(patch) {
|
|
88070
|
-
const { [MESH_AUTH_MODE_FIELD]: mode, ...secretPatch } = patch;
|
|
88219
|
+
const { [MESH_AUTH_MODE_FIELD]: mode, [MESH_CLEAR_PORT_FIELD]: clearPort, ...secretPatch } = patch;
|
|
88220
|
+
if (clearPort !== void 0) await this.writeMeshClearPort(clearPort);
|
|
88071
88221
|
if (mode !== void 0) {
|
|
88072
88222
|
if (!isMeshAuthMode(mode)) throw new Error(`system-config: meshAuth must be 'transition' or 'enforced'`);
|
|
88073
88223
|
await this.writeMeshAuthMode(mode);
|
|
@@ -88078,6 +88228,27 @@ var SystemConfigAddon = class extends require_dist.BaseAddon {
|
|
|
88078
88228
|
this.ctx.logger.info("Cluster secret updated");
|
|
88079
88229
|
await this.clusterSecretKeeper?.reload();
|
|
88080
88230
|
}
|
|
88231
|
+
/**
|
|
88232
|
+
* `cluster.meshClearPort`: `off` is REFUSED while any online agent still
|
|
88233
|
+
* connects in clear (or the TLS listener is not up); the refusal names them.
|
|
88234
|
+
* Persist, then apply — an `on` save always passes.
|
|
88235
|
+
*/
|
|
88236
|
+
async writeMeshClearPort(value) {
|
|
88237
|
+
if (!isMeshClearPortMode(value)) throw new Error(`system-config: meshClearPort must be 'on' or 'off'`);
|
|
88238
|
+
const stored = (await this.ctx.settings.getSection("cluster"))[MESH_CLEAR_PORT_FIELD];
|
|
88239
|
+
if (parseMeshClearPortMode(stored) === value) return;
|
|
88240
|
+
const decision = checkProcessMeshClearPort(value);
|
|
88241
|
+
if (!decision.allowed) {
|
|
88242
|
+
this.ctx.logger.warn("mesh clear port switch-off refused", { meta: {
|
|
88243
|
+
blocking: decision.blocking,
|
|
88244
|
+
error: decision.error
|
|
88245
|
+
} });
|
|
88246
|
+
throw new Error(decision.error);
|
|
88247
|
+
}
|
|
88248
|
+
await this.ctx.settings.setSection("cluster", { [MESH_CLEAR_PORT_FIELD]: value });
|
|
88249
|
+
await applyProcessMeshClearPort(value);
|
|
88250
|
+
this.ctx.logger.info("Mesh clear port switched", { meta: { mode: value } });
|
|
88251
|
+
}
|
|
88081
88252
|
/** `cluster.meshAuth` (D802), then republish it. */
|
|
88082
88253
|
async writeMeshAuthMode(mode) {
|
|
88083
88254
|
await this.ctx.settings.setSection("cluster", { [MESH_AUTH_MODE_FIELD]: mode });
|
|
@@ -88122,18 +88293,34 @@ var SystemConfigAddon = class extends require_dist.BaseAddon {
|
|
|
88122
88293
|
if (orphaned.length > 0) this.ctx.logger.warn("an old cluster secret value could not be deleted from the keychain", { meta: { refs: orphaned } });
|
|
88123
88294
|
}
|
|
88124
88295
|
};
|
|
88296
|
+
function parseMintJoinTokenInput(input) {
|
|
88297
|
+
if (input === null || input === void 0 || typeof input !== "object") return {};
|
|
88298
|
+
const out = {};
|
|
88299
|
+
const nodeId = Reflect.get(input, "nodeId");
|
|
88300
|
+
const hubUrl = Reflect.get(input, "hubUrl");
|
|
88301
|
+
const hubMeshAddress = Reflect.get(input, "hubMeshAddress");
|
|
88302
|
+
const ttlSec = Reflect.get(input, "ttlSec");
|
|
88303
|
+
if (typeof nodeId === "string") out.nodeId = nodeId;
|
|
88304
|
+
if (typeof hubUrl === "string") out.hubUrl = hubUrl;
|
|
88305
|
+
if (typeof hubMeshAddress === "string") out.hubMeshAddress = hubMeshAddress;
|
|
88306
|
+
if (typeof ttlSec === "number") out.ttlSec = ttlSec;
|
|
88307
|
+
return out;
|
|
88308
|
+
}
|
|
88125
88309
|
/** Read `exp` from an unencrypted JWT payload (no verify — already minted here). */
|
|
88126
88310
|
function readJwtExp(token) {
|
|
88127
88311
|
const parts = token.split(".");
|
|
88128
88312
|
if (parts.length < 2 || parts[1] === void 0) return 0;
|
|
88129
88313
|
const json = Buffer.from(parts[1], "base64url").toString("utf8");
|
|
88130
88314
|
const payload = JSON.parse(json);
|
|
88131
|
-
|
|
88315
|
+
if (payload === null || typeof payload !== "object") return 0;
|
|
88316
|
+
const exp = Reflect.get(payload, "exp");
|
|
88317
|
+
return typeof exp === "number" ? exp : 0;
|
|
88132
88318
|
}
|
|
88133
88319
|
//#endregion
|
|
88134
88320
|
exports.Errors = Errors;
|
|
88135
88321
|
exports.JoinTokenJtiStore = JoinTokenJtiStore;
|
|
88136
88322
|
exports.MESH_AUTH_MODE_FIELD = MESH_AUTH_MODE_FIELD;
|
|
88323
|
+
exports.MESH_CLEAR_PORT_FIELD = MESH_CLEAR_PORT_FIELD;
|
|
88137
88324
|
exports.MeshCaKeeper = MeshCaKeeper;
|
|
88138
88325
|
exports.MeshCrl = MeshCrl;
|
|
88139
88326
|
exports.SYSTEM_CONFIG_CLUSTER_SECTION_ID = SYSTEM_CONFIG_CLUSTER_SECTION_ID;
|
|
@@ -88141,16 +88328,21 @@ exports.ServiceBroker = ServiceBroker;
|
|
|
88141
88328
|
exports.SystemConfigAddon = SystemConfigAddon;
|
|
88142
88329
|
exports.default = SystemConfigAddon;
|
|
88143
88330
|
exports.TlsTcpTransporter = TlsTcpTransporter;
|
|
88331
|
+
exports.applyProcessMeshClearPort = applyProcessMeshClearPort;
|
|
88144
88332
|
exports.caFingerprint = caFingerprint;
|
|
88145
88333
|
exports.caFingerprintCompact = caFingerprintCompact;
|
|
88334
|
+
exports.checkProcessMeshClearPort = checkProcessMeshClearPort;
|
|
88146
88335
|
exports.createCidrMatcher = createCidrMatcher;
|
|
88147
88336
|
exports.createMeshCsrPem = createMeshCsrPem;
|
|
88337
|
+
exports.decideMeshClearPortOff = decideMeshClearPortOff;
|
|
88148
88338
|
exports.enrollMeshNode = enrollMeshNode;
|
|
88339
|
+
exports.exportMeshPrivateKeyPem = exportMeshPrivateKeyPem;
|
|
88149
88340
|
exports.generateMeshCa = generateMeshCa;
|
|
88150
88341
|
exports.generateMeshKeyPair = generateMeshKeyPair;
|
|
88151
88342
|
exports.getProcessMeshAuthMode = getProcessMeshAuthMode;
|
|
88152
88343
|
exports.getProcessMeshAuthStatus = getProcessMeshAuthStatus;
|
|
88153
88344
|
exports.getProcessMeshCaState = getProcessMeshCaState;
|
|
88345
|
+
exports.getProcessNodeMeshTransport = getProcessNodeMeshTransport;
|
|
88154
88346
|
exports.isMeshAuthMode = isMeshAuthMode;
|
|
88155
88347
|
exports.issueMeshLeafForNode = issueMeshLeafForNode;
|
|
88156
88348
|
exports.issueMeshLeafFromCsr = issueMeshLeafFromCsr;
|
|
@@ -88166,6 +88358,7 @@ exports.requestProcessMeshEnforcement = requestProcessMeshEnforcement;
|
|
|
88166
88358
|
exports.requireProcessMeshCa = requireProcessMeshCa;
|
|
88167
88359
|
exports.setProcessMeshAuthMode = setProcessMeshAuthMode;
|
|
88168
88360
|
exports.setProcessMeshAuthStatusSource = setProcessMeshAuthStatusSource;
|
|
88361
|
+
exports.setProcessMeshClearPortControl = setProcessMeshClearPortControl;
|
|
88169
88362
|
exports.setProcessMeshEnforcer = setProcessMeshEnforcer;
|
|
88170
88363
|
exports.setProcessMeshTlsEnabler = setProcessMeshTlsEnabler;
|
|
88171
88364
|
exports.systemConfigMeshActions = systemConfigMeshActions;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM, r as __exportAll, t as __commonJSMin } from "../../chunk-CNf5ZN-e.mjs";
|
|
2
|
-
import { Dr as defineCustomActions, Er as customAction, Ir as isSecretClear, Lr as isSecretMarker, Mr as hydrateSchema, Zr as secretTreeFromSchema, lr as errMsg, ti as transformSecretWrite, ur as BaseAddon, zr as makeSecretMarker } from "../../dist-
|
|
2
|
+
import { Dr as defineCustomActions, Er as customAction, Ir as isSecretClear, Lr as isSecretMarker, Mr as hydrateSchema, Zr as secretTreeFromSchema, lr as errMsg, ti as transformSecretWrite, ur as BaseAddon, zr as makeSecretMarker } from "../../dist-Biqn3TEn.mjs";
|
|
3
3
|
import { n as require_safe_buffer, t as require_ms } from "../../ms-DlIWNkkk.mjs";
|
|
4
|
-
import { a as MESH_CA_FIELD, c as MESH_CA_VALIDITY_DAYS, f as CLUSTER_SECRET_REF, i as MESH_CA_COMMON_NAME, l as MESH_JOIN_TOKEN_MAX_TTL_SEC, n as DEFAULT_HUB_TCP_PORT, o as MESH_CA_KEY_REF, r as DEFAULT_HUB_TLS_TCP_PORT, s as MESH_CA_SETTINGS_KEY, t as CLIENT_AUTH_OID, u as SERVER_AUTH_OID, x as setProcessClusterSecretGate, y as hashClusterSecret } from "../../constants-
|
|
4
|
+
import { a as MESH_CA_FIELD, c as MESH_CA_VALIDITY_DAYS, f as CLUSTER_SECRET_REF, i as MESH_CA_COMMON_NAME, l as MESH_JOIN_TOKEN_MAX_TTL_SEC, n as DEFAULT_HUB_TCP_PORT, o as MESH_CA_KEY_REF, r as DEFAULT_HUB_TLS_TCP_PORT, s as MESH_CA_SETTINGS_KEY, t as CLIENT_AUTH_OID, u as SERVER_AUTH_OID, x as setProcessClusterSecretGate, y as hashClusterSecret } from "../../constants-D5z4EwY8.mjs";
|
|
5
5
|
import { a as KeyUsagesExtension, c as Pkcs10CertificateRequestGenerator, d as X509Certificate$1, f as X509CertificateGenerator, i as KeyUsageFlags, n as BasicConstraintsExtension, o as PemConverter, p as cryptoProvider, r as ExtendedKeyUsageExtension, s as Pkcs10CertificateRequest, t as AuthorityKeyIdentifierExtension, u as SubjectKeyIdentifierExtension } from "../../x509.es-Xf3K_brW.mjs";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { X509Certificate, createHash, randomBytes, webcrypto } from "node:crypto";
|
|
@@ -271,6 +271,9 @@ async function createMeshCsrPem(keys, commonName) {
|
|
|
271
271
|
async function generateMeshKeyPair() {
|
|
272
272
|
return webcrypto.subtle.generateKey(MESH_KEY_ALGORITHM, true, ["sign", "verify"]);
|
|
273
273
|
}
|
|
274
|
+
async function exportMeshPrivateKeyPem(key) {
|
|
275
|
+
return exportKeyPem(key);
|
|
276
|
+
}
|
|
274
277
|
//#endregion
|
|
275
278
|
//#region src/kernel/moleculer/mesh-mtls/mesh-ca-keeper.ts
|
|
276
279
|
var SLOT$2 = Symbol.for("camstack.mesh.ca");
|
|
@@ -456,6 +459,69 @@ function processMeshCrl() {
|
|
|
456
459
|
return crl;
|
|
457
460
|
}
|
|
458
461
|
//#endregion
|
|
462
|
+
//#region src/kernel/moleculer/mesh-mtls/mesh-clear-port.ts
|
|
463
|
+
/** The field key inside the `cluster` section. */
|
|
464
|
+
var MESH_CLEAR_PORT_FIELD = "meshClearPort";
|
|
465
|
+
function isMeshClearPortMode(value) {
|
|
466
|
+
return value === "on" || value === "off";
|
|
467
|
+
}
|
|
468
|
+
/** A stored value that is not a mode reads as `on`: never cut the mesh on a typo. */
|
|
469
|
+
function parseMeshClearPortMode(value) {
|
|
470
|
+
return isMeshClearPortMode(value) ? value : "on";
|
|
471
|
+
}
|
|
472
|
+
/** Pure: may the clear port be switched off right now? */
|
|
473
|
+
function decideMeshClearPortOff(control) {
|
|
474
|
+
if (control === null) return {
|
|
475
|
+
allowed: false,
|
|
476
|
+
blocking: [],
|
|
477
|
+
error: "The clear mesh port can only be switched off on the hub (no mesh transport here)."
|
|
478
|
+
};
|
|
479
|
+
if (!control.isTlsListening()) return {
|
|
480
|
+
allowed: false,
|
|
481
|
+
blocking: [],
|
|
482
|
+
error: "Refusing to close the clear mesh port: the TLS listener (:6443) is not up yet, so no node could reach the hub."
|
|
483
|
+
};
|
|
484
|
+
const blocking = control.listOnlineAgents().filter((agent) => agent.transport !== "tls").map((agent) => agent.nodeId).toSorted();
|
|
485
|
+
if (blocking.length > 0) return {
|
|
486
|
+
allowed: false,
|
|
487
|
+
blocking,
|
|
488
|
+
error: `Refusing to close the clear mesh port: ${blocking.join(", ")} still ${blocking.length === 1 ? "connects" : "connect"} in clear. Enroll ${blocking.length === 1 ? "it" : "each"} with a join token first (System → Cluster → Add agent).`
|
|
489
|
+
};
|
|
490
|
+
return { allowed: true };
|
|
491
|
+
}
|
|
492
|
+
var CONTROL_SLOT = Symbol.for("camstack.cluster.mesh-clear-port-control");
|
|
493
|
+
/** Published by the hub's broker owner; cleared on shutdown. */
|
|
494
|
+
function setProcessMeshClearPortControl(control) {
|
|
495
|
+
if (control === null) Reflect.deleteProperty(globalThis, CONTROL_SLOT);
|
|
496
|
+
else Reflect.set(globalThis, CONTROL_SLOT, control);
|
|
497
|
+
}
|
|
498
|
+
function isMeshClearPortControl(value) {
|
|
499
|
+
return value !== null && typeof value === "object" && typeof Reflect.get(value, "listOnlineAgents") === "function" && typeof Reflect.get(value, "isTlsListening") === "function" && typeof Reflect.get(value, "apply") === "function" && typeof Reflect.get(value, "transportOf") === "function";
|
|
500
|
+
}
|
|
501
|
+
function getProcessMeshClearPortControl() {
|
|
502
|
+
const value = Reflect.get(globalThis, CONTROL_SLOT);
|
|
503
|
+
return isMeshClearPortControl(value) ? value : null;
|
|
504
|
+
}
|
|
505
|
+
/** `tls` / `clear` for an agent as the hub sees it; `undefined` off the hub. */
|
|
506
|
+
function getProcessNodeMeshTransport(nodeId) {
|
|
507
|
+
return getProcessMeshClearPortControl()?.transportOf(nodeId);
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Validate a requested mode against the live mesh. `on` is always allowed; `off`
|
|
511
|
+
* only when {@link decideMeshClearPortOff} allows. Does NOT apply it.
|
|
512
|
+
*/
|
|
513
|
+
function checkProcessMeshClearPort(mode) {
|
|
514
|
+
if (mode === "on") return { allowed: true };
|
|
515
|
+
return decideMeshClearPortOff(getProcessMeshClearPortControl());
|
|
516
|
+
}
|
|
517
|
+
/** Apply a mode that {@link checkProcessMeshClearPort} allowed. No-op where nobody can. */
|
|
518
|
+
async function applyProcessMeshClearPort(mode) {
|
|
519
|
+
const control = getProcessMeshClearPortControl();
|
|
520
|
+
if (control === null) return false;
|
|
521
|
+
await control.apply(mode);
|
|
522
|
+
return true;
|
|
523
|
+
}
|
|
524
|
+
//#endregion
|
|
459
525
|
//#region ../../node_modules/jose/dist/webapi/lib/buffer_utils.js
|
|
460
526
|
var encoder = new TextEncoder(), decoder = new TextDecoder(), strictDecoder = new TextDecoder("utf-8", { fatal: !0 });
|
|
461
527
|
function concat(...buffers) {
|
|
@@ -87300,6 +87366,9 @@ var TlsTcpTransporter = class extends TcpTransporter {
|
|
|
87300
87366
|
meshTls;
|
|
87301
87367
|
crl;
|
|
87302
87368
|
tlsSockets = /* @__PURE__ */ new Set();
|
|
87369
|
+
/** Node id (certificate CN) → serial of every peer that completed a mutual-TLS handshake. */
|
|
87370
|
+
tlsPeers = /* @__PURE__ */ new Map();
|
|
87371
|
+
clearDisabled = false;
|
|
87303
87372
|
constructor(opts = {}) {
|
|
87304
87373
|
super({ ...opts });
|
|
87305
87374
|
this.meshTls = opts.meshTls ?? null;
|
|
@@ -87392,6 +87461,54 @@ var TlsTcpTransporter = class extends TcpTransporter {
|
|
|
87392
87461
|
this.meshTls = material;
|
|
87393
87462
|
await this.startTlsServerIfReady();
|
|
87394
87463
|
}
|
|
87464
|
+
/**
|
|
87465
|
+
* How `nodeId` reaches this transporter: `tls` once it completed a mutual-TLS
|
|
87466
|
+
* handshake (certificate CN = node id, issued by the mesh CA — so this cannot
|
|
87467
|
+
* be claimed by a clear-text peer), otherwise `clear`. Sticky until
|
|
87468
|
+
* {@link forgetPeer}: Moleculer closes idle sockets while the node is still
|
|
87469
|
+
* connected, and a closed socket is not a downgrade.
|
|
87470
|
+
*/
|
|
87471
|
+
peerTransport(nodeId) {
|
|
87472
|
+
return this.tlsPeers.has(nodeId) ? "tls" : "clear";
|
|
87473
|
+
}
|
|
87474
|
+
/**
|
|
87475
|
+
* The node disconnected: its next connection decides its transport again —
|
|
87476
|
+
* unless it already re-handshook (the disconnect can be noticed after the
|
|
87477
|
+
* restarted node is back), in which case its live TLS socket keeps it `tls`.
|
|
87478
|
+
*/
|
|
87479
|
+
forgetPeer(nodeId) {
|
|
87480
|
+
for (const socket of this.tlsSockets) if (socket.meshPeerCn === nodeId && !socket.destroyed) return;
|
|
87481
|
+
this.tlsPeers.delete(nodeId);
|
|
87482
|
+
}
|
|
87483
|
+
/** True once the mutual-TLS listener is bound. */
|
|
87484
|
+
isTlsListening() {
|
|
87485
|
+
return this.tlsServer !== null;
|
|
87486
|
+
}
|
|
87487
|
+
isClearTransportEnabled() {
|
|
87488
|
+
return !this.clearDisabled;
|
|
87489
|
+
}
|
|
87490
|
+
/**
|
|
87491
|
+
* Cutover: stop listening on the clear TCP port, drop the clear-text sockets
|
|
87492
|
+
* already open on it and stop UDP discovery. TLS sockets (own listener, own
|
|
87493
|
+
* port) are untouched. Idempotent.
|
|
87494
|
+
*/
|
|
87495
|
+
disableClearTransport() {
|
|
87496
|
+
this.clearDisabled = true;
|
|
87497
|
+
const reader = this.reader;
|
|
87498
|
+
if (reader?.server?.listening) reader.server.close();
|
|
87499
|
+
for (const socket of [...reader?.sockets ?? []]) if (!isTlsSocket(socket)) socket.destroy();
|
|
87500
|
+
this.udpServer?.close();
|
|
87501
|
+
this.udpServer = null;
|
|
87502
|
+
this.logger.info("Mesh clear transport disabled (clear TCP reader and UDP discovery stopped).");
|
|
87503
|
+
}
|
|
87504
|
+
/** The switch back: listen on the clear port again and restart UDP discovery. Idempotent. */
|
|
87505
|
+
async enableClearTransport() {
|
|
87506
|
+
if (!this.clearDisabled) return;
|
|
87507
|
+
this.clearDisabled = false;
|
|
87508
|
+
if (this.reader !== null && this.reader.server?.listening !== true) await this.reader.listen();
|
|
87509
|
+
if (this.udpServer === null) await this.startUdpServer();
|
|
87510
|
+
this.logger.info("Mesh clear transport re-enabled.");
|
|
87511
|
+
}
|
|
87395
87512
|
/** Close sockets whose peer serial was just revoked. */
|
|
87396
87513
|
dropRevokedSockets() {
|
|
87397
87514
|
for (const socket of [...this.tlsSockets]) {
|
|
@@ -87414,6 +87531,7 @@ var TlsTcpTransporter = class extends TcpTransporter {
|
|
|
87414
87531
|
s.destroy();
|
|
87415
87532
|
} catch {}
|
|
87416
87533
|
this.tlsSockets.clear();
|
|
87534
|
+
this.tlsPeers.clear();
|
|
87417
87535
|
return super.disconnect();
|
|
87418
87536
|
}
|
|
87419
87537
|
onIncomingMessage(type, message, socket) {
|
|
@@ -87490,6 +87608,7 @@ var TlsTcpTransporter = class extends TcpTransporter {
|
|
|
87490
87608
|
socket.meshPeerCn = identity.cn;
|
|
87491
87609
|
socket.meshPeerSerial = identity.serial;
|
|
87492
87610
|
this.tlsSockets.add(socket);
|
|
87611
|
+
this.tlsPeers.set(identity.cn, identity.serial);
|
|
87493
87612
|
const reader = this.reader;
|
|
87494
87613
|
socket.on("close", () => {
|
|
87495
87614
|
this.tlsSockets.delete(socket);
|
|
@@ -87747,7 +87866,8 @@ var KEY_TO_SECTION = {
|
|
|
87747
87866
|
trustedProxies: "auth",
|
|
87748
87867
|
trustedNetworks: "auth",
|
|
87749
87868
|
[CLUSTER_SECRET_FIELD]: "cluster",
|
|
87750
|
-
[MESH_AUTH_MODE_FIELD]: "cluster"
|
|
87869
|
+
[MESH_AUTH_MODE_FIELD]: "cluster",
|
|
87870
|
+
[MESH_CLEAR_PORT_FIELD]: "cluster"
|
|
87751
87871
|
};
|
|
87752
87872
|
/** Bytes of a generated cluster secret (D802) — 256 bits, base64url (43 chars). */
|
|
87753
87873
|
var GENERATED_SECRET_BYTES = 32;
|
|
@@ -87800,10 +87920,11 @@ var SystemConfigAddon = class extends BaseAddon {
|
|
|
87800
87920
|
onReady: (ca) => this.onMeshCaReady(ca)
|
|
87801
87921
|
});
|
|
87802
87922
|
await this.meshCaKeeper.start();
|
|
87923
|
+
await this.applyStoredClearPort();
|
|
87803
87924
|
this.ctx.logger.info("Initialized — exposes yml-backed sections via getGlobalSettings");
|
|
87804
87925
|
return {
|
|
87805
87926
|
customActions: systemConfigMeshActions,
|
|
87806
|
-
actionHandlers: { mintJoinToken: async (input) => this.mintJoinToken(input
|
|
87927
|
+
actionHandlers: { mintJoinToken: async (input) => this.mintJoinToken(parseMintJoinTokenInput(input)) }
|
|
87807
87928
|
};
|
|
87808
87929
|
}
|
|
87809
87930
|
async onShutdown() {
|
|
@@ -87813,6 +87934,20 @@ var SystemConfigAddon = class extends BaseAddon {
|
|
|
87813
87934
|
this.clusterSecretKeeper?.stop();
|
|
87814
87935
|
this.clusterSecretKeeper = null;
|
|
87815
87936
|
}
|
|
87937
|
+
/**
|
|
87938
|
+
* Boot: a stored `meshClearPort: off` is the operator's standing choice, so it
|
|
87939
|
+
* is applied WITHOUT the agents gate (nobody is connected yet to be checked).
|
|
87940
|
+
* Default `on` changes nothing.
|
|
87941
|
+
*/
|
|
87942
|
+
async applyStoredClearPort() {
|
|
87943
|
+
try {
|
|
87944
|
+
const stored = (await this.ctx.settings?.getSection("cluster"))?.[MESH_CLEAR_PORT_FIELD];
|
|
87945
|
+
if (parseMeshClearPortMode(stored) !== "off") return;
|
|
87946
|
+
if (await applyProcessMeshClearPort("off")) this.ctx.logger.info("Mesh clear port is OFF (cluster.meshClearPort)");
|
|
87947
|
+
} catch (err) {
|
|
87948
|
+
this.ctx.logger.warn("could not apply cluster.meshClearPort at boot", { meta: { error: errMsg(err) } });
|
|
87949
|
+
}
|
|
87950
|
+
}
|
|
87816
87951
|
async onMeshCaReady(ca) {
|
|
87817
87952
|
const hubLeaf = await issueMeshLeafForNode(ca, "hub");
|
|
87818
87953
|
await requestProcessMeshTlsEnable({
|
|
@@ -87844,7 +87979,7 @@ var SystemConfigAddon = class extends BaseAddon {
|
|
|
87844
87979
|
}
|
|
87845
87980
|
async resolveHubUrl() {
|
|
87846
87981
|
try {
|
|
87847
|
-
const book = await this.ctx.api?.localNetwork?.getAddresses?.({ scheme: "https" });
|
|
87982
|
+
const book = await this.ctx.api?.localNetwork?.getAddresses?.query({ scheme: "https" });
|
|
87848
87983
|
const url = book?.roles?.publicEndpoint?.resolved ?? book?.roles?.internal?.resolved ?? null;
|
|
87849
87984
|
if (typeof url === "string" && url.length > 0) return url;
|
|
87850
87985
|
} catch {}
|
|
@@ -87854,7 +87989,7 @@ var SystemConfigAddon = class extends BaseAddon {
|
|
|
87854
87989
|
}
|
|
87855
87990
|
async resolveHubMeshAddress() {
|
|
87856
87991
|
try {
|
|
87857
|
-
const base = (await this.ctx.api?.localNetwork?.getAddresses?.({}))?.roles?.internal?.resolved;
|
|
87992
|
+
const base = (await this.ctx.api?.localNetwork?.getAddresses?.query({}))?.roles?.internal?.resolved;
|
|
87858
87993
|
if (typeof base === "string" && base.length > 0) return `${new URL(base).hostname}:${DEFAULT_HUB_TCP_PORT}`;
|
|
87859
87994
|
} catch {}
|
|
87860
87995
|
const host = (await this.ctx.settings?.getSection("server"))?.["host"];
|
|
@@ -87975,6 +88110,20 @@ var SystemConfigAddon = class extends BaseAddon {
|
|
|
87975
88110
|
label: "Enforced — refuse"
|
|
87976
88111
|
}]
|
|
87977
88112
|
},
|
|
88113
|
+
{
|
|
88114
|
+
type: "select",
|
|
88115
|
+
key: MESH_CLEAR_PORT_FIELD,
|
|
88116
|
+
label: "Clear mesh port (:6000)",
|
|
88117
|
+
description: "On keeps the clear TCP port and UDP discovery next to the TLS port (:6443). Off closes both — refused while any connected agent has not enrolled with a join token (the Cluster page shows TLS / clear per node).",
|
|
88118
|
+
default: "on",
|
|
88119
|
+
options: [{
|
|
88120
|
+
value: "on",
|
|
88121
|
+
label: "On — accept clear agents"
|
|
88122
|
+
}, {
|
|
88123
|
+
value: "off",
|
|
88124
|
+
label: "Off — mutual TLS only"
|
|
88125
|
+
}]
|
|
88126
|
+
},
|
|
87978
88127
|
{
|
|
87979
88128
|
type: "info",
|
|
87980
88129
|
key: "meshAuthStatus",
|
|
@@ -88057,7 +88206,8 @@ var SystemConfigAddon = class extends BaseAddon {
|
|
|
88057
88206
|
* is REFUSED — never written inline (D787, P5). `meshAuth` is a plain value.
|
|
88058
88207
|
*/
|
|
88059
88208
|
async writeClusterSection(patch) {
|
|
88060
|
-
const { [MESH_AUTH_MODE_FIELD]: mode, ...secretPatch } = patch;
|
|
88209
|
+
const { [MESH_AUTH_MODE_FIELD]: mode, [MESH_CLEAR_PORT_FIELD]: clearPort, ...secretPatch } = patch;
|
|
88210
|
+
if (clearPort !== void 0) await this.writeMeshClearPort(clearPort);
|
|
88061
88211
|
if (mode !== void 0) {
|
|
88062
88212
|
if (!isMeshAuthMode(mode)) throw new Error(`system-config: meshAuth must be 'transition' or 'enforced'`);
|
|
88063
88213
|
await this.writeMeshAuthMode(mode);
|
|
@@ -88068,6 +88218,27 @@ var SystemConfigAddon = class extends BaseAddon {
|
|
|
88068
88218
|
this.ctx.logger.info("Cluster secret updated");
|
|
88069
88219
|
await this.clusterSecretKeeper?.reload();
|
|
88070
88220
|
}
|
|
88221
|
+
/**
|
|
88222
|
+
* `cluster.meshClearPort`: `off` is REFUSED while any online agent still
|
|
88223
|
+
* connects in clear (or the TLS listener is not up); the refusal names them.
|
|
88224
|
+
* Persist, then apply — an `on` save always passes.
|
|
88225
|
+
*/
|
|
88226
|
+
async writeMeshClearPort(value) {
|
|
88227
|
+
if (!isMeshClearPortMode(value)) throw new Error(`system-config: meshClearPort must be 'on' or 'off'`);
|
|
88228
|
+
const stored = (await this.ctx.settings.getSection("cluster"))[MESH_CLEAR_PORT_FIELD];
|
|
88229
|
+
if (parseMeshClearPortMode(stored) === value) return;
|
|
88230
|
+
const decision = checkProcessMeshClearPort(value);
|
|
88231
|
+
if (!decision.allowed) {
|
|
88232
|
+
this.ctx.logger.warn("mesh clear port switch-off refused", { meta: {
|
|
88233
|
+
blocking: decision.blocking,
|
|
88234
|
+
error: decision.error
|
|
88235
|
+
} });
|
|
88236
|
+
throw new Error(decision.error);
|
|
88237
|
+
}
|
|
88238
|
+
await this.ctx.settings.setSection("cluster", { [MESH_CLEAR_PORT_FIELD]: value });
|
|
88239
|
+
await applyProcessMeshClearPort(value);
|
|
88240
|
+
this.ctx.logger.info("Mesh clear port switched", { meta: { mode: value } });
|
|
88241
|
+
}
|
|
88071
88242
|
/** `cluster.meshAuth` (D802), then republish it. */
|
|
88072
88243
|
async writeMeshAuthMode(mode) {
|
|
88073
88244
|
await this.ctx.settings.setSection("cluster", { [MESH_AUTH_MODE_FIELD]: mode });
|
|
@@ -88112,13 +88283,28 @@ var SystemConfigAddon = class extends BaseAddon {
|
|
|
88112
88283
|
if (orphaned.length > 0) this.ctx.logger.warn("an old cluster secret value could not be deleted from the keychain", { meta: { refs: orphaned } });
|
|
88113
88284
|
}
|
|
88114
88285
|
};
|
|
88286
|
+
function parseMintJoinTokenInput(input) {
|
|
88287
|
+
if (input === null || input === void 0 || typeof input !== "object") return {};
|
|
88288
|
+
const out = {};
|
|
88289
|
+
const nodeId = Reflect.get(input, "nodeId");
|
|
88290
|
+
const hubUrl = Reflect.get(input, "hubUrl");
|
|
88291
|
+
const hubMeshAddress = Reflect.get(input, "hubMeshAddress");
|
|
88292
|
+
const ttlSec = Reflect.get(input, "ttlSec");
|
|
88293
|
+
if (typeof nodeId === "string") out.nodeId = nodeId;
|
|
88294
|
+
if (typeof hubUrl === "string") out.hubUrl = hubUrl;
|
|
88295
|
+
if (typeof hubMeshAddress === "string") out.hubMeshAddress = hubMeshAddress;
|
|
88296
|
+
if (typeof ttlSec === "number") out.ttlSec = ttlSec;
|
|
88297
|
+
return out;
|
|
88298
|
+
}
|
|
88115
88299
|
/** Read `exp` from an unencrypted JWT payload (no verify — already minted here). */
|
|
88116
88300
|
function readJwtExp(token) {
|
|
88117
88301
|
const parts = token.split(".");
|
|
88118
88302
|
if (parts.length < 2 || parts[1] === void 0) return 0;
|
|
88119
88303
|
const json = Buffer.from(parts[1], "base64url").toString("utf8");
|
|
88120
88304
|
const payload = JSON.parse(json);
|
|
88121
|
-
|
|
88305
|
+
if (payload === null || typeof payload !== "object") return 0;
|
|
88306
|
+
const exp = Reflect.get(payload, "exp");
|
|
88307
|
+
return typeof exp === "number" ? exp : 0;
|
|
88122
88308
|
}
|
|
88123
88309
|
//#endregion
|
|
88124
|
-
export {
|
|
88310
|
+
export { loadMeshCa as A, createCidrMatcher as B, caFingerprintCompact as C, generateMeshKeyPair as D, generateMeshCa as E, parseMeshAuthMode as F, parseCidrList as H, requestProcessMeshEnforcement as I, setProcessMeshAuthMode as L, getProcessMeshAuthMode as M, getProcessMeshAuthStatus as N, issueMeshLeafForNode as O, isMeshAuthMode as P, setProcessMeshAuthStatusSource as R, caFingerprint as S, SYSTEM_CONFIG_CLUSTER_SECTION_ID, SystemConfigAddon, SystemConfigAddon as default, exportMeshPrivateKeyPem as T, readCidrSetting as U, normalizeIp as V, MeshCrl as _, setProcessMeshTlsEnabler as a, getProcessMeshCaState as b, mintMeshJoinToken as c, MESH_CLEAR_PORT_FIELD as d, applyProcessMeshClearPort as f, setProcessMeshClearPortControl as g, getProcessNodeMeshTransport as h, ServiceBroker as i, MESH_AUTH_MODE_FIELD as j, issueMeshLeafFromCsr as k, processJoinTokenJtiStore as l, decideMeshClearPortOff as m, TlsTcpTransporter as n, enrollMeshNode as o, checkProcessMeshClearPort as p, Errors as r, JoinTokenJtiStore as s, systemConfigMeshActions as t, verifyMeshJoinToken as u, processMeshCrl as v, createMeshCsrPem as w, requireProcessMeshCa as x, MeshCaKeeper as y, setProcessMeshEnforcer as z };
|