@camstack/system 1.2.458 → 1.2.459
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-https-DOELEug8.js → agent-https-Dnr_nIVx.js} +3 -723
- package/dist/{agent-https-CQA3W-3e.mjs → agent-https-Iw3RSQup.mjs} +4 -616
- 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 +5 -7
- package/dist/builtins/alerts/alerts.addon.mjs +5 -7
- package/dist/builtins/alerts/record-kinds.d.ts +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 +242 -4
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +240 -2
- package/dist/builtins/calendars/calendars.addon.js +2 -10
- package/dist/builtins/calendars/calendars.addon.mjs +2 -3
- package/dist/builtins/calendars/record-kinds.d.ts +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 +4 -6
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +4 -6
- package/dist/builtins/device-history/device-history.addon.js +1 -1
- package/dist/builtins/device-history/device-history.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +2 -4
- package/dist/builtins/device-manager/device-manager.addon.mjs +2 -4
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/keychain/index.js +41 -43
- package/dist/builtins/keychain/index.mjs +41 -43
- 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 +24 -40
- package/dist/builtins/local-auth/local-auth.addon.mjs +24 -40
- package/dist/builtins/local-network/local-network.addon.js +2 -2
- package/dist/builtins/local-network/local-network.addon.mjs +2 -2
- 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/native-metrics/node-metrics-history-recorder.d.ts +1 -2
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/record-entries/index.js +1 -1
- package/dist/builtins/record-entries/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/file-host-protocol.d.ts +0 -15
- 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/index.js +1 -1
- package/dist/builtins/sqlite-storage/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/owned-sqlite-file.d.ts +0 -44
- package/dist/builtins/sqlite-storage/records/records-catalog.d.ts +0 -2
- package/dist/builtins/sqlite-storage/records/records-engine-types.d.ts +0 -21
- package/dist/builtins/sqlite-storage/records/records-engine.d.ts +3 -20
- package/dist/builtins/sqlite-storage/records/records-provider.d.ts +4 -18
- package/dist/builtins/sqlite-storage/records/records-sql.d.ts +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +0 -17
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +21 -196
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +21 -196
- package/dist/builtins/sqlite-storage/storage-admin-provider.d.ts +6 -14
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +2 -2
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +7 -9
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +7 -9
- package/dist/builtins/system-config/system-config.addon.js +667 -40
- package/dist/builtins/system-config/system-config.addon.mjs +625 -15
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{composition-sources-BVguN7VV.mjs → composition-sources-DNRZWR1j.mjs} +1 -1
- package/dist/{composition-sources-DURQNDJ4.js → composition-sources-b0e_2I-D.js} +1 -1
- package/dist/{constants-Njka8Cv6.js → constants-6OeJiZgi.js} +1 -1
- package/dist/{constants-FGjnsE2x.mjs → constants-BnkOdj9Q.mjs} +1 -1
- package/dist/{device-ca-lEaAQ9Xi.mjs → device-ca-Bnkw8GyU.mjs} +1 -1
- package/dist/{device-ca-BrVK_5s3.js → device-ca-CPTDs--d.js} +1 -1
- package/dist/{dist-BZQjLPiz.js → dist-Bn-RNanh.js} +29 -130
- package/dist/{dist-B_GRPhnY.mjs → dist-Wy8cTxg6.mjs} +30 -113
- package/dist/file-host-worker.js +1 -1
- package/dist/file-host-worker.mjs +1 -1
- package/dist/{hub-mdns-ciao-Bg2HfTwP.mjs → hub-mdns-ciao-CJu0nhla.mjs} +2 -2
- package/dist/{hub-mdns-ciao-nce3TXs_.js → hub-mdns-ciao-CTBTlia4.js} +2 -2
- package/dist/index.js +27 -30
- package/dist/index.mjs +11 -14
- package/dist/{restore-archive-C7kLDI_0.mjs → restore-archive-CmP-iiG9.mjs} +84 -331
- package/dist/{restore-archive-FyfZ8YMC.js → restore-archive-DM1OBYq4.js} +89 -342
- package/dist/{wal-maintenance-DfSIOj24.js → wal-maintenance-DUHEp_09.js} +0 -0
- package/dist/{wal-maintenance-Duejh7Lx.mjs → wal-maintenance-NKbwWovZ.mjs} +0 -0
- package/package.json +1 -1
- package/dist/builtins/records/legacy-table-row-count.d.ts +0 -5
- package/dist/builtins/records/phase6-declare-gate.d.ts +0 -28
- package/dist/builtins/sqlite-storage/records/count-settings-array-legacy.d.ts +0 -5
- package/dist/builtins/sqlite-storage/records/count-settings-brokers-legacy.d.ts +0 -6
- package/dist/phase6-declare-gate-Dag1_WR2.js +0 -98
- package/dist/phase6-declare-gate-Lb-mrVvz.mjs +0 -93
|
@@ -3,11 +3,11 @@ 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-Bn-RNanh.js");
|
|
7
7
|
const require_safe_buffer$1 = require("../../safe-buffer-D3aoa-7X.js");
|
|
8
8
|
const require_x509_es = require("../../x509.es-C4cOo48M.js");
|
|
9
|
-
const
|
|
10
|
-
const
|
|
9
|
+
const require_agent_https = require("../../agent-https-Dnr_nIVx.js");
|
|
10
|
+
const require_constants$8 = require("../../constants-6OeJiZgi.js");
|
|
11
11
|
const require_src$1 = require("../../src-CnAqG6EF.js");
|
|
12
12
|
let zod = require("zod");
|
|
13
13
|
let node_crypto = require("node:crypto");
|
|
@@ -298,8 +298,161 @@ function parsePayload(value) {
|
|
|
298
298
|
};
|
|
299
299
|
}
|
|
300
300
|
//#endregion
|
|
301
|
+
//#region src/kernel/moleculer/mesh-mtls/mesh-ca.ts
|
|
302
|
+
/**
|
|
303
|
+
* Hub mesh CA — EC P-256, key in the keychain, cert in `cluster.meshCa`.
|
|
304
|
+
*
|
|
305
|
+
* Distinct from the HTTPS local CA (`tls/cert-issue.ts`, RSA): the mesh CA
|
|
306
|
+
* signs node leaves (CN = nodeID) and unlock-ticket JWS (ES256). Issuance uses
|
|
307
|
+
* `@peculiar/x509` over WebCrypto — same library the HTTPS path already uses
|
|
308
|
+
* (installed `1.14.3`; registry latest at adoption time was `2.1.0` — stay on
|
|
309
|
+
* the pinned range already shipping).
|
|
310
|
+
*/
|
|
311
|
+
require_x509_es.cryptoProvider.set(node_crypto.webcrypto);
|
|
312
|
+
var MS_PER_DAY = 864e5;
|
|
313
|
+
/** Key generation — EC P-256 (plan). WebCrypto DOM names are not in @types/node. */
|
|
314
|
+
var MESH_KEY_ALGORITHM = {
|
|
315
|
+
name: "ECDSA",
|
|
316
|
+
namedCurve: "P-256"
|
|
317
|
+
};
|
|
318
|
+
/** Signing — ECDSA with SHA-256. */
|
|
319
|
+
var MESH_SIGNING_ALGORITHM = {
|
|
320
|
+
name: "ECDSA",
|
|
321
|
+
hash: "SHA-256"
|
|
322
|
+
};
|
|
323
|
+
/** A node id: letters, digits, `.`, `_`, `-`; at most 63 characters; starts with a letter or digit. */
|
|
324
|
+
var NODE_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,62}$/i;
|
|
325
|
+
/** Ids no agent may take: the hub's own, whatever the case (D857). */
|
|
326
|
+
var RESERVED_NODE_IDS = new Set(["hub"]);
|
|
327
|
+
/** True for an id an agent may carry: the pattern, and not a reserved name. */
|
|
328
|
+
function isValidAgentNodeId(nodeId) {
|
|
329
|
+
return NODE_ID_PATTERN.test(nodeId) && !RESERVED_NODE_IDS.has(nodeId.toLowerCase());
|
|
330
|
+
}
|
|
331
|
+
function freshSerial() {
|
|
332
|
+
return `00${(0, node_crypto.randomBytes)(16).toString("hex")}`;
|
|
333
|
+
}
|
|
334
|
+
function toPkcs8Pem(pkcs8) {
|
|
335
|
+
return `${require_x509_es.PemConverter.encode(pkcs8, "PRIVATE KEY")}\n`;
|
|
336
|
+
}
|
|
337
|
+
function toCertPem(cert) {
|
|
338
|
+
return `${cert.toString("pem")}\n`;
|
|
339
|
+
}
|
|
340
|
+
async function exportKeyPem(key) {
|
|
341
|
+
return toPkcs8Pem(await node_crypto.webcrypto.subtle.exportKey("pkcs8", key));
|
|
342
|
+
}
|
|
343
|
+
/** SHA-256 fingerprint of the DER cert, colon-hex uppercase (Node style). */
|
|
344
|
+
function caFingerprint(certPem) {
|
|
345
|
+
return new node_crypto.X509Certificate(certPem).fingerprint256;
|
|
346
|
+
}
|
|
347
|
+
/** Compact fingerprint (lowercase hex, no colons). */
|
|
348
|
+
function caFingerprintCompact(certPem) {
|
|
349
|
+
return (0, node_crypto.createHash)("sha256").update(new node_crypto.X509Certificate(certPem).raw).digest("hex");
|
|
350
|
+
}
|
|
351
|
+
async function generateMeshCa(now = /* @__PURE__ */ new Date()) {
|
|
352
|
+
const keys = await node_crypto.webcrypto.subtle.generateKey(MESH_KEY_ALGORITHM, true, ["sign", "verify"]);
|
|
353
|
+
const extensions = [
|
|
354
|
+
new require_x509_es.BasicConstraintsExtension(true, 0, true),
|
|
355
|
+
new require_x509_es.KeyUsagesExtension(require_x509_es.KeyUsageFlags.keyCertSign | require_x509_es.KeyUsageFlags.cRLSign, true),
|
|
356
|
+
await require_x509_es.SubjectKeyIdentifierExtension.create(keys.publicKey)
|
|
357
|
+
];
|
|
358
|
+
const cert = await require_x509_es.X509CertificateGenerator.createSelfSigned({
|
|
359
|
+
serialNumber: freshSerial(),
|
|
360
|
+
name: `CN=${require_constants$8.MESH_CA_COMMON_NAME}`,
|
|
361
|
+
notBefore: require_x509_es.backdatedNotBefore(now),
|
|
362
|
+
notAfter: new Date(now.getTime() + require_constants$8.MESH_CA_VALIDITY_DAYS * MS_PER_DAY),
|
|
363
|
+
keys,
|
|
364
|
+
signingAlgorithm: MESH_SIGNING_ALGORITHM,
|
|
365
|
+
extensions
|
|
366
|
+
});
|
|
367
|
+
return {
|
|
368
|
+
certPem: toCertPem(cert),
|
|
369
|
+
keyPem: await exportKeyPem(keys.privateKey),
|
|
370
|
+
cert,
|
|
371
|
+
privateKey: keys.privateKey,
|
|
372
|
+
publicKey: keys.publicKey
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
async function loadMeshCa(certPem, keyPem) {
|
|
376
|
+
const cert = new require_x509_es.X509Certificate(certPem);
|
|
377
|
+
return {
|
|
378
|
+
certPem,
|
|
379
|
+
keyPem,
|
|
380
|
+
cert,
|
|
381
|
+
privateKey: await node_crypto.webcrypto.subtle.importKey("pkcs8", require_x509_es.PemConverter.decodeFirst(keyPem), MESH_KEY_ALGORITHM, true, ["sign"]),
|
|
382
|
+
publicKey: await cert.publicKey.export(node_crypto.webcrypto)
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Issue a leaf from a CSR. CN must be present; when `expectedNodeId` is set
|
|
387
|
+
* it must match. Validity: 30 days.
|
|
388
|
+
*/
|
|
389
|
+
async function issueMeshLeafFromCsr(ca, csrPem, opts = {}) {
|
|
390
|
+
const csr = new require_x509_es.Pkcs10CertificateRequest(csrPem);
|
|
391
|
+
if (!await csr.verify(node_crypto.webcrypto)) throw new Error("CSR signature is invalid");
|
|
392
|
+
const cn = subjectCn(csr.subject);
|
|
393
|
+
if (cn === null || cn.length === 0) throw new Error("CSR subject has no CN");
|
|
394
|
+
if (opts.allowReserved !== true && !isValidAgentNodeId(cn)) throw new Error(`CSR CN '${cn}' is not an id an agent may carry`);
|
|
395
|
+
if (opts.expectedNodeId !== void 0 && opts.expectedNodeId !== cn) throw new Error(`CSR CN '${cn}' does not match the token nodeId '${opts.expectedNodeId}'`);
|
|
396
|
+
const now = opts.now ?? /* @__PURE__ */ new Date();
|
|
397
|
+
const validDays = opts.validDays ?? 30;
|
|
398
|
+
const serialNumber = freshSerial();
|
|
399
|
+
const notAfter = new Date(now.getTime() + validDays * MS_PER_DAY);
|
|
400
|
+
const extensions = [
|
|
401
|
+
new require_x509_es.BasicConstraintsExtension(false, void 0, true),
|
|
402
|
+
new require_x509_es.KeyUsagesExtension(require_x509_es.KeyUsageFlags.digitalSignature, true),
|
|
403
|
+
new require_x509_es.ExtendedKeyUsageExtension([require_constants$8.SERVER_AUTH_OID, require_constants$8.CLIENT_AUTH_OID]),
|
|
404
|
+
await require_x509_es.SubjectKeyIdentifierExtension.create(csr.publicKey),
|
|
405
|
+
await require_x509_es.AuthorityKeyIdentifierExtension.create(ca.cert)
|
|
406
|
+
];
|
|
407
|
+
return {
|
|
408
|
+
certPem: toCertPem(await require_x509_es.X509CertificateGenerator.create({
|
|
409
|
+
serialNumber,
|
|
410
|
+
subject: `CN=${cn}`,
|
|
411
|
+
issuer: ca.cert.subject,
|
|
412
|
+
notBefore: require_x509_es.backdatedNotBefore(now),
|
|
413
|
+
notAfter,
|
|
414
|
+
publicKey: csr.publicKey,
|
|
415
|
+
signingKey: ca.privateKey,
|
|
416
|
+
signingAlgorithm: MESH_SIGNING_ALGORITHM,
|
|
417
|
+
extensions
|
|
418
|
+
})),
|
|
419
|
+
serialNumber,
|
|
420
|
+
notAfter,
|
|
421
|
+
commonName: cn
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
/** Issue a leaf for a known nodeID with a freshly generated key pair (tests / hub self). */
|
|
425
|
+
async function issueMeshLeafForNode(ca, nodeId, opts = {}) {
|
|
426
|
+
const keys = await node_crypto.webcrypto.subtle.generateKey(MESH_KEY_ALGORITHM, true, ["sign", "verify"]);
|
|
427
|
+
return {
|
|
428
|
+
...await issueMeshLeafFromCsr(ca, await createMeshCsrPem(keys, nodeId), {
|
|
429
|
+
expectedNodeId: nodeId,
|
|
430
|
+
now: opts.now,
|
|
431
|
+
validDays: opts.validDays,
|
|
432
|
+
allowReserved: true
|
|
433
|
+
}),
|
|
434
|
+
keyPem: await exportKeyPem(keys.privateKey)
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function subjectCn(subject) {
|
|
438
|
+
return /(?:^|,)\s*CN\s*=\s*([^,]+)/i.exec(subject)?.[1]?.trim() ?? null;
|
|
439
|
+
}
|
|
440
|
+
async function createMeshCsrPem(keys, commonName) {
|
|
441
|
+
return `${(await require_x509_es.Pkcs10CertificateRequestGenerator.create({
|
|
442
|
+
name: `CN=${commonName}`,
|
|
443
|
+
keys,
|
|
444
|
+
signingAlgorithm: MESH_SIGNING_ALGORITHM
|
|
445
|
+
})).toString("pem")}\n`;
|
|
446
|
+
}
|
|
447
|
+
async function generateMeshKeyPair() {
|
|
448
|
+
return node_crypto.webcrypto.subtle.generateKey(MESH_KEY_ALGORITHM, true, ["sign", "verify"]);
|
|
449
|
+
}
|
|
450
|
+
async function exportMeshPrivateKeyPem(key) {
|
|
451
|
+
return exportKeyPem(key);
|
|
452
|
+
}
|
|
453
|
+
//#endregion
|
|
301
454
|
//#region src/kernel/moleculer/mesh-mtls/mesh-ca-keeper.ts
|
|
302
|
-
var SLOT$
|
|
455
|
+
var SLOT$3 = Symbol.for("camstack.mesh.ca");
|
|
303
456
|
var NOT_LOADED = {
|
|
304
457
|
state: "unavailable",
|
|
305
458
|
reason: "not-loaded"
|
|
@@ -311,10 +464,10 @@ function isState(value) {
|
|
|
311
464
|
return state === "unavailable" && typeof Reflect.get(value, "reason") === "string";
|
|
312
465
|
}
|
|
313
466
|
function setProcessMeshCa(state) {
|
|
314
|
-
Reflect.set(globalThis, SLOT$
|
|
467
|
+
Reflect.set(globalThis, SLOT$3, state);
|
|
315
468
|
}
|
|
316
469
|
function getProcessMeshCaState() {
|
|
317
|
-
const value = Reflect.get(globalThis, SLOT$
|
|
470
|
+
const value = Reflect.get(globalThis, SLOT$3);
|
|
318
471
|
return isState(value) ? value : NOT_LOADED;
|
|
319
472
|
}
|
|
320
473
|
function requireProcessMeshCa() {
|
|
@@ -420,7 +573,7 @@ async function loadOrGenerateMeshCa(deps) {
|
|
|
420
573
|
try {
|
|
421
574
|
return {
|
|
422
575
|
state: "ready",
|
|
423
|
-
ca: await
|
|
576
|
+
ca: await loadMeshCa(certPem, entry.value)
|
|
424
577
|
};
|
|
425
578
|
} catch (err) {
|
|
426
579
|
return {
|
|
@@ -430,12 +583,12 @@ async function loadOrGenerateMeshCa(deps) {
|
|
|
430
583
|
}
|
|
431
584
|
}
|
|
432
585
|
case "missing": try {
|
|
433
|
-
const generated = await
|
|
586
|
+
const generated = await generateMeshCa();
|
|
434
587
|
await deps.secrets.put(require_constants$8.MESH_CA_KEY_REF, generated.keyPem);
|
|
435
588
|
await deps.writeMeshCaCert(generated.certPem);
|
|
436
589
|
return {
|
|
437
590
|
state: "ready",
|
|
438
|
-
ca: await
|
|
591
|
+
ca: await loadMeshCa(generated.certPem, generated.keyPem)
|
|
439
592
|
};
|
|
440
593
|
} catch (err) {
|
|
441
594
|
return {
|
|
@@ -456,10 +609,10 @@ async function loadOrGenerateMeshCa(deps) {
|
|
|
456
609
|
var MeshCrl = class {
|
|
457
610
|
serials = /* @__PURE__ */ new Set();
|
|
458
611
|
revoke(serialNumber) {
|
|
459
|
-
this.serials.add(normalizeSerial(serialNumber));
|
|
612
|
+
this.serials.add(normalizeSerial$1(serialNumber));
|
|
460
613
|
}
|
|
461
614
|
isRevoked(serialNumber) {
|
|
462
|
-
return this.serials.has(normalizeSerial(serialNumber));
|
|
615
|
+
return this.serials.has(normalizeSerial$1(serialNumber));
|
|
463
616
|
}
|
|
464
617
|
list() {
|
|
465
618
|
return [...this.serials];
|
|
@@ -467,21 +620,478 @@ var MeshCrl = class {
|
|
|
467
620
|
/** Restore from persisted serials (system-settings). */
|
|
468
621
|
replaceAll(serials) {
|
|
469
622
|
this.serials.clear();
|
|
470
|
-
for (const s of serials) this.serials.add(normalizeSerial(s));
|
|
623
|
+
for (const s of serials) this.serials.add(normalizeSerial$1(s));
|
|
471
624
|
}
|
|
472
625
|
};
|
|
473
|
-
function normalizeSerial(serial) {
|
|
626
|
+
function normalizeSerial$1(serial) {
|
|
474
627
|
return serial.replace(/^0x/i, "").replace(/:/g, "").toLowerCase();
|
|
475
628
|
}
|
|
476
|
-
var SLOT$
|
|
629
|
+
var SLOT$2 = Symbol.for("camstack.mesh.crl");
|
|
477
630
|
function processMeshCrl() {
|
|
478
|
-
const existing = Reflect.get(globalThis, SLOT$
|
|
631
|
+
const existing = Reflect.get(globalThis, SLOT$2);
|
|
479
632
|
if (existing instanceof MeshCrl) return existing;
|
|
480
633
|
const crl = new MeshCrl();
|
|
481
|
-
Reflect.set(globalThis, SLOT$
|
|
634
|
+
Reflect.set(globalThis, SLOT$2, crl);
|
|
482
635
|
return crl;
|
|
483
636
|
}
|
|
484
637
|
//#endregion
|
|
638
|
+
//#region src/kernel/moleculer/mesh-mtls/mesh-certificate-registry.ts
|
|
639
|
+
/**
|
|
640
|
+
* The hub's record of every mesh leaf (D856): which certificate each node holds,
|
|
641
|
+
* when it expires, and which serials are revoked.
|
|
642
|
+
*
|
|
643
|
+
* Three ways a row appears:
|
|
644
|
+
* - `enroll` / `renewal` / `boot` - the hub issued it and wrote it down at the
|
|
645
|
+
* moment of issuing (the authoritative record);
|
|
646
|
+
* - `observed` - a node enrolled before this record existed (or by a path that
|
|
647
|
+
* did not write it) presented this leaf on its LIVE mutual-TLS connection.
|
|
648
|
+
*
|
|
649
|
+
* Revoked serials are persisted here and replayed into the in-memory CRL at boot,
|
|
650
|
+
* so a revocation survives a hub restart. Hub only: agents persist none of it.
|
|
651
|
+
*/
|
|
652
|
+
/**
|
|
653
|
+
* @durable class=config owner=system-config
|
|
654
|
+
* write="the hub records every mesh leaf it issues (enroll, renewal, boot) and every
|
|
655
|
+
* leaf a connected node is observed presenting; a revocation stamps revokedAt"
|
|
656
|
+
* retention="a revoked or expired row is kept 90 days past its end, then reaped; a
|
|
657
|
+
* live row is never deleted. Forgetting a node does not delete its rows."
|
|
658
|
+
*/
|
|
659
|
+
var MESH_NODE_CERTIFICATES_COLLECTION = "mesh_node_certificates";
|
|
660
|
+
var MESH_NODE_CERTIFICATES_COLUMNS = [
|
|
661
|
+
{
|
|
662
|
+
name: "id",
|
|
663
|
+
type: "TEXT",
|
|
664
|
+
primaryKey: true,
|
|
665
|
+
notNull: true
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
name: "nodeId",
|
|
669
|
+
type: "TEXT",
|
|
670
|
+
notNull: true
|
|
671
|
+
},
|
|
672
|
+
{
|
|
673
|
+
name: "serial",
|
|
674
|
+
type: "TEXT",
|
|
675
|
+
notNull: true
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
name: "fingerprint",
|
|
679
|
+
type: "TEXT",
|
|
680
|
+
notNull: true
|
|
681
|
+
},
|
|
682
|
+
{
|
|
683
|
+
name: "notBefore",
|
|
684
|
+
type: "INTEGER",
|
|
685
|
+
notNull: true
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
name: "notAfter",
|
|
689
|
+
type: "INTEGER",
|
|
690
|
+
notNull: true
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
name: "issuedAt",
|
|
694
|
+
type: "INTEGER",
|
|
695
|
+
notNull: true
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
name: "issuedVia",
|
|
699
|
+
type: "TEXT",
|
|
700
|
+
notNull: true
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
name: "revokedAt",
|
|
704
|
+
type: "INTEGER"
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
name: "revokedReason",
|
|
708
|
+
type: "TEXT"
|
|
709
|
+
}
|
|
710
|
+
];
|
|
711
|
+
var RETENTION_MS = 90 * 864e5;
|
|
712
|
+
/**
|
|
713
|
+
* A leaf the hub signed for a renewal that did not complete. It must not be the node's
|
|
714
|
+
* `current` leaf, but it is NOT on the CRL: the node may still hold it (the swap
|
|
715
|
+
* could have landed without the hub seeing it), and cutting it off would be the
|
|
716
|
+
* outage the hand-over exists to prevent. If the node is later seen presenting it, it
|
|
717
|
+
* is reinstated.
|
|
718
|
+
*/
|
|
719
|
+
var RENEWAL_ABANDONED = "renewal-abandoned";
|
|
720
|
+
var VIAS = [
|
|
721
|
+
"enroll",
|
|
722
|
+
"pairing",
|
|
723
|
+
"renewal",
|
|
724
|
+
"boot",
|
|
725
|
+
"observed"
|
|
726
|
+
];
|
|
727
|
+
/** CN, serial, fingerprint and validity of a leaf PEM (or DER). */
|
|
728
|
+
function describeMeshLeaf(cert) {
|
|
729
|
+
const x509 = new node_crypto.X509Certificate(cert);
|
|
730
|
+
return {
|
|
731
|
+
cn: subjectCn(x509.subject) ?? "",
|
|
732
|
+
serial: x509.serialNumber,
|
|
733
|
+
fingerprint: x509.fingerprint256,
|
|
734
|
+
notBefore: Date.parse(x509.validFrom),
|
|
735
|
+
notAfter: Date.parse(x509.validTo)
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
function normalizeSerial(serial) {
|
|
739
|
+
return serial.replace(/^0x/i, "").replace(/:/g, "").toLowerCase();
|
|
740
|
+
}
|
|
741
|
+
function parse$1(data) {
|
|
742
|
+
const str = (k) => {
|
|
743
|
+
const v = data[k];
|
|
744
|
+
return typeof v === "string" && v.length > 0 ? v : null;
|
|
745
|
+
};
|
|
746
|
+
const num = (k) => {
|
|
747
|
+
const v = data[k];
|
|
748
|
+
return typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
749
|
+
};
|
|
750
|
+
const nodeId = str("nodeId");
|
|
751
|
+
const serial = str("serial");
|
|
752
|
+
const fingerprint = str("fingerprint");
|
|
753
|
+
const notBefore = num("notBefore");
|
|
754
|
+
const notAfter = num("notAfter");
|
|
755
|
+
const issuedAt = num("issuedAt");
|
|
756
|
+
if (nodeId === null || serial === null || fingerprint === null || notBefore === null || notAfter === null || issuedAt === null) return null;
|
|
757
|
+
return {
|
|
758
|
+
nodeId,
|
|
759
|
+
serial,
|
|
760
|
+
fingerprint,
|
|
761
|
+
notBefore,
|
|
762
|
+
notAfter,
|
|
763
|
+
issuedAt,
|
|
764
|
+
issuedVia: VIAS.find((v) => v === data["issuedVia"]) ?? "observed",
|
|
765
|
+
revokedAt: num("revokedAt"),
|
|
766
|
+
revokedReason: str("revokedReason")
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
var MeshCertificateRegistry = class {
|
|
770
|
+
store;
|
|
771
|
+
deps;
|
|
772
|
+
rows = /* @__PURE__ */ new Map();
|
|
773
|
+
constructor(store, deps = {}) {
|
|
774
|
+
this.store = store;
|
|
775
|
+
this.deps = deps;
|
|
776
|
+
}
|
|
777
|
+
recordToBody(record) {
|
|
778
|
+
return {
|
|
779
|
+
nodeId: record.nodeId,
|
|
780
|
+
serial: record.serial,
|
|
781
|
+
fingerprint: record.fingerprint,
|
|
782
|
+
notBefore: record.notBefore,
|
|
783
|
+
notAfter: record.notAfter,
|
|
784
|
+
issuedAt: record.issuedAt,
|
|
785
|
+
issuedVia: record.issuedVia,
|
|
786
|
+
revokedAt: record.revokedAt,
|
|
787
|
+
revokedReason: record.revokedReason
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
now() {
|
|
791
|
+
return (this.deps.now ?? Date.now)();
|
|
792
|
+
}
|
|
793
|
+
async load() {
|
|
794
|
+
const kind = this.deps.meshKind ?? null;
|
|
795
|
+
if (kind !== null) {
|
|
796
|
+
const page = await kind.query({ limit: 1e4 });
|
|
797
|
+
for (const row of page.records) {
|
|
798
|
+
const { fingerprint: _fp, ...body } = row.data;
|
|
799
|
+
const record = parse$1({
|
|
800
|
+
...body,
|
|
801
|
+
fingerprint: row.id
|
|
802
|
+
});
|
|
803
|
+
if (record !== null) this.rows.set(record.fingerprint, record);
|
|
804
|
+
}
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
const rows = await this.store.query.query({ collection: MESH_NODE_CERTIFICATES_COLLECTION });
|
|
808
|
+
for (const row of rows) {
|
|
809
|
+
const record = parse$1(row.data);
|
|
810
|
+
if (record !== null) this.rows.set(record.fingerprint, record);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
/** Write down a leaf the hub just issued. Idempotent per fingerprint. */
|
|
814
|
+
async recordIssued(nodeId, certPem, via) {
|
|
815
|
+
const leaf = describeMeshLeaf(certPem);
|
|
816
|
+
const existing = this.rows.get(leaf.fingerprint);
|
|
817
|
+
if (existing !== void 0) return existing;
|
|
818
|
+
const record = {
|
|
819
|
+
nodeId,
|
|
820
|
+
serial: leaf.serial,
|
|
821
|
+
fingerprint: leaf.fingerprint,
|
|
822
|
+
notBefore: leaf.notBefore,
|
|
823
|
+
notAfter: leaf.notAfter,
|
|
824
|
+
issuedAt: this.now(),
|
|
825
|
+
issuedVia: via,
|
|
826
|
+
revokedAt: null,
|
|
827
|
+
revokedReason: null
|
|
828
|
+
};
|
|
829
|
+
await this.insert(record);
|
|
830
|
+
this.deps.logger?.info("Mesh certificate recorded", {
|
|
831
|
+
tags: { nodeId },
|
|
832
|
+
meta: {
|
|
833
|
+
via,
|
|
834
|
+
serial: leaf.serial,
|
|
835
|
+
notAfter: new Date(leaf.notAfter).toISOString()
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
return record;
|
|
839
|
+
}
|
|
840
|
+
/**
|
|
841
|
+
* A node was seen presenting `leaf` on its live connection. A leaf the hub already
|
|
842
|
+
* knows is left alone; an unknown one is recorded `observed` (a node enrolled
|
|
843
|
+
* before this record existed).
|
|
844
|
+
*/
|
|
845
|
+
async recordObserved(nodeId, leaf) {
|
|
846
|
+
const known = this.rows.get(leaf.fingerprint);
|
|
847
|
+
if (known !== void 0) {
|
|
848
|
+
if (known.revokedReason !== "renewal-abandoned") return known.revokedAt === null ? this.abandonRenewalsNewerThan(known) : false;
|
|
849
|
+
const reinstated = {
|
|
850
|
+
...known,
|
|
851
|
+
revokedAt: null,
|
|
852
|
+
revokedReason: null
|
|
853
|
+
};
|
|
854
|
+
this.rows.set(known.fingerprint, reinstated);
|
|
855
|
+
await this.persist(reinstated);
|
|
856
|
+
this.deps.logger?.warn("An abandoned renewal leaf is in use - reinstated", {
|
|
857
|
+
tags: { nodeId },
|
|
858
|
+
meta: { serial: known.serial }
|
|
859
|
+
});
|
|
860
|
+
return true;
|
|
861
|
+
}
|
|
862
|
+
const record = {
|
|
863
|
+
nodeId,
|
|
864
|
+
serial: leaf.serial,
|
|
865
|
+
fingerprint: leaf.fingerprint,
|
|
866
|
+
notBefore: leaf.notBefore,
|
|
867
|
+
notAfter: leaf.notAfter,
|
|
868
|
+
issuedAt: this.now(),
|
|
869
|
+
issuedVia: "observed",
|
|
870
|
+
revokedAt: null,
|
|
871
|
+
revokedReason: null
|
|
872
|
+
};
|
|
873
|
+
await this.insert(record);
|
|
874
|
+
this.deps.logger?.info("Mesh certificate observed on a live connection and recorded", {
|
|
875
|
+
tags: { nodeId },
|
|
876
|
+
meta: {
|
|
877
|
+
serial: leaf.serial,
|
|
878
|
+
fingerprint: leaf.fingerprint
|
|
879
|
+
}
|
|
880
|
+
});
|
|
881
|
+
return true;
|
|
882
|
+
}
|
|
883
|
+
list(nodeId) {
|
|
884
|
+
return [...this.rows.values()].filter((r) => nodeId === void 0 || r.nodeId === nodeId).toSorted((a, b) => b.issuedAt - a.issuedAt);
|
|
885
|
+
}
|
|
886
|
+
/** The newest non-revoked leaf of a node, or `undefined`. */
|
|
887
|
+
currentFor(nodeId) {
|
|
888
|
+
return this.list(nodeId).find((r) => r.revokedAt === null);
|
|
889
|
+
}
|
|
890
|
+
/** Every non-revoked leaf of a node (a renewal in flight has two). */
|
|
891
|
+
liveFor(nodeId) {
|
|
892
|
+
return this.list(nodeId).filter((r) => r.revokedAt === null);
|
|
893
|
+
}
|
|
894
|
+
/** Stamp a leaf revoked (and mirror it into `crl`). `false` when unknown or already revoked. */
|
|
895
|
+
async revoke(fingerprint, reason, crl) {
|
|
896
|
+
const record = this.rows.get(fingerprint);
|
|
897
|
+
if (record === void 0 || record.revokedAt !== null) return false;
|
|
898
|
+
const revoked = {
|
|
899
|
+
...record,
|
|
900
|
+
revokedAt: this.now(),
|
|
901
|
+
revokedReason: reason
|
|
902
|
+
};
|
|
903
|
+
this.rows.set(fingerprint, revoked);
|
|
904
|
+
crl?.revoke(record.serial);
|
|
905
|
+
await this.persist(revoked);
|
|
906
|
+
this.deps.logger?.warn("Mesh certificate revoked", {
|
|
907
|
+
tags: { nodeId: record.nodeId },
|
|
908
|
+
meta: {
|
|
909
|
+
serial: record.serial,
|
|
910
|
+
reason
|
|
911
|
+
}
|
|
912
|
+
});
|
|
913
|
+
return true;
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Operator revocation of a whole node: EVERY leaf of the node that is not already on the
|
|
917
|
+
* CRL goes on it - the current one, an observed one, a renewal one, and a leaf that was set
|
|
918
|
+
* aside (`renewal-abandoned`: a copy of it may sit on a stolen box, and it must not stay
|
|
919
|
+
* valid just because the hub once set it aside). Returns the serials put on the CRL.
|
|
920
|
+
* A leaf revoked this way is never reinstated by `recordObserved`.
|
|
921
|
+
*/
|
|
922
|
+
async revokeAllForNode(nodeId, reason, crl, keepFingerprint) {
|
|
923
|
+
const serials = [];
|
|
924
|
+
for (const record of this.list(nodeId)) {
|
|
925
|
+
if (keepFingerprint !== void 0 && record.fingerprint === keepFingerprint) continue;
|
|
926
|
+
if (record.revokedAt !== null && record.revokedReason !== "renewal-abandoned") continue;
|
|
927
|
+
const revoked = {
|
|
928
|
+
...record,
|
|
929
|
+
revokedAt: record.revokedAt ?? this.now(),
|
|
930
|
+
revokedReason: reason
|
|
931
|
+
};
|
|
932
|
+
this.rows.set(record.fingerprint, revoked);
|
|
933
|
+
crl.revoke(record.serial);
|
|
934
|
+
await this.persist(revoked);
|
|
935
|
+
serials.push(record.serial);
|
|
936
|
+
}
|
|
937
|
+
if (serials.length > 0) this.deps.logger?.warn("Every mesh certificate of the node revoked", {
|
|
938
|
+
tags: { nodeId },
|
|
939
|
+
meta: {
|
|
940
|
+
serials,
|
|
941
|
+
reason
|
|
942
|
+
}
|
|
943
|
+
});
|
|
944
|
+
return serials;
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* At boot: a node with TWO live leaves had a renewal in flight when the hub stopped. The
|
|
948
|
+
* newer (renewal) one is set aside - it is not the node's current leaf until the node is
|
|
949
|
+
* seen presenting it, which reinstates it. It is NOT put on the CRL.
|
|
950
|
+
*/
|
|
951
|
+
async settleInterruptedRenewals() {
|
|
952
|
+
let settled = 0;
|
|
953
|
+
const byNode = /* @__PURE__ */ new Map();
|
|
954
|
+
for (const record of this.rows.values()) {
|
|
955
|
+
if (record.revokedAt !== null) continue;
|
|
956
|
+
byNode.set(record.nodeId, [...byNode.get(record.nodeId) ?? [], record]);
|
|
957
|
+
}
|
|
958
|
+
for (const records of byNode.values()) {
|
|
959
|
+
if (records.length < 2) continue;
|
|
960
|
+
for (const record of records) if (record.issuedVia === "renewal" && await this.abandon(record.fingerprint)) settled += 1;
|
|
961
|
+
}
|
|
962
|
+
return settled;
|
|
963
|
+
}
|
|
964
|
+
/** Set a never-completed renewal leaf aside (not current, NOT on the CRL). */
|
|
965
|
+
/** Fingerprints of renewal leaves whose handover is running right now (this process only). */
|
|
966
|
+
renewalsInFlight = /* @__PURE__ */ new Set();
|
|
967
|
+
/**
|
|
968
|
+
* A renewal's leaf is "in flight" from signing until the handover ends. While it is, a node seen
|
|
969
|
+
* presenting its OLD leaf is simply not swapped yet: the new leaf is not set aside (that would
|
|
970
|
+
* flap set-aside / reinstate on the node's first connection with it).
|
|
971
|
+
*/
|
|
972
|
+
beginRenewal(fingerprint) {
|
|
973
|
+
this.renewalsInFlight.add(fingerprint);
|
|
974
|
+
}
|
|
975
|
+
endRenewal(fingerprint) {
|
|
976
|
+
this.renewalsInFlight.delete(fingerprint);
|
|
977
|
+
}
|
|
978
|
+
async abandonRenewalsNewerThan(presented) {
|
|
979
|
+
let changed = false;
|
|
980
|
+
for (const record of this.liveFor(presented.nodeId)) {
|
|
981
|
+
if (record.fingerprint === presented.fingerprint) continue;
|
|
982
|
+
if (record.issuedVia !== "renewal" || record.issuedAt <= presented.issuedAt) continue;
|
|
983
|
+
if (this.renewalsInFlight.has(record.fingerprint)) continue;
|
|
984
|
+
if (await this.abandon(record.fingerprint)) changed = true;
|
|
985
|
+
}
|
|
986
|
+
return changed;
|
|
987
|
+
}
|
|
988
|
+
async abandon(fingerprint) {
|
|
989
|
+
const record = this.rows.get(fingerprint);
|
|
990
|
+
if (record === void 0 || record.revokedAt !== null) return false;
|
|
991
|
+
const abandoned = {
|
|
992
|
+
...record,
|
|
993
|
+
revokedAt: this.now(),
|
|
994
|
+
revokedReason: RENEWAL_ABANDONED
|
|
995
|
+
};
|
|
996
|
+
this.rows.set(fingerprint, abandoned);
|
|
997
|
+
await this.persist(abandoned);
|
|
998
|
+
this.deps.logger?.warn("Renewal leaf set aside (not current, not on the CRL)", {
|
|
999
|
+
tags: { nodeId: record.nodeId },
|
|
1000
|
+
meta: { serial: record.serial }
|
|
1001
|
+
});
|
|
1002
|
+
return true;
|
|
1003
|
+
}
|
|
1004
|
+
/** Replay every persisted revocation into `crl` (hub boot). Set-aside leaves are not revocations. */
|
|
1005
|
+
restoreCrl(crl) {
|
|
1006
|
+
let restored = 0;
|
|
1007
|
+
for (const record of this.rows.values()) {
|
|
1008
|
+
if (record.revokedAt === null || record.revokedReason === "renewal-abandoned") continue;
|
|
1009
|
+
crl.revoke(record.serial);
|
|
1010
|
+
restored += 1;
|
|
1011
|
+
}
|
|
1012
|
+
return restored;
|
|
1013
|
+
}
|
|
1014
|
+
/** Is `serial` listed as revoked here? */
|
|
1015
|
+
isRevoked(serial) {
|
|
1016
|
+
const wanted = normalizeSerial(serial);
|
|
1017
|
+
for (const record of this.rows.values()) if (record.revokedAt !== null && record.revokedReason !== "renewal-abandoned" && normalizeSerial(record.serial) === wanted) return true;
|
|
1018
|
+
return false;
|
|
1019
|
+
}
|
|
1020
|
+
async reap() {
|
|
1021
|
+
const cutoff = this.now() - RETENTION_MS;
|
|
1022
|
+
const dead = [...this.rows.values()].filter((r) => (r.revokedAt ?? r.notAfter) < cutoff);
|
|
1023
|
+
for (const r of dead) {
|
|
1024
|
+
this.rows.delete(r.fingerprint);
|
|
1025
|
+
await this.deleteRow(r.fingerprint);
|
|
1026
|
+
}
|
|
1027
|
+
return dead.length;
|
|
1028
|
+
}
|
|
1029
|
+
async insert(record) {
|
|
1030
|
+
this.rows.set(record.fingerprint, record);
|
|
1031
|
+
const kind = this.deps.meshKind ?? null;
|
|
1032
|
+
if (kind !== null) {
|
|
1033
|
+
const parsed = require_agent_https.systemConfigRecordKinds["mesh-node-certificates"].schema.safeParse(this.recordToBody(record));
|
|
1034
|
+
if (!parsed.success) throw new Error(parsed.error.message);
|
|
1035
|
+
await kind.put({
|
|
1036
|
+
id: record.fingerprint,
|
|
1037
|
+
data: parsed.data
|
|
1038
|
+
});
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
await this.store.insert.mutate({
|
|
1042
|
+
collection: MESH_NODE_CERTIFICATES_COLLECTION,
|
|
1043
|
+
record: {
|
|
1044
|
+
id: record.fingerprint,
|
|
1045
|
+
data: { ...record }
|
|
1046
|
+
}
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
async deleteRow(fingerprint) {
|
|
1050
|
+
const kind = this.deps.meshKind ?? null;
|
|
1051
|
+
if (kind !== null) {
|
|
1052
|
+
await kind.delete(fingerprint);
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
await this.store.delete.mutate({
|
|
1056
|
+
collection: MESH_NODE_CERTIFICATES_COLLECTION,
|
|
1057
|
+
key: fingerprint
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
async persist(record) {
|
|
1061
|
+
try {
|
|
1062
|
+
const kind = this.deps.meshKind ?? null;
|
|
1063
|
+
if (kind !== null) {
|
|
1064
|
+
const parsed = require_agent_https.systemConfigRecordKinds["mesh-node-certificates"].schema.safeParse(this.recordToBody(record));
|
|
1065
|
+
if (!parsed.success) throw new Error(parsed.error.message);
|
|
1066
|
+
await kind.put({
|
|
1067
|
+
id: record.fingerprint,
|
|
1068
|
+
data: parsed.data
|
|
1069
|
+
});
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
await this.store.update.mutate({
|
|
1073
|
+
collection: MESH_NODE_CERTIFICATES_COLLECTION,
|
|
1074
|
+
id: record.fingerprint,
|
|
1075
|
+
data: { ...record }
|
|
1076
|
+
});
|
|
1077
|
+
} catch (error) {
|
|
1078
|
+
this.deps.logger?.error("Mesh certificate write failed - the change holds until restart", {
|
|
1079
|
+
tags: { nodeId: record.nodeId },
|
|
1080
|
+
meta: { error: error instanceof Error ? error.message : String(error) }
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
var SLOT$1 = Symbol.for("camstack.mesh.certificate-registry");
|
|
1086
|
+
function setProcessMeshCertificateRegistry(registry) {
|
|
1087
|
+
if (registry === null) Reflect.deleteProperty(globalThis, SLOT$1);
|
|
1088
|
+
else Reflect.set(globalThis, SLOT$1, registry);
|
|
1089
|
+
}
|
|
1090
|
+
function getProcessMeshCertificateRegistry() {
|
|
1091
|
+
const value = Reflect.get(globalThis, SLOT$1);
|
|
1092
|
+
return value !== null && typeof value === "object" && typeof Reflect.get(value, "recordIssued") === "function" ? value : null;
|
|
1093
|
+
}
|
|
1094
|
+
//#endregion
|
|
485
1095
|
//#region src/kernel/moleculer/mesh-mtls/mesh-tls-gate.ts
|
|
486
1096
|
var STATE_SLOT = Symbol.for("camstack.mesh.tls-listener-state");
|
|
487
1097
|
function setProcessMeshTlsListenerState(state) {
|
|
@@ -624,7 +1234,7 @@ async function runRenewal(deps, inFlight) {
|
|
|
624
1234
|
}
|
|
625
1235
|
let issued;
|
|
626
1236
|
try {
|
|
627
|
-
issued = await
|
|
1237
|
+
issued = await issueMeshLeafFromCsr(deps.ca, csrPem, { expectedNodeId: nodeId });
|
|
628
1238
|
await deps.registry.recordIssued(nodeId, issued.certPem, "renewal");
|
|
629
1239
|
log.info("Mesh renewal: new leaf signed and recorded", {
|
|
630
1240
|
tags,
|
|
@@ -636,7 +1246,7 @@ async function runRenewal(deps, inFlight) {
|
|
|
636
1246
|
} catch (error) {
|
|
637
1247
|
return fail("issue", message(error), true);
|
|
638
1248
|
}
|
|
639
|
-
const newLeaf =
|
|
1249
|
+
const newLeaf = describeMeshLeaf(issued.certPem);
|
|
640
1250
|
deps.registry.beginRenewal(newLeaf.fingerprint);
|
|
641
1251
|
inFlight.fingerprint = newLeaf.fingerprint;
|
|
642
1252
|
let resolveRegistered = () => void 0;
|
|
@@ -86154,7 +86764,7 @@ function peerIdentity(socket) {
|
|
|
86154
86764
|
if (cert == null || typeof cert !== "object" || !("raw" in cert) || !cert.raw) return null;
|
|
86155
86765
|
try {
|
|
86156
86766
|
const x509 = new node_crypto.X509Certificate(cert.raw);
|
|
86157
|
-
const cn =
|
|
86767
|
+
const cn = subjectCn(x509.subject) ?? subjectCn(`CN=${cert.subject?.CN ?? ""}`);
|
|
86158
86768
|
if (cn === null || cn.length === 0) return null;
|
|
86159
86769
|
return {
|
|
86160
86770
|
cn,
|
|
@@ -86682,7 +87292,7 @@ function newPairingId() {
|
|
|
86682
87292
|
}
|
|
86683
87293
|
/** The hub's commitment to its nonce, bound to the CA certificate it offers. */
|
|
86684
87294
|
function computePairingCommitment(caCertPem, hubNonce) {
|
|
86685
|
-
return (0, node_crypto.createHash)("sha256").update(`${PAIRING_DOMAIN}|commit|${
|
|
87295
|
+
return (0, node_crypto.createHash)("sha256").update(`${PAIRING_DOMAIN}|commit|${caFingerprintCompact(caCertPem)}|${hubNonce}`, "utf8").digest("hex");
|
|
86686
87296
|
}
|
|
86687
87297
|
function commitmentsEqual(expected, presented) {
|
|
86688
87298
|
return constantTimeEqual(expected, presented);
|
|
@@ -86692,7 +87302,7 @@ function computePairingSas(input) {
|
|
|
86692
87302
|
const value = (0, node_crypto.createHash)("sha256").update([
|
|
86693
87303
|
PAIRING_DOMAIN,
|
|
86694
87304
|
"sas",
|
|
86695
|
-
|
|
87305
|
+
caFingerprintCompact(input.caCertPem),
|
|
86696
87306
|
input.agentPublicKeySpki,
|
|
86697
87307
|
input.hubNonce,
|
|
86698
87308
|
input.agentNonce
|
|
@@ -86708,7 +87318,7 @@ function constantTimeEqual(expected, presented) {
|
|
|
86708
87318
|
async function describeCsr(csrPem) {
|
|
86709
87319
|
const csr = new require_x509_es.Pkcs10CertificateRequest(csrPem);
|
|
86710
87320
|
if (!await csr.verify(node_crypto.webcrypto)) throw new Error("the agent CSR signature is invalid");
|
|
86711
|
-
const cn =
|
|
87321
|
+
const cn = subjectCn(csr.subject);
|
|
86712
87322
|
if (cn === null || cn.length === 0) throw new Error("the agent CSR has no CN");
|
|
86713
87323
|
return {
|
|
86714
87324
|
commonName: cn,
|
|
@@ -87019,23 +87629,23 @@ var AgentPairing = class {
|
|
|
87019
87629
|
async offer(body) {
|
|
87020
87630
|
this.expireIfDue();
|
|
87021
87631
|
if (this.deps.isPaired()) return fail(409, "already-paired", "this agent is paired — unpair it first (from the hub or here)");
|
|
87022
|
-
if (!
|
|
87632
|
+
if (!isValidAgentNodeId(this.deps.nodeId)) return fail(409, "invalid-node-id", `this agent's node id "${this.deps.nodeId}" cannot be paired - rename the agent`);
|
|
87023
87633
|
const request = parseOffer(body);
|
|
87024
87634
|
if (request === null) return fail(400, "bad-offer", "the offer is malformed");
|
|
87025
87635
|
if (request.protocol !== 1) return fail(400, "unsupported-protocol", `pairing protocol ${String(request.protocol)} is not supported`);
|
|
87026
87636
|
if (readCaCertificate(request.hubCaCertPem, this.now()) === null) return fail(400, "bad-ca", "the offered CA is not a valid self-signed CA certificate");
|
|
87027
87637
|
if (!/^[0-9a-f]{64}$/.test(request.commitment)) return fail(400, "bad-offer", "the commitment is malformed");
|
|
87028
87638
|
if (!isHttpUrl(request.hubUrl)) return fail(400, "bad-offer", "hubUrl is not an http(s) URL");
|
|
87029
|
-
const caFingerprint =
|
|
87639
|
+
const caFingerprint = caFingerprintCompact(request.hubCaCertPem);
|
|
87030
87640
|
const refused = this.checkOfferSignature(request, caFingerprint);
|
|
87031
87641
|
if (refused !== null) return refused;
|
|
87032
87642
|
const rationed = this.rationOffer(caFingerprint);
|
|
87033
87643
|
if (rationed !== null) return rationed;
|
|
87034
87644
|
for (const existing of [...this.sessions.values()]) if (existing.hub.caFingerprint === caFingerprint) this.end(existing, "failed");
|
|
87035
87645
|
if (this.sessions.size >= 4 && !this.makeRoom()) return fail(409, "too-many-pending", "too many pairings are pending on this agent");
|
|
87036
|
-
const keys = await
|
|
87037
|
-
const csrPem = await
|
|
87038
|
-
const keyPem = await
|
|
87646
|
+
const keys = await generateMeshKeyPair();
|
|
87647
|
+
const csrPem = await createMeshCsrPem(keys, this.deps.nodeId);
|
|
87648
|
+
const keyPem = await exportMeshPrivateKeyPem(keys.privateKey);
|
|
87039
87649
|
const publicKeySpki = Buffer.from(await node_crypto.webcrypto.subtle.exportKey("spki", keys.publicKey)).toString("base64");
|
|
87040
87650
|
if (this.sessions.size >= 4 && !this.makeRoom()) return fail(409, "too-many-pending", "too many pairings are pending on this agent");
|
|
87041
87651
|
const session = {
|
|
@@ -87375,7 +87985,7 @@ var AgentPairing = class {
|
|
|
87375
87985
|
const leaf = new node_crypto.X509Certificate(leafPem);
|
|
87376
87986
|
const ca = new node_crypto.X509Certificate(session.hub.caCertPem);
|
|
87377
87987
|
if (!leaf.verify(ca.publicKey)) return fail(400, "bad-leaf", "the certificate is not signed by the offered CA");
|
|
87378
|
-
if (
|
|
87988
|
+
if (subjectCn(leaf.subject.replaceAll("\n", ",")) !== this.deps.nodeId) return fail(400, "bad-leaf", `the certificate is not issued for "${this.deps.nodeId}"`);
|
|
87379
87989
|
if (!leaf.checkPrivateKey((0, node_crypto.createPrivateKey)(session.keyPem))) return fail(400, "bad-leaf", "the certificate does not match this agent's private key");
|
|
87380
87990
|
if (Date.parse(leaf.validTo) <= this.now()) return fail(400, "bad-leaf", "the certificate is already expired");
|
|
87381
87991
|
} catch {
|
|
@@ -87433,7 +88043,7 @@ function field(body, key) {
|
|
|
87433
88043
|
}
|
|
87434
88044
|
function caFingerprintOf(pem) {
|
|
87435
88045
|
try {
|
|
87436
|
-
return
|
|
88046
|
+
return caFingerprintCompact(pem);
|
|
87437
88047
|
} catch {
|
|
87438
88048
|
return null;
|
|
87439
88049
|
}
|
|
@@ -87528,7 +88138,7 @@ var HubPairing = class {
|
|
|
87528
88138
|
use: PAIRING_OFFER_USE,
|
|
87529
88139
|
agentNodeId: hello.nodeId,
|
|
87530
88140
|
hubNodeId: this.deps.hubNodeId,
|
|
87531
|
-
caFingerprint:
|
|
88141
|
+
caFingerprint: caFingerprintCompact(ca.certPem),
|
|
87532
88142
|
commitment,
|
|
87533
88143
|
hubUrl,
|
|
87534
88144
|
hubMeshAddress,
|
|
@@ -87645,14 +88255,14 @@ var HubPairing = class {
|
|
|
87645
88255
|
}
|
|
87646
88256
|
async complete(session) {
|
|
87647
88257
|
const ca = this.deps.ca();
|
|
87648
|
-
if (ca === null ||
|
|
88258
|
+
if (ca === null || caFingerprintCompact(ca.certPem) !== caFingerprintCompact(session.caCertPem)) {
|
|
87649
88259
|
await this.cancel(session, "failed", "the mesh CA changed during the pairing");
|
|
87650
88260
|
return;
|
|
87651
88261
|
}
|
|
87652
88262
|
session.state = "completing";
|
|
87653
88263
|
let issuedPem = null;
|
|
87654
88264
|
try {
|
|
87655
|
-
const leaf = await
|
|
88265
|
+
const leaf = await issueMeshLeafFromCsr(ca, session.csrPem, { expectedNodeId: session.nodeId });
|
|
87656
88266
|
issuedPem = leaf.certPem;
|
|
87657
88267
|
await this.deps.recordIssued(session.nodeId, leaf.certPem);
|
|
87658
88268
|
session.issued = true;
|
|
@@ -87664,7 +88274,7 @@ var HubPairing = class {
|
|
|
87664
88274
|
hubNonce: session.hubNonce,
|
|
87665
88275
|
agentNonce: session.agentNonce,
|
|
87666
88276
|
leafFingerprint: leafFingerprintCompact(leaf.certPem),
|
|
87667
|
-
caFingerprint:
|
|
88277
|
+
caFingerprint: caFingerprintCompact(ca.certPem),
|
|
87668
88278
|
hubMeshAddress: session.hubMeshAddress,
|
|
87669
88279
|
hubUrl: session.hubUrl,
|
|
87670
88280
|
iat: this.now(),
|
|
@@ -87718,7 +88328,7 @@ var HubPairing = class {
|
|
|
87718
88328
|
}
|
|
87719
88329
|
}
|
|
87720
88330
|
assertNodeIdUsable(nodeId) {
|
|
87721
|
-
if (!
|
|
88331
|
+
if (!isValidAgentNodeId(nodeId)) throw new Error(`"${nodeId}" cannot be an agent node id (letters, digits, . _ - ; not "hub"; at most 63 characters)`);
|
|
87722
88332
|
if (this.deps.isNodeConnected(nodeId)) throw new Error(`a node called "${nodeId}" is already connected to this hub — unpair it first`);
|
|
87723
88333
|
const lower = nodeId.toLowerCase();
|
|
87724
88334
|
const clash = (this.deps.knownNodeIds?.() ?? []).find((known) => known !== nodeId && known.toLowerCase() === lower);
|
|
@@ -87903,7 +88513,7 @@ var SystemConfigAddon = class extends require_dist.BaseAddon {
|
|
|
87903
88513
|
};
|
|
87904
88514
|
}
|
|
87905
88515
|
async onShutdown() {
|
|
87906
|
-
|
|
88516
|
+
setProcessMeshCertificateRegistry(null);
|
|
87907
88517
|
setProcessMeshTlsListenerState(null);
|
|
87908
88518
|
require_agent_https.setProcessAgentHttpsNames(null);
|
|
87909
88519
|
this.stoppedMesh = true;
|
|
@@ -87934,14 +88544,14 @@ var SystemConfigAddon = class extends require_dist.BaseAddon {
|
|
|
87934
88544
|
this.loadAgentHttpsNames(store, records);
|
|
87935
88545
|
try {
|
|
87936
88546
|
if (records === void 0) await store.declareCollection.mutate({
|
|
87937
|
-
collection:
|
|
87938
|
-
columns:
|
|
88547
|
+
collection: MESH_NODE_CERTIFICATES_COLLECTION,
|
|
88548
|
+
columns: MESH_NODE_CERTIFICATES_COLUMNS,
|
|
87939
88549
|
indexes: [{
|
|
87940
88550
|
name: "idx_mesh_node_certificates_node",
|
|
87941
88551
|
columns: ["nodeId"]
|
|
87942
88552
|
}]
|
|
87943
88553
|
});
|
|
87944
|
-
const registry = new
|
|
88554
|
+
const registry = new MeshCertificateRegistry(store, {
|
|
87945
88555
|
logger: this.ctx.logger,
|
|
87946
88556
|
meshKind
|
|
87947
88557
|
});
|
|
@@ -87949,7 +88559,7 @@ var SystemConfigAddon = class extends require_dist.BaseAddon {
|
|
|
87949
88559
|
const restored = registry.restoreCrl(processMeshCrl());
|
|
87950
88560
|
const settled = await registry.settleInterruptedRenewals();
|
|
87951
88561
|
if (settled > 0) this.ctx.logger.warn("A mesh renewal was interrupted by a hub restart - its new leaf was set aside", { meta: { settled } });
|
|
87952
|
-
|
|
88562
|
+
setProcessMeshCertificateRegistry(registry);
|
|
87953
88563
|
if (restored > 0) this.ctx.logger.info("Mesh revocations restored into the CRL", { meta: { restored } });
|
|
87954
88564
|
return registry;
|
|
87955
88565
|
} catch (err) {
|
|
@@ -87983,7 +88593,7 @@ var SystemConfigAddon = class extends require_dist.BaseAddon {
|
|
|
87983
88593
|
await openMeshTlsWhenRevocationsLoaded({
|
|
87984
88594
|
loadRegistry: () => this.initMeshCertificateRegistry(),
|
|
87985
88595
|
open: async (registry) => {
|
|
87986
|
-
const hubLeaf = await
|
|
88596
|
+
const hubLeaf = await issueMeshLeafForNode(ca, "hub");
|
|
87987
88597
|
await registry.recordIssued("hub", hubLeaf.certPem, "boot").catch((err) => {
|
|
87988
88598
|
this.ctx.logger.warn("Hub mesh leaf could not be recorded", { meta: { error: err instanceof Error ? err.message : String(err) } });
|
|
87989
88599
|
});
|
|
@@ -88290,8 +88900,11 @@ exports.AGENT_UI_UNLOCK_VERSION = AGENT_UI_UNLOCK_VERSION;
|
|
|
88290
88900
|
exports.AgentPairing = AgentPairing;
|
|
88291
88901
|
exports.Errors = Errors;
|
|
88292
88902
|
exports.HubPairing = HubPairing;
|
|
88903
|
+
exports.MESH_NODE_CERTIFICATES_COLLECTION = MESH_NODE_CERTIFICATES_COLLECTION;
|
|
88904
|
+
exports.MESH_NODE_CERTIFICATES_COLUMNS = MESH_NODE_CERTIFICATES_COLUMNS;
|
|
88293
88905
|
exports.MESH_TLS_PORT_HELLO_FIELD = MESH_TLS_PORT_HELLO_FIELD;
|
|
88294
88906
|
exports.MeshCaKeeper = MeshCaKeeper;
|
|
88907
|
+
exports.MeshCertificateRegistry = MeshCertificateRegistry;
|
|
88295
88908
|
exports.MeshCrl = MeshCrl;
|
|
88296
88909
|
exports.PAIRING_CODE_DIGITS = PAIRING_CODE_DIGITS;
|
|
88297
88910
|
exports.PAIRING_MAX_CODE_ATTEMPTS = PAIRING_MAX_CODE_ATTEMPTS;
|
|
@@ -88301,26 +88914,39 @@ exports.PAIRING_PIN_DIGITS = PAIRING_PIN_DIGITS;
|
|
|
88301
88914
|
exports.PAIRING_PROTOCOL = PAIRING_PROTOCOL;
|
|
88302
88915
|
exports.PAIRING_TTL_MS = PAIRING_TTL_MS;
|
|
88303
88916
|
exports.PairingHttpError = PairingHttpError;
|
|
88917
|
+
exports.RENEWAL_ABANDONED = RENEWAL_ABANDONED;
|
|
88304
88918
|
exports.SYSTEM_CONFIG_CLUSTER_SECTION_ID = SYSTEM_CONFIG_CLUSTER_SECTION_ID;
|
|
88305
88919
|
exports.ServiceBroker = ServiceBroker;
|
|
88306
88920
|
exports.SystemConfigAddon = SystemConfigAddon;
|
|
88307
88921
|
exports.default = SystemConfigAddon;
|
|
88308
88922
|
exports.TlsTcpTransporter = TlsTcpTransporter;
|
|
88309
88923
|
exports.UnlockTicketJtiStore = UnlockTicketJtiStore;
|
|
88924
|
+
exports.caFingerprint = caFingerprint;
|
|
88925
|
+
exports.caFingerprintCompact = caFingerprintCompact;
|
|
88310
88926
|
exports.computePairingCommitment = computePairingCommitment;
|
|
88311
88927
|
exports.computePairingSas = computePairingSas;
|
|
88312
88928
|
exports.createCidrMatcher = createCidrMatcher;
|
|
88929
|
+
exports.createMeshCsrPem = createMeshCsrPem;
|
|
88313
88930
|
exports.describeCsr = describeCsr;
|
|
88931
|
+
exports.describeMeshLeaf = describeMeshLeaf;
|
|
88314
88932
|
exports.dropProcessPeer = dropProcessPeer;
|
|
88315
88933
|
exports.emitProcessNodeRegistered = emitProcessNodeRegistered;
|
|
88934
|
+
exports.exportMeshPrivateKeyPem = exportMeshPrivateKeyPem;
|
|
88935
|
+
exports.generateMeshCa = generateMeshCa;
|
|
88936
|
+
exports.generateMeshKeyPair = generateMeshKeyPair;
|
|
88316
88937
|
exports.getProcessMeshCaState = getProcessMeshCaState;
|
|
88938
|
+
exports.getProcessMeshCertificateRegistry = getProcessMeshCertificateRegistry;
|
|
88317
88939
|
exports.getProcessMeshTlsListenerState = getProcessMeshTlsListenerState;
|
|
88318
88940
|
exports.getProcessNodeMeshTransport = getProcessNodeMeshTransport;
|
|
88319
88941
|
exports.getProcessPeerLeaf = getProcessPeerLeaf;
|
|
88320
88942
|
exports.hubHostFromMeshAddress = hubHostFromMeshAddress;
|
|
88321
88943
|
exports.isAllowedAgentDashboardReturnTo = isAllowedAgentDashboardReturnTo;
|
|
88322
88944
|
exports.isProcessVerifiedMeshPeer = isProcessVerifiedMeshPeer;
|
|
88945
|
+
exports.isValidAgentNodeId = isValidAgentNodeId;
|
|
88946
|
+
exports.issueMeshLeafForNode = issueMeshLeafForNode;
|
|
88947
|
+
exports.issueMeshLeafFromCsr = issueMeshLeafFromCsr;
|
|
88323
88948
|
exports.keyFingerprintShort = keyFingerprintShort;
|
|
88949
|
+
exports.loadMeshCa = loadMeshCa;
|
|
88324
88950
|
exports.mintAgentUiUnlockTicket = mintAgentUiUnlockTicket;
|
|
88325
88951
|
exports.normalizeIp = normalizeIp;
|
|
88326
88952
|
exports.onProcessNodeRegistered = onProcessNodeRegistered;
|
|
@@ -88332,6 +88958,7 @@ exports.readCidrSetting = readCidrSetting;
|
|
|
88332
88958
|
exports.renewMeshCertificate = renewMeshCertificate;
|
|
88333
88959
|
exports.requireProcessMeshCa = requireProcessMeshCa;
|
|
88334
88960
|
exports.revokeMeshCertificate = revokeMeshCertificate;
|
|
88961
|
+
exports.setProcessMeshCertificateRegistry = setProcessMeshCertificateRegistry;
|
|
88335
88962
|
exports.setProcessMeshPeerTransportLookup = setProcessMeshPeerTransportLookup;
|
|
88336
88963
|
exports.setProcessMeshTlsEnabler = setProcessMeshTlsEnabler;
|
|
88337
88964
|
exports.setProcessMeshTlsListenerState = setProcessMeshTlsListenerState;
|