@camstack/system 1.2.427 → 1.2.429

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.
Files changed (100) hide show
  1. package/dist/agent-https-names-CpUSuP4w.js +120 -0
  2. package/dist/agent-https-names-uetr4OQl.mjs +91 -0
  3. package/dist/auth/auth-audit-log.d.ts +1 -1
  4. package/dist/auth/device-ca-store.d.ts +57 -0
  5. package/dist/auth/device-ca.d.ts +75 -0
  6. package/dist/auth/device-credential-manager.d.ts +154 -0
  7. package/dist/auth/login-session-manager.d.ts +14 -2
  8. package/dist/auth/user-management-internal.d.ts +10 -1
  9. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  10. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  11. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  12. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  13. package/dist/builtins/alerts/alerts.addon.js +1 -1
  14. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  15. package/dist/builtins/autotrack/index.js +1 -1
  16. package/dist/builtins/autotrack/index.mjs +1 -1
  17. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
  18. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
  19. package/dist/builtins/calendars/calendars.addon.js +1 -1
  20. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  21. package/dist/builtins/camera-grid/index.js +1 -1
  22. package/dist/builtins/camera-grid/index.mjs +1 -1
  23. package/dist/builtins/composer/composer.addon.js +2 -2
  24. package/dist/builtins/composer/composer.addon.mjs +2 -2
  25. package/dist/builtins/console-logging/index.js +1 -1
  26. package/dist/builtins/console-logging/index.mjs +1 -1
  27. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  28. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  29. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  30. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  31. package/dist/builtins/hub-forwarder/index.js +1 -1
  32. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  33. package/dist/builtins/keychain/index.js +10 -6
  34. package/dist/builtins/keychain/index.mjs +8 -4
  35. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  36. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  37. package/dist/builtins/local-auth/local-auth.addon.d.ts +6 -0
  38. package/dist/builtins/local-auth/local-auth.addon.js +955 -47
  39. package/dist/builtins/local-auth/local-auth.addon.mjs +946 -41
  40. package/dist/builtins/local-network/local-network.addon.js +2 -2
  41. package/dist/builtins/local-network/local-network.addon.mjs +2 -2
  42. package/dist/builtins/loki-logging/index.js +1 -1
  43. package/dist/builtins/loki-logging/index.mjs +1 -1
  44. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  45. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  46. package/dist/builtins/platform-probe/index.js +1 -1
  47. package/dist/builtins/platform-probe/index.mjs +1 -1
  48. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  49. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  50. package/dist/builtins/snapshot/index.js +1 -1
  51. package/dist/builtins/snapshot/index.mjs +1 -1
  52. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  53. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  54. package/dist/builtins/sqlite-storage/index.js +1 -1
  55. package/dist/builtins/sqlite-storage/index.mjs +1 -1
  56. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +3 -3
  57. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +3 -3
  58. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  59. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  60. package/dist/builtins/system-config/system-config.addon.d.ts +18 -0
  61. package/dist/builtins/system-config/system-config.addon.js +834 -18
  62. package/dist/builtins/system-config/system-config.addon.mjs +819 -19
  63. package/dist/builtins/winston-logging/index.js +1 -1
  64. package/dist/builtins/winston-logging/index.mjs +1 -1
  65. package/dist/{composition-sources-DvCGZEUI.js → composition-sources-C2VNX6tF.js} +1 -1
  66. package/dist/{composition-sources-BQNxXQZG.mjs → composition-sources-SYtYuAgh.mjs} +1 -1
  67. package/dist/{constants-CR4If_lP.mjs → constants-CKuWoWvc.mjs} +1 -1
  68. package/dist/{constants-CCSJGnjs.js → constants-RI-hGP5S.js} +1 -1
  69. package/dist/device-ca-BWfdDB3k.js +462 -0
  70. package/dist/device-ca-R7DKKZL6.mjs +355 -0
  71. package/dist/{dist-BQG3QFg7.js → dist-Bgy_AmwX.js} +554 -0
  72. package/dist/{dist-DHSJV4Qc.mjs → dist-CSSfTM18.mjs} +507 -1
  73. package/dist/file-host-worker.js +1 -1
  74. package/dist/file-host-worker.mjs +1 -1
  75. package/dist/index.d.ts +4 -1
  76. package/dist/index.js +59 -12
  77. package/dist/index.mjs +10 -9
  78. package/dist/kernel/index.d.ts +2 -2
  79. package/dist/kernel/moleculer/mesh-mtls/index.d.ts +4 -1
  80. package/dist/kernel/moleculer/mesh-mtls/mesh-certificate-registry.d.ts +91 -0
  81. package/dist/kernel/moleculer/mesh-mtls/mesh-enroll.d.ts +4 -0
  82. package/dist/kernel/moleculer/mesh-mtls/mesh-peer-transport.d.ts +23 -0
  83. package/dist/kernel/moleculer/mesh-mtls/mesh-renewal.d.ts +126 -0
  84. package/dist/kernel/moleculer/mesh-mtls/mesh-tls-gate.d.ts +40 -0
  85. package/dist/kernel/moleculer/mesh-mtls/tls-tcp-transporter.d.ts +15 -0
  86. package/dist/{restore-archive-DH1Pxq_1.mjs → restore-archive-AIwMHO5_.mjs} +1 -1
  87. package/dist/{restore-archive-Dccm8lkA.js → restore-archive-BYUEMsog.js} +1 -1
  88. package/dist/{retired-settings-keys-BfGaSeQI.js → retired-settings-keys-BTPkRLTS.js} +1 -1
  89. package/dist/{retired-settings-keys-CPz7zpza.mjs → retired-settings-keys-H0rYXT27.mjs} +1 -1
  90. package/dist/tls/agent-https-names.d.ts +27 -0
  91. package/dist/tls/agent-https.d.ts +111 -0
  92. package/dist/tls/cert-issue.d.ts +7 -0
  93. package/dist/tls/index.d.ts +3 -0
  94. package/dist/{tls-BgImdxCO.js → tls-C5IYg3V5.js} +406 -0
  95. package/dist/{tls-CaOLZ09W.mjs → tls-CpcGkeJV.mjs} +314 -4
  96. package/dist/{wal-maintenance-DItkuOvu.js → wal-maintenance-BSGsxKW7.js} +0 -0
  97. package/dist/{wal-maintenance-Drqhcjit.mjs → wal-maintenance-DW0FfGV7.mjs} +0 -0
  98. package/package.json +4 -1
  99. package/dist/jwt-signing-key-BZm055IP.js +0 -174
  100. package/dist/jwt-signing-key-DL-qk1SM.mjs +0 -127
@@ -1,8 +1,9 @@
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, Xr as secretTreeFromSchema, cr as errMsg, ei as transformSecretWrite, lr as BaseAddon, zr as makeSecretMarker } from "../../dist-DHSJV4Qc.mjs";
2
+ import { Br as hydrateSchema, Fr as defineCustomActions, Gr as isSecretMarker, Pr as customAction, Wr as isSecretClear, _r as BaseAddon, ci as transformSecretWrite, gr as errMsg, ii as secretTreeFromSchema, qr as makeSecretMarker } from "../../dist-CSSfTM18.mjs";
3
3
  import { t as require_safe_buffer } from "../../safe-buffer-B1OjL1I9.mjs";
4
- import { a as MESH_CA_KEY_REF, b as setProcessClusterSecretGate, c as MESH_JOIN_TOKEN_MAX_TTL_SEC, d as CLUSTER_SECRET_REF, i as MESH_CA_FIELD, l as SERVER_AUTH_OID, n as DEFAULT_HUB_TLS_TCP_PORT, o as MESH_CA_SETTINGS_KEY, r as MESH_CA_COMMON_NAME, s as MESH_CA_VALIDITY_DAYS, t as CLIENT_AUTH_OID, v as hashClusterSecret } from "../../constants-CR4If_lP.mjs";
5
4
  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-CAjSsrkk.mjs";
5
+ import { a as setProcessAgentHttpsNames, n as AGENT_HTTPS_NAMES_COLUMNS, r as AgentHttpsNames, t as AGENT_HTTPS_NAMES_COLLECTION } from "../../agent-https-names-uetr4OQl.mjs";
6
+ import { a as MESH_CA_KEY_REF, b as setProcessClusterSecretGate, c as MESH_JOIN_TOKEN_MAX_TTL_SEC, d as CLUSTER_SECRET_REF, i as MESH_CA_FIELD, l as SERVER_AUTH_OID, n as DEFAULT_HUB_TLS_TCP_PORT, o as MESH_CA_SETTINGS_KEY, r as MESH_CA_COMMON_NAME, s as MESH_CA_VALIDITY_DAYS, t as CLIENT_AUTH_OID, v as hashClusterSecret } from "../../constants-CKuWoWvc.mjs";
6
7
  import { t as require_src } from "../../src-hG4grMU4.mjs";
7
8
  import { z } from "zod";
8
9
  import { X509Certificate, createHash, createHmac, randomBytes, timingSafeEqual, webcrypto } from "node:crypto";
@@ -416,7 +417,7 @@ async function exportMeshPrivateKeyPem(key) {
416
417
  }
417
418
  //#endregion
418
419
  //#region src/kernel/moleculer/mesh-mtls/mesh-ca-keeper.ts
419
- var SLOT$2 = Symbol.for("camstack.mesh.ca");
420
+ var SLOT$3 = Symbol.for("camstack.mesh.ca");
420
421
  var NOT_LOADED = {
421
422
  state: "unavailable",
422
423
  reason: "not-loaded"
@@ -428,10 +429,10 @@ function isState(value) {
428
429
  return state === "unavailable" && typeof Reflect.get(value, "reason") === "string";
429
430
  }
430
431
  function setProcessMeshCa(state) {
431
- Reflect.set(globalThis, SLOT$2, state);
432
+ Reflect.set(globalThis, SLOT$3, state);
432
433
  }
433
434
  function getProcessMeshCaState() {
434
- const value = Reflect.get(globalThis, SLOT$2);
435
+ const value = Reflect.get(globalThis, SLOT$3);
435
436
  return isState(value) ? value : NOT_LOADED;
436
437
  }
437
438
  function requireProcessMeshCa() {
@@ -573,10 +574,10 @@ async function loadOrGenerateMeshCa(deps) {
573
574
  var MeshCrl = class {
574
575
  serials = /* @__PURE__ */ new Set();
575
576
  revoke(serialNumber) {
576
- this.serials.add(normalizeSerial(serialNumber));
577
+ this.serials.add(normalizeSerial$1(serialNumber));
577
578
  }
578
579
  isRevoked(serialNumber) {
579
- return this.serials.has(normalizeSerial(serialNumber));
580
+ return this.serials.has(normalizeSerial$1(serialNumber));
580
581
  }
581
582
  list() {
582
583
  return [...this.serials];
@@ -584,21 +585,650 @@ var MeshCrl = class {
584
585
  /** Restore from persisted serials (system-settings). */
585
586
  replaceAll(serials) {
586
587
  this.serials.clear();
587
- for (const s of serials) this.serials.add(normalizeSerial(s));
588
+ for (const s of serials) this.serials.add(normalizeSerial$1(s));
588
589
  }
589
590
  };
590
- function normalizeSerial(serial) {
591
+ function normalizeSerial$1(serial) {
591
592
  return serial.replace(/^0x/i, "").replace(/:/g, "").toLowerCase();
592
593
  }
593
- var SLOT$1 = Symbol.for("camstack.mesh.crl");
594
+ var SLOT$2 = Symbol.for("camstack.mesh.crl");
594
595
  function processMeshCrl() {
595
- const existing = Reflect.get(globalThis, SLOT$1);
596
+ const existing = Reflect.get(globalThis, SLOT$2);
596
597
  if (existing instanceof MeshCrl) return existing;
597
598
  const crl = new MeshCrl();
598
- Reflect.set(globalThis, SLOT$1, crl);
599
+ Reflect.set(globalThis, SLOT$2, crl);
599
600
  return crl;
600
601
  }
601
602
  //#endregion
603
+ //#region src/kernel/moleculer/mesh-mtls/mesh-certificate-registry.ts
604
+ /**
605
+ * The hub's record of every mesh leaf (D856): which certificate each node holds,
606
+ * when it expires, and which serials are revoked.
607
+ *
608
+ * Three ways a row appears:
609
+ * - `enroll` / `renewal` / `boot` - the hub issued it and wrote it down at the
610
+ * moment of issuing (the authoritative record);
611
+ * - `observed` - a node enrolled before this record existed (or by a path that
612
+ * did not write it) presented this leaf on its LIVE mutual-TLS connection.
613
+ *
614
+ * Revoked serials are persisted here and replayed into the in-memory CRL at boot,
615
+ * so a revocation survives a hub restart. Hub only: agents persist none of it.
616
+ */
617
+ /**
618
+ * @durable class=config owner=system-config
619
+ * write="the hub records every mesh leaf it issues (enroll, renewal, boot) and every
620
+ * leaf a connected node is observed presenting; a revocation stamps revokedAt"
621
+ * retention="a revoked or expired row is kept 90 days past its end, then reaped; a
622
+ * live row is never deleted. Forgetting a node does not delete its rows."
623
+ */
624
+ var MESH_NODE_CERTIFICATES_COLLECTION = "mesh_node_certificates";
625
+ var MESH_NODE_CERTIFICATES_COLUMNS = [
626
+ {
627
+ name: "id",
628
+ type: "TEXT",
629
+ primaryKey: true,
630
+ notNull: true
631
+ },
632
+ {
633
+ name: "nodeId",
634
+ type: "TEXT",
635
+ notNull: true
636
+ },
637
+ {
638
+ name: "serial",
639
+ type: "TEXT",
640
+ notNull: true
641
+ },
642
+ {
643
+ name: "fingerprint",
644
+ type: "TEXT",
645
+ notNull: true
646
+ },
647
+ {
648
+ name: "notBefore",
649
+ type: "INTEGER",
650
+ notNull: true
651
+ },
652
+ {
653
+ name: "notAfter",
654
+ type: "INTEGER",
655
+ notNull: true
656
+ },
657
+ {
658
+ name: "issuedAt",
659
+ type: "INTEGER",
660
+ notNull: true
661
+ },
662
+ {
663
+ name: "issuedVia",
664
+ type: "TEXT",
665
+ notNull: true
666
+ },
667
+ {
668
+ name: "revokedAt",
669
+ type: "INTEGER"
670
+ },
671
+ {
672
+ name: "revokedReason",
673
+ type: "TEXT"
674
+ }
675
+ ];
676
+ var RETENTION_MS = 90 * 864e5;
677
+ /**
678
+ * A leaf the hub signed for a renewal that did not complete. It must not be the node's
679
+ * `current` leaf, but it is NOT on the CRL: the node may still hold it (the swap
680
+ * could have landed without the hub seeing it), and cutting it off would be the
681
+ * outage the hand-over exists to prevent. If the node is later seen presenting it, it
682
+ * is reinstated.
683
+ */
684
+ var RENEWAL_ABANDONED = "renewal-abandoned";
685
+ var VIAS = [
686
+ "enroll",
687
+ "renewal",
688
+ "boot",
689
+ "observed"
690
+ ];
691
+ /** CN, serial, fingerprint and validity of a leaf PEM (or DER). */
692
+ function describeMeshLeaf(cert) {
693
+ const x509 = new X509Certificate(cert);
694
+ return {
695
+ cn: subjectCn(x509.subject) ?? "",
696
+ serial: x509.serialNumber,
697
+ fingerprint: x509.fingerprint256,
698
+ notBefore: Date.parse(x509.validFrom),
699
+ notAfter: Date.parse(x509.validTo)
700
+ };
701
+ }
702
+ function normalizeSerial(serial) {
703
+ return serial.replace(/^0x/i, "").replace(/:/g, "").toLowerCase();
704
+ }
705
+ function parse$2(data) {
706
+ const str = (k) => {
707
+ const v = data[k];
708
+ return typeof v === "string" && v.length > 0 ? v : null;
709
+ };
710
+ const num = (k) => {
711
+ const v = data[k];
712
+ return typeof v === "number" && Number.isFinite(v) ? v : null;
713
+ };
714
+ const nodeId = str("nodeId");
715
+ const serial = str("serial");
716
+ const fingerprint = str("fingerprint");
717
+ const notBefore = num("notBefore");
718
+ const notAfter = num("notAfter");
719
+ const issuedAt = num("issuedAt");
720
+ if (nodeId === null || serial === null || fingerprint === null || notBefore === null || notAfter === null || issuedAt === null) return null;
721
+ return {
722
+ nodeId,
723
+ serial,
724
+ fingerprint,
725
+ notBefore,
726
+ notAfter,
727
+ issuedAt,
728
+ issuedVia: VIAS.find((v) => v === data["issuedVia"]) ?? "observed",
729
+ revokedAt: num("revokedAt"),
730
+ revokedReason: str("revokedReason")
731
+ };
732
+ }
733
+ var MeshCertificateRegistry = class {
734
+ store;
735
+ deps;
736
+ rows = /* @__PURE__ */ new Map();
737
+ constructor(store, deps = {}) {
738
+ this.store = store;
739
+ this.deps = deps;
740
+ }
741
+ now() {
742
+ return (this.deps.now ?? Date.now)();
743
+ }
744
+ async load() {
745
+ const rows = await this.store.query.query({ collection: MESH_NODE_CERTIFICATES_COLLECTION });
746
+ for (const row of rows) {
747
+ const record = parse$2(row.data);
748
+ if (record !== null) this.rows.set(record.fingerprint, record);
749
+ }
750
+ }
751
+ /** Write down a leaf the hub just issued. Idempotent per fingerprint. */
752
+ async recordIssued(nodeId, certPem, via) {
753
+ const leaf = describeMeshLeaf(certPem);
754
+ const existing = this.rows.get(leaf.fingerprint);
755
+ if (existing !== void 0) return existing;
756
+ const record = {
757
+ nodeId,
758
+ serial: leaf.serial,
759
+ fingerprint: leaf.fingerprint,
760
+ notBefore: leaf.notBefore,
761
+ notAfter: leaf.notAfter,
762
+ issuedAt: this.now(),
763
+ issuedVia: via,
764
+ revokedAt: null,
765
+ revokedReason: null
766
+ };
767
+ await this.insert(record);
768
+ this.deps.logger?.info("Mesh certificate recorded", {
769
+ tags: { nodeId },
770
+ meta: {
771
+ via,
772
+ serial: leaf.serial,
773
+ notAfter: new Date(leaf.notAfter).toISOString()
774
+ }
775
+ });
776
+ return record;
777
+ }
778
+ /**
779
+ * A node was seen presenting `leaf` on its live connection. A leaf the hub already
780
+ * knows is left alone; an unknown one is recorded `observed` (a node enrolled
781
+ * before this record existed).
782
+ */
783
+ async recordObserved(nodeId, leaf) {
784
+ const known = this.rows.get(leaf.fingerprint);
785
+ if (known !== void 0) {
786
+ if (known.revokedReason !== "renewal-abandoned") return false;
787
+ const reinstated = {
788
+ ...known,
789
+ revokedAt: null,
790
+ revokedReason: null
791
+ };
792
+ this.rows.set(known.fingerprint, reinstated);
793
+ await this.persist(reinstated);
794
+ this.deps.logger?.warn("An abandoned renewal leaf is in use - reinstated", {
795
+ tags: { nodeId },
796
+ meta: { serial: known.serial }
797
+ });
798
+ return true;
799
+ }
800
+ const record = {
801
+ nodeId,
802
+ serial: leaf.serial,
803
+ fingerprint: leaf.fingerprint,
804
+ notBefore: leaf.notBefore,
805
+ notAfter: leaf.notAfter,
806
+ issuedAt: this.now(),
807
+ issuedVia: "observed",
808
+ revokedAt: null,
809
+ revokedReason: null
810
+ };
811
+ await this.insert(record);
812
+ this.deps.logger?.info("Mesh certificate observed on a live connection and recorded", {
813
+ tags: { nodeId },
814
+ meta: {
815
+ serial: leaf.serial,
816
+ fingerprint: leaf.fingerprint
817
+ }
818
+ });
819
+ return true;
820
+ }
821
+ list(nodeId) {
822
+ return [...this.rows.values()].filter((r) => nodeId === void 0 || r.nodeId === nodeId).toSorted((a, b) => b.issuedAt - a.issuedAt);
823
+ }
824
+ /** The newest non-revoked leaf of a node, or `undefined`. */
825
+ currentFor(nodeId) {
826
+ return this.list(nodeId).find((r) => r.revokedAt === null);
827
+ }
828
+ /** Every non-revoked leaf of a node (a renewal in flight has two). */
829
+ liveFor(nodeId) {
830
+ return this.list(nodeId).filter((r) => r.revokedAt === null);
831
+ }
832
+ /** Stamp a leaf revoked (and mirror it into `crl`). `false` when unknown or already revoked. */
833
+ async revoke(fingerprint, reason, crl) {
834
+ const record = this.rows.get(fingerprint);
835
+ if (record === void 0 || record.revokedAt !== null) return false;
836
+ const revoked = {
837
+ ...record,
838
+ revokedAt: this.now(),
839
+ revokedReason: reason
840
+ };
841
+ this.rows.set(fingerprint, revoked);
842
+ crl?.revoke(record.serial);
843
+ await this.persist(revoked);
844
+ this.deps.logger?.warn("Mesh certificate revoked", {
845
+ tags: { nodeId: record.nodeId },
846
+ meta: {
847
+ serial: record.serial,
848
+ reason
849
+ }
850
+ });
851
+ return true;
852
+ }
853
+ /**
854
+ * Operator revocation of a whole node: EVERY leaf of the node that is not already on the
855
+ * CRL goes on it - the current one, an observed one, a renewal one, and a leaf that was set
856
+ * aside (`renewal-abandoned`: a copy of it may sit on a stolen box, and it must not stay
857
+ * valid just because the hub once set it aside). Returns the serials put on the CRL.
858
+ * A leaf revoked this way is never reinstated by `recordObserved`.
859
+ */
860
+ async revokeAllForNode(nodeId, reason, crl) {
861
+ const serials = [];
862
+ for (const record of this.list(nodeId)) {
863
+ if (record.revokedAt !== null && record.revokedReason !== "renewal-abandoned") continue;
864
+ const revoked = {
865
+ ...record,
866
+ revokedAt: record.revokedAt ?? this.now(),
867
+ revokedReason: reason
868
+ };
869
+ this.rows.set(record.fingerprint, revoked);
870
+ crl.revoke(record.serial);
871
+ await this.persist(revoked);
872
+ serials.push(record.serial);
873
+ }
874
+ if (serials.length > 0) this.deps.logger?.warn("Every mesh certificate of the node revoked", {
875
+ tags: { nodeId },
876
+ meta: {
877
+ serials,
878
+ reason
879
+ }
880
+ });
881
+ return serials;
882
+ }
883
+ /**
884
+ * At boot: a node with TWO live leaves had a renewal in flight when the hub stopped. The
885
+ * newer (renewal) one is set aside - it is not the node's current leaf until the node is
886
+ * seen presenting it, which reinstates it. It is NOT put on the CRL.
887
+ */
888
+ async settleInterruptedRenewals() {
889
+ let settled = 0;
890
+ const byNode = /* @__PURE__ */ new Map();
891
+ for (const record of this.rows.values()) {
892
+ if (record.revokedAt !== null) continue;
893
+ byNode.set(record.nodeId, [...byNode.get(record.nodeId) ?? [], record]);
894
+ }
895
+ for (const records of byNode.values()) {
896
+ if (records.length < 2) continue;
897
+ for (const record of records) if (record.issuedVia === "renewal" && await this.abandon(record.fingerprint)) settled += 1;
898
+ }
899
+ return settled;
900
+ }
901
+ /** Set a never-completed renewal leaf aside (not current, NOT on the CRL). */
902
+ async abandon(fingerprint) {
903
+ const record = this.rows.get(fingerprint);
904
+ if (record === void 0 || record.revokedAt !== null) return false;
905
+ const abandoned = {
906
+ ...record,
907
+ revokedAt: this.now(),
908
+ revokedReason: RENEWAL_ABANDONED
909
+ };
910
+ this.rows.set(fingerprint, abandoned);
911
+ await this.persist(abandoned);
912
+ this.deps.logger?.warn("Renewal leaf set aside (not current, not on the CRL)", {
913
+ tags: { nodeId: record.nodeId },
914
+ meta: { serial: record.serial }
915
+ });
916
+ return true;
917
+ }
918
+ /** Replay every persisted revocation into `crl` (hub boot). Set-aside leaves are not revocations. */
919
+ restoreCrl(crl) {
920
+ let restored = 0;
921
+ for (const record of this.rows.values()) {
922
+ if (record.revokedAt === null || record.revokedReason === "renewal-abandoned") continue;
923
+ crl.revoke(record.serial);
924
+ restored += 1;
925
+ }
926
+ return restored;
927
+ }
928
+ /** Is `serial` listed as revoked here? */
929
+ isRevoked(serial) {
930
+ const wanted = normalizeSerial(serial);
931
+ for (const record of this.rows.values()) if (record.revokedAt !== null && record.revokedReason !== "renewal-abandoned" && normalizeSerial(record.serial) === wanted) return true;
932
+ return false;
933
+ }
934
+ async reap() {
935
+ const cutoff = this.now() - RETENTION_MS;
936
+ const dead = [...this.rows.values()].filter((r) => (r.revokedAt ?? r.notAfter) < cutoff);
937
+ for (const r of dead) {
938
+ this.rows.delete(r.fingerprint);
939
+ await this.store.delete.mutate({
940
+ collection: MESH_NODE_CERTIFICATES_COLLECTION,
941
+ key: r.fingerprint
942
+ });
943
+ }
944
+ return dead.length;
945
+ }
946
+ async insert(record) {
947
+ this.rows.set(record.fingerprint, record);
948
+ await this.store.insert.mutate({
949
+ collection: MESH_NODE_CERTIFICATES_COLLECTION,
950
+ record: {
951
+ id: record.fingerprint,
952
+ data: { ...record }
953
+ }
954
+ });
955
+ }
956
+ async persist(record) {
957
+ try {
958
+ await this.store.update.mutate({
959
+ collection: MESH_NODE_CERTIFICATES_COLLECTION,
960
+ id: record.fingerprint,
961
+ data: { ...record }
962
+ });
963
+ } catch (error) {
964
+ this.deps.logger?.error("Mesh certificate write failed - the change holds until restart", {
965
+ tags: { nodeId: record.nodeId },
966
+ meta: { error: error instanceof Error ? error.message : String(error) }
967
+ });
968
+ }
969
+ }
970
+ };
971
+ var SLOT$1 = Symbol.for("camstack.mesh.certificate-registry");
972
+ function setProcessMeshCertificateRegistry(registry) {
973
+ if (registry === null) Reflect.deleteProperty(globalThis, SLOT$1);
974
+ else Reflect.set(globalThis, SLOT$1, registry);
975
+ }
976
+ function getProcessMeshCertificateRegistry() {
977
+ const value = Reflect.get(globalThis, SLOT$1);
978
+ return value !== null && typeof value === "object" && typeof Reflect.get(value, "recordIssued") === "function" ? value : null;
979
+ }
980
+ //#endregion
981
+ //#region src/kernel/moleculer/mesh-mtls/mesh-tls-gate.ts
982
+ var STATE_SLOT = Symbol.for("camstack.mesh.tls-listener-state");
983
+ function setProcessMeshTlsListenerState(state) {
984
+ if (state === null) Reflect.deleteProperty(globalThis, STATE_SLOT);
985
+ else Reflect.set(globalThis, STATE_SLOT, state);
986
+ }
987
+ /** `null` before the gate has run at all (no mesh CA yet). */
988
+ function getProcessMeshTlsListenerState() {
989
+ const value = Reflect.get(globalThis, STATE_SLOT);
990
+ if (value === null || typeof value !== "object") return null;
991
+ const state = Reflect.get(value, "state");
992
+ return state === "open" || state === "closed" ? value : null;
993
+ }
994
+ var DEFAULT_RETRY_MS = 15e3;
995
+ /**
996
+ * Nothing in `run` can reject: a load or an open that throws is reported and retried, so a
997
+ * failure can never become an unhandled rejection that leaves the listener closed in silence.
998
+ */
999
+ function openMeshTlsWhenRevocationsLoaded(deps) {
1000
+ let attempt = 0;
1001
+ let opened = false;
1002
+ const now = deps.now ?? Date.now;
1003
+ let closedSince = null;
1004
+ const closed = (reason) => {
1005
+ closedSince ??= now();
1006
+ deps.onState?.({
1007
+ state: "closed",
1008
+ reason,
1009
+ since: closedSince
1010
+ });
1011
+ };
1012
+ const run = async () => {
1013
+ if (opened) return true;
1014
+ attempt += 1;
1015
+ let registry = null;
1016
+ try {
1017
+ registry = await deps.loadRegistry();
1018
+ } catch (error) {
1019
+ deps.onError?.(error);
1020
+ }
1021
+ if (registry === null) {
1022
+ closed("the revocation registry has not loaded - agents are refused");
1023
+ deps.onWaiting?.(attempt);
1024
+ deps.retry(() => void run(), deps.retryMs ?? DEFAULT_RETRY_MS);
1025
+ return false;
1026
+ }
1027
+ try {
1028
+ await deps.open(registry);
1029
+ } catch (error) {
1030
+ deps.onError?.(error);
1031
+ closed("the listener failed to open - retrying");
1032
+ deps.retry(() => void run(), deps.retryMs ?? DEFAULT_RETRY_MS);
1033
+ return false;
1034
+ }
1035
+ opened = true;
1036
+ deps.onState?.({ state: "open" });
1037
+ return true;
1038
+ };
1039
+ return run();
1040
+ }
1041
+ //#endregion
1042
+ //#region src/kernel/moleculer/mesh-mtls/mesh-renewal.ts
1043
+ var DEFAULT_RECONNECT_TIMEOUT_MS = 45e3;
1044
+ var DEFAULT_CONFIRM_RETRY_MS = 500;
1045
+ function defaultSetTimer(fn, ms) {
1046
+ const timer = setTimeout(fn, ms);
1047
+ timer.unref?.();
1048
+ return { cancel: () => clearTimeout(timer) };
1049
+ }
1050
+ function defaultSleep(ms) {
1051
+ return new Promise((resolve) => setTimeout(resolve, ms));
1052
+ }
1053
+ function message$1(error) {
1054
+ return error instanceof Error ? error.message : String(error);
1055
+ }
1056
+ async function renewMeshCertificate(deps) {
1057
+ const { nodeId, log } = deps;
1058
+ const tags = { nodeId };
1059
+ const sleep = deps.sleep ?? defaultSleep;
1060
+ const confirmRetryMs = deps.confirmRetryMs ?? DEFAULT_CONFIRM_RETRY_MS;
1061
+ const setTimer = deps.setTimer ?? defaultSetTimer;
1062
+ const timeoutMs = deps.reconnectTimeoutMs ?? DEFAULT_RECONNECT_TIMEOUT_MS;
1063
+ const now = deps.now ?? Date.now;
1064
+ const live = deps.peers.peerLeaf(nodeId);
1065
+ const previous = live ?? deps.registry.currentFor(nodeId);
1066
+ if (live !== void 0) await deps.registry.recordObserved(nodeId, {
1067
+ cn: nodeId,
1068
+ serial: live.serial,
1069
+ fingerprint: live.fingerprint,
1070
+ notBefore: live.notBefore,
1071
+ notAfter: live.notAfter
1072
+ });
1073
+ log.info("Mesh renewal: starting", {
1074
+ tags,
1075
+ meta: {
1076
+ currentSerial: previous?.serial ?? null,
1077
+ connected: live !== void 0
1078
+ }
1079
+ });
1080
+ const fail = (stage, reason, keptPreviousLeaf) => {
1081
+ log.error("Mesh renewal: failed", {
1082
+ tags,
1083
+ meta: {
1084
+ stage,
1085
+ reason,
1086
+ keptPreviousLeaf
1087
+ }
1088
+ });
1089
+ return {
1090
+ ok: false,
1091
+ stage,
1092
+ reason,
1093
+ keptPreviousLeaf
1094
+ };
1095
+ };
1096
+ let csrPem;
1097
+ try {
1098
+ csrPem = (await deps.client.prepareMeshRenewal()).csrPem;
1099
+ log.info("Mesh renewal: the node generated a new key and sent its CSR", { tags });
1100
+ } catch (error) {
1101
+ return fail("prepare", message$1(error), true);
1102
+ }
1103
+ let issued;
1104
+ try {
1105
+ issued = await issueMeshLeafFromCsr(deps.ca, csrPem, { expectedNodeId: nodeId });
1106
+ await deps.registry.recordIssued(nodeId, issued.certPem, "renewal");
1107
+ log.info("Mesh renewal: new leaf signed and recorded", {
1108
+ tags,
1109
+ meta: {
1110
+ serial: issued.serialNumber,
1111
+ notAfter: issued.notAfter.toISOString()
1112
+ }
1113
+ });
1114
+ } catch (error) {
1115
+ return fail("issue", message$1(error), true);
1116
+ }
1117
+ const newLeaf = describeMeshLeaf(issued.certPem);
1118
+ let resolveRegistered = () => void 0;
1119
+ const registeredPromise = new Promise((resolve) => {
1120
+ resolveRegistered = resolve;
1121
+ });
1122
+ const unsubscribe = deps.registrations.subscribe((id) => {
1123
+ if (id === nodeId && deps.peers.peerLeaf(nodeId)?.fingerprint === newLeaf.fingerprint) resolveRegistered(true);
1124
+ });
1125
+ const deadlineTimer = setTimer(() => resolveRegistered(false), timeoutMs);
1126
+ registeredPromise.then(() => deadlineTimer.cancel());
1127
+ try {
1128
+ await deps.client.installMeshLeaf({
1129
+ certPem: issued.certPem,
1130
+ caCertPem: deps.ca.certPem,
1131
+ confirmWithinMs: timeoutMs + 3e4
1132
+ });
1133
+ log.info("Mesh renewal: the node installed the new leaf and is reconnecting", { tags });
1134
+ } catch (error) {
1135
+ unsubscribe();
1136
+ deadlineTimer.cancel();
1137
+ await deps.client.rollbackMeshLeaf().catch(() => void 0);
1138
+ await deps.registry.abandon(newLeaf.fingerprint);
1139
+ return fail("install", message$1(error), true);
1140
+ }
1141
+ const deadline = now() + timeoutMs;
1142
+ const registered = await registeredPromise;
1143
+ unsubscribe();
1144
+ if (!registered) {
1145
+ const back = await deps.client.rollbackMeshLeaf().catch(() => ({ rolledBack: false }));
1146
+ log.warn("Mesh renewal: the node was not back and registered with the new leaf in time", {
1147
+ tags,
1148
+ meta: {
1149
+ timeoutMs,
1150
+ rolledBack: back.rolledBack
1151
+ }
1152
+ });
1153
+ await deps.registry.abandon(newLeaf.fingerprint);
1154
+ return fail("reconnect", `the node was not back and registered with the new leaf within ${Math.round(timeoutMs / 1e3)} s; the previous leaf was kept and is still valid`, true);
1155
+ }
1156
+ log.info("Mesh renewal: the node registered presenting the new leaf", { tags });
1157
+ let confirmError = "the node did not confirm the new leaf";
1158
+ let confirmedByNode = false;
1159
+ for (;;) {
1160
+ try {
1161
+ if ((await deps.client.confirmMeshLeaf({ fingerprintSha256: newLeaf.fingerprint })).confirmed) {
1162
+ confirmedByNode = true;
1163
+ break;
1164
+ }
1165
+ confirmError = "the node did not confirm the new leaf";
1166
+ } catch (error) {
1167
+ confirmError = message$1(error);
1168
+ log.warn("Mesh renewal: confirm not accepted yet", {
1169
+ tags,
1170
+ meta: { error: confirmError }
1171
+ });
1172
+ }
1173
+ if (now() >= deadline) break;
1174
+ await sleep(confirmRetryMs);
1175
+ }
1176
+ if (!confirmedByNode) return fail("confirm", confirmError, false);
1177
+ if (previous !== void 0 && previous.serial !== newLeaf.serial) {
1178
+ await deps.registry.revoke(previous.fingerprint, "replaced-by-renewal", deps.crl);
1179
+ deps.crl.revoke(previous.serial);
1180
+ log.info("Mesh renewal: the previous leaf is revoked", {
1181
+ tags,
1182
+ meta: { oldSerial: previous.serial }
1183
+ });
1184
+ }
1185
+ log.info("Mesh renewal: done", {
1186
+ tags,
1187
+ meta: {
1188
+ newSerial: newLeaf.serial,
1189
+ notAfter: new Date(newLeaf.notAfter).toISOString()
1190
+ }
1191
+ });
1192
+ return {
1193
+ ok: true,
1194
+ oldSerial: previous?.serial ?? null,
1195
+ newSerial: newLeaf.serial,
1196
+ newNotAfter: newLeaf.notAfter
1197
+ };
1198
+ }
1199
+ /**
1200
+ * Revoke every live mesh leaf of `nodeId` and cut its connection. The node cannot
1201
+ * reconnect: it must re-join with a new join token. The node is NOT forgotten
1202
+ * (that stays `clusterNodes.forgetNode`).
1203
+ */
1204
+ async function revokeMeshCertificate(deps) {
1205
+ const { nodeId, log } = deps;
1206
+ const tags = { nodeId };
1207
+ const live = deps.peers.peerLeaf(nodeId);
1208
+ if (live !== void 0) await deps.registry.recordObserved(nodeId, {
1209
+ cn: nodeId,
1210
+ serial: live.serial,
1211
+ fingerprint: live.fingerprint,
1212
+ notBefore: live.notBefore,
1213
+ notAfter: live.notAfter
1214
+ });
1215
+ const serials = [...await deps.registry.revokeAllForNode(nodeId, "revoked-by-operator", deps.crl)];
1216
+ const disconnected = deps.peers.dropPeer(nodeId);
1217
+ log.warn("Mesh certificate revoked and the node disconnected", {
1218
+ tags,
1219
+ meta: {
1220
+ serials,
1221
+ disconnected
1222
+ }
1223
+ });
1224
+ return {
1225
+ nodeId,
1226
+ revokedSerials: serials,
1227
+ disconnected,
1228
+ next: "The node can no longer connect. Re-join it with a new join token (System -> Cluster -> Add agent); to remove it from the cluster use Forget node."
1229
+ };
1230
+ }
1231
+ //#endregion
602
1232
  //#region src/kernel/moleculer/mesh-mtls/mesh-peer-transport.ts
603
1233
  var LOOKUP_SLOT = Symbol.for("camstack.cluster.mesh-peer-transport-lookup");
604
1234
  /** Published by the hub broker owner; cleared on shutdown. */
@@ -617,6 +1247,36 @@ function getProcessMeshPeerTransportLookup() {
617
1247
  function getProcessNodeMeshTransport(nodeId) {
618
1248
  return getProcessMeshPeerTransportLookup()?.transportOf(nodeId);
619
1249
  }
1250
+ /** The leaf `nodeId` presented on its live connection, or `undefined`. */
1251
+ function getProcessPeerLeaf(nodeId) {
1252
+ return getProcessMeshPeerTransportLookup()?.peerLeaf?.(nodeId);
1253
+ }
1254
+ /** Disconnect `nodeId` (closes its mutual-TLS sockets). `0` when none were open. */
1255
+ function dropProcessPeer(nodeId) {
1256
+ return getProcessMeshPeerTransportLookup()?.dropPeer?.(nodeId) ?? 0;
1257
+ }
1258
+ var REGISTRATION_SLOT = Symbol.for("camstack.cluster.node-registration-listeners");
1259
+ function registrationListeners() {
1260
+ const existing = Reflect.get(globalThis, REGISTRATION_SLOT);
1261
+ if (existing instanceof Set) return existing;
1262
+ const created = /* @__PURE__ */ new Set();
1263
+ Reflect.set(globalThis, REGISTRATION_SLOT, created);
1264
+ return created;
1265
+ }
1266
+ /** Subscribe to "node X finished registering". Returns the unsubscribe. */
1267
+ function onProcessNodeRegistered(listener) {
1268
+ const listeners = registrationListeners();
1269
+ listeners.add(listener);
1270
+ return () => {
1271
+ listeners.delete(listener);
1272
+ };
1273
+ }
1274
+ /** Called by the hub when a node's `registerNode` has been applied. */
1275
+ function emitProcessNodeRegistered(nodeId) {
1276
+ for (const listener of [...registrationListeners()]) try {
1277
+ listener(nodeId);
1278
+ } catch {}
1279
+ }
620
1280
  //#endregion
621
1281
  //#region ../../node_modules/jose/dist/webapi/lib/buffer_utils.js
622
1282
  var encoder = new TextEncoder(), decoder = new TextDecoder(), strictDecoder = new TextDecoder("utf-8", { fatal: !0 });
@@ -1466,6 +2126,11 @@ async function enrollMeshNode(body, deps) {
1466
2126
  status: 400
1467
2127
  };
1468
2128
  }
2129
+ try {
2130
+ await deps.onIssued?.(leaf.commonName, leaf.certPem);
2131
+ } catch (err) {
2132
+ deps.onRecordFailed?.(leaf.commonName, err);
2133
+ }
1469
2134
  return {
1470
2135
  ok: true,
1471
2136
  cert: leaf.certPem,
@@ -86783,6 +87448,23 @@ function peekSender(transporter, type, message) {
86783
87448
  return null;
86784
87449
  }
86785
87450
  }
87451
+ function peerLeafOf(socket) {
87452
+ const cert = socket.getPeerCertificate(true);
87453
+ if (cert == null || typeof cert !== "object" || !("raw" in cert) || !Buffer.isBuffer(cert.raw)) return null;
87454
+ try {
87455
+ const x509 = new X509Certificate(cert.raw);
87456
+ const remote = socket.remoteAddress;
87457
+ return {
87458
+ ...remote !== void 0 ? { remoteAddress: remote.replace(/^::ffff:/, "") } : {},
87459
+ serial: x509.serialNumber,
87460
+ fingerprint: x509.fingerprint256,
87461
+ notBefore: Date.parse(x509.validFrom),
87462
+ notAfter: Date.parse(x509.validTo)
87463
+ };
87464
+ } catch {
87465
+ return null;
87466
+ }
87467
+ }
86786
87468
  function peerIdentity(socket) {
86787
87469
  const cert = socket.getPeerCertificate(true);
86788
87470
  if (cert == null || typeof cert !== "object" || !("raw" in cert) || !cert.raw) return null;
@@ -86810,6 +87492,8 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
86810
87492
  tlsSockets = /* @__PURE__ */ new Set();
86811
87493
  /** Node id (certificate CN) → serial of every peer that completed a mutual-TLS handshake. */
86812
87494
  tlsPeers = /* @__PURE__ */ new Map();
87495
+ /** Node id → the leaf it presented at its last handshake (hub inventory, D856). */
87496
+ peerLeaves = /* @__PURE__ */ new Map();
86813
87497
  clearDisabled = false;
86814
87498
  hubTlsOnly;
86815
87499
  hubOnlyPeers;
@@ -86923,6 +87607,7 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
86923
87607
  tlsSock.meshPeerCn = identity.cn;
86924
87608
  tlsSock.meshPeerSerial = identity.serial;
86925
87609
  self.tlsPeers.set(identity.cn, identity.serial);
87610
+ self.rememberPeerLeaf(identity.cn, tlsSock);
86926
87611
  self.tlsSockets.add(tlsSock);
86927
87612
  tlsSock.on("close", () => {
86928
87613
  self.tlsSockets.delete(tlsSock);
@@ -86975,6 +87660,25 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
86975
87660
  forgetPeer(nodeId) {
86976
87661
  for (const socket of this.tlsSockets) if (socket.meshPeerCn === nodeId && !socket.destroyed) return;
86977
87662
  this.tlsPeers.delete(nodeId);
87663
+ this.peerLeaves.delete(nodeId);
87664
+ }
87665
+ rememberPeerLeaf(nodeId, socket) {
87666
+ const leaf = peerLeafOf(socket);
87667
+ if (leaf !== null) this.peerLeaves.set(nodeId, leaf);
87668
+ }
87669
+ /** The leaf `nodeId` presented at its last mutual-TLS handshake. */
87670
+ peerLeaf(nodeId) {
87671
+ return this.peerLeaves.get(nodeId);
87672
+ }
87673
+ /** Close every live mutual-TLS socket of `nodeId` (revocation, renewal). Returns how many. */
87674
+ dropPeer(nodeId) {
87675
+ let closed = 0;
87676
+ for (const socket of this.tlsSockets) {
87677
+ if (socket.meshPeerCn !== nodeId || socket.destroyed) continue;
87678
+ socket.destroy();
87679
+ closed += 1;
87680
+ }
87681
+ return closed;
86978
87682
  }
86979
87683
  /** True once the mutual-TLS listener is bound. */
86980
87684
  isTlsListening() {
@@ -87029,6 +87733,7 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
87029
87733
  } catch {}
87030
87734
  this.tlsSockets.clear();
87031
87735
  this.tlsPeers.clear();
87736
+ this.peerLeaves.clear();
87032
87737
  return super.disconnect();
87033
87738
  }
87034
87739
  /**
@@ -87157,6 +87862,7 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
87157
87862
  socket.meshPeerSerial = identity.serial;
87158
87863
  this.tlsSockets.add(socket);
87159
87864
  this.tlsPeers.set(identity.cn, identity.serial);
87865
+ this.rememberPeerLeaf(identity.cn, socket);
87160
87866
  const reader = this.reader;
87161
87867
  socket.on("close", () => {
87162
87868
  this.tlsSockets.delete(socket);
@@ -87377,6 +88083,8 @@ var systemConfigMeshActions = defineCustomActions({
87377
88083
  });
87378
88084
  //#endregion
87379
88085
  //#region src/builtins/system-config/system-config.addon.ts
88086
+ /** How often superseded and expired mesh certificate rows are reaped. */
88087
+ var MESH_REAP_MS = 24 * 36e5;
87380
88088
  /**
87381
88089
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
87382
88090
  *
@@ -87454,6 +88162,10 @@ var SystemConfigAddon = class extends BaseAddon {
87454
88162
  id = "system-config";
87455
88163
  clusterSecretKeeper = null;
87456
88164
  meshCaKeeper = null;
88165
+ meshReapTimer = null;
88166
+ meshGateTimers = /* @__PURE__ */ new Set();
88167
+ stoppedMesh = false;
88168
+ loggedNoSettingsStore = false;
87457
88169
  constructor() {
87458
88170
  super({});
87459
88171
  }
@@ -87493,19 +88205,107 @@ var SystemConfigAddon = class extends BaseAddon {
87493
88205
  }
87494
88206
  async onShutdown() {
87495
88207
  setProcessMeshEnforcer(null);
88208
+ setProcessMeshCertificateRegistry(null);
88209
+ setProcessMeshTlsListenerState(null);
88210
+ setProcessAgentHttpsNames(null);
88211
+ this.stoppedMesh = true;
88212
+ if (this.meshReapTimer !== null) clearInterval(this.meshReapTimer);
88213
+ this.meshReapTimer = null;
88214
+ for (const timer of this.meshGateTimers) clearTimeout(timer);
88215
+ this.meshGateTimers.clear();
87496
88216
  this.meshCaKeeper?.stop();
87497
88217
  this.meshCaKeeper = null;
87498
88218
  this.clusterSecretKeeper?.stop();
87499
88219
  this.clusterSecretKeeper = null;
87500
88220
  }
88221
+ /**
88222
+ * The hub's record of mesh leaves (D856): declared and loaded BEFORE the TLS
88223
+ * listener opens, so persisted revocations are in the CRL before any node can
88224
+ * connect. Best effort: a hub whose settings store is not up still serves the
88225
+ * mesh (the registry then fills from the leaves nodes present).
88226
+ */
88227
+ async initMeshCertificateRegistry() {
88228
+ const store = this.ctx.api?.settingsStore;
88229
+ if (!store) {
88230
+ if (!this.loggedNoSettingsStore) {
88231
+ this.loggedNoSettingsStore = true;
88232
+ this.ctx.logger.error("No settings store in this process: the revocation registry cannot load, so the mesh TLS listener stays CLOSED and every agent is refused (shown on Identity -> Certificates)");
88233
+ }
88234
+ return null;
88235
+ }
88236
+ this.loadAgentHttpsNames(store);
88237
+ try {
88238
+ await store.declareCollection.mutate({
88239
+ collection: MESH_NODE_CERTIFICATES_COLLECTION,
88240
+ columns: MESH_NODE_CERTIFICATES_COLUMNS,
88241
+ indexes: [{
88242
+ name: "idx_mesh_node_certificates_node",
88243
+ columns: ["nodeId"]
88244
+ }]
88245
+ });
88246
+ const registry = new MeshCertificateRegistry(store, { logger: this.ctx.logger });
88247
+ await registry.load();
88248
+ const restored = registry.restoreCrl(processMeshCrl());
88249
+ const settled = await registry.settleInterruptedRenewals();
88250
+ if (settled > 0) this.ctx.logger.warn("A mesh renewal was interrupted by a hub restart - its new leaf was set aside", { meta: { settled } });
88251
+ setProcessMeshCertificateRegistry(registry);
88252
+ if (restored > 0) this.ctx.logger.info("Mesh revocations restored into the CRL", { meta: { restored } });
88253
+ return registry;
88254
+ } catch (err) {
88255
+ this.ctx.logger.error("Mesh certificate registry unavailable", { meta: { error: err instanceof Error ? err.message : String(err) } });
88256
+ return null;
88257
+ }
88258
+ }
88259
+ /** Which names the hub certified to which agent. Best effort; nothing waits on it. */
88260
+ async loadAgentHttpsNames(store) {
88261
+ try {
88262
+ await store.declareCollection.mutate({
88263
+ collection: AGENT_HTTPS_NAMES_COLLECTION,
88264
+ columns: AGENT_HTTPS_NAMES_COLUMNS
88265
+ });
88266
+ const names = new AgentHttpsNames(store);
88267
+ await names.load();
88268
+ setProcessAgentHttpsNames(names);
88269
+ } catch (err) {
88270
+ this.ctx.logger.warn("Agent HTTPS names registry unavailable (agent HTTPS issuance will be refused until it loads)", { meta: { error: err instanceof Error ? err.message : String(err) } });
88271
+ }
88272
+ }
88273
+ /**
88274
+ * The mesh TLS listener opens ONLY after the revocation registry has loaded and its
88275
+ * revocations are in the CRL (D856): until then every agent is refused, because an
88276
+ * empty CRL would accept a revoked leaf. Retries until the settings store answers.
88277
+ */
87501
88278
  async onMeshCaReady(ca) {
87502
- const hubLeaf = await issueMeshLeafForNode(ca, "hub");
87503
- await requestProcessMeshTlsEnable({
87504
- caCertPem: ca.certPem,
87505
- certPem: hubLeaf.certPem,
87506
- keyPem: hubLeaf.keyPem
88279
+ await openMeshTlsWhenRevocationsLoaded({
88280
+ loadRegistry: () => this.initMeshCertificateRegistry(),
88281
+ open: async (registry) => {
88282
+ const hubLeaf = await issueMeshLeafForNode(ca, "hub");
88283
+ await registry.recordIssued("hub", hubLeaf.certPem, "boot").catch((err) => {
88284
+ this.ctx.logger.warn("Hub mesh leaf could not be recorded", { meta: { error: err instanceof Error ? err.message : String(err) } });
88285
+ });
88286
+ await registry.reap().catch(() => void 0);
88287
+ if (this.meshReapTimer !== null) clearInterval(this.meshReapTimer);
88288
+ this.meshReapTimer = setInterval(() => void registry.reap().catch(() => void 0), MESH_REAP_MS);
88289
+ this.meshReapTimer.unref?.();
88290
+ await requestProcessMeshTlsEnable({
88291
+ caCertPem: ca.certPem,
88292
+ certPem: hubLeaf.certPem,
88293
+ keyPem: hubLeaf.keyPem
88294
+ });
88295
+ this.ctx.logger.info("Mesh CA ready — revocations loaded, TLS material offered to the transporter");
88296
+ },
88297
+ retry: (fn, ms) => {
88298
+ if (this.stoppedMesh) return;
88299
+ const timer = setTimeout(fn, ms);
88300
+ timer.unref?.();
88301
+ this.meshGateTimers.add(timer);
88302
+ },
88303
+ onState: (state) => setProcessMeshTlsListenerState(state),
88304
+ onWaiting: (attempt) => {
88305
+ if (attempt === 1 || attempt % 20 === 0) this.ctx.logger.error("Mesh TLS stays CLOSED: the revocation registry has not loaded, so agents are refused rather than accepted with an empty CRL", { meta: { attempt } });
88306
+ },
88307
+ onError: (err) => this.ctx.logger.error("Mesh certificate registry failed to load", { meta: { error: err instanceof Error ? err.message : String(err) } })
87507
88308
  });
87508
- this.ctx.logger.info("Mesh CA ready — TLS material offered to the transporter");
87509
88309
  }
87510
88310
  async mintJoinToken(input) {
87511
88311
  const ca = requireProcessMeshCa();
@@ -87847,4 +88647,4 @@ function readJwtExp(token) {
87847
88647
  return typeof exp === "number" ? exp : 0;
87848
88648
  }
87849
88649
  //#endregion
87850
- export { AGENT_UI_UNLOCK_TTL_MS as A, isMeshAuthMode as B, generateMeshCa as C, loadMeshCa as D, issueMeshLeafFromCsr as E, parseAgentDashboardOrigins as F, setProcessMeshEnforcer as G, requestProcessMeshEnforcement as H, verifyAgentUiUnlockTicket as I, parseCidrList as J, createCidrMatcher as K, MESH_AUTH_MODE_FIELD as L, UnlockTicketJtiStore as M, isAllowedAgentDashboardReturnTo as N, AGENT_DASHBOARD_ORIGINS_FIELD as O, mintAgentUiUnlockTicket as P, getProcessMeshAuthMode as R, exportMeshPrivateKeyPem as S, SYSTEM_CONFIG_CLUSTER_SECTION_ID, SystemConfigAddon, SystemConfigAddon as default, issueMeshLeafForNode as T, setProcessMeshAuthMode as U, parseMeshAuthMode as V, setProcessMeshAuthStatusSource as W, readCidrSetting as Y, getProcessMeshCaState as _, ServiceBroker as a, caFingerprintCompact as b, JoinTokenJtiStore as c, verifyMeshJoinToken as d, getProcessNodeMeshTransport as f, MeshCaKeeper as g, processMeshCrl as h, Errors as i, AGENT_UI_UNLOCK_VERSION as j, AGENT_UI_UNLOCK_DOMAIN as k, mintMeshJoinToken as l, MeshCrl as m, MESH_TLS_PORT_HELLO_FIELD as n, setProcessMeshTlsEnabler as o, setProcessMeshPeerTransportLookup as p, normalizeIp as q, TlsTcpTransporter as r, enrollMeshNode as s, systemConfigMeshActions as t, processJoinTokenJtiStore as u, requireProcessMeshCa as v, generateMeshKeyPair as w, createMeshCsrPem as x, caFingerprint as y, getProcessMeshAuthStatus as z };
88650
+ export { MESH_AUTH_MODE_FIELD as $, MeshCrl as A, generateMeshKeyPair as B, MESH_NODE_CERTIFICATES_COLLECTION as C, describeMeshLeaf as D, RENEWAL_ABANDONED as E, caFingerprint as F, AGENT_UI_UNLOCK_DOMAIN as G, issueMeshLeafFromCsr as H, caFingerprintCompact as I, UnlockTicketJtiStore as J, AGENT_UI_UNLOCK_TTL_MS as K, createMeshCsrPem as L, MeshCaKeeper as M, getProcessMeshCaState as N, getProcessMeshCertificateRegistry as O, requireProcessMeshCa as P, verifyAgentUiUnlockTicket as Q, exportMeshPrivateKeyPem as R, setProcessMeshTlsListenerState as S, SYSTEM_CONFIG_CLUSTER_SECTION_ID, SystemConfigAddon, SystemConfigAddon as default, MeshCertificateRegistry as T, loadMeshCa as U, issueMeshLeafForNode as V, AGENT_DASHBOARD_ORIGINS_FIELD as W, mintAgentUiUnlockTicket as X, isAllowedAgentDashboardReturnTo as Y, parseAgentDashboardOrigins as Z, setProcessMeshPeerTransportLookup as _, ServiceBroker as a, setProcessMeshAuthMode as at, getProcessMeshTlsListenerState as b, JoinTokenJtiStore as c, createCidrMatcher as ct, verifyMeshJoinToken as d, readCidrSetting as dt, getProcessMeshAuthMode as et, dropProcessPeer as f, onProcessNodeRegistered as g, getProcessPeerLeaf as h, Errors as i, requestProcessMeshEnforcement as it, processMeshCrl as j, setProcessMeshCertificateRegistry as k, mintMeshJoinToken as l, normalizeIp as lt, getProcessNodeMeshTransport as m, MESH_TLS_PORT_HELLO_FIELD as n, isMeshAuthMode as nt, setProcessMeshTlsEnabler as o, setProcessMeshAuthStatusSource as ot, emitProcessNodeRegistered as p, AGENT_UI_UNLOCK_VERSION as q, TlsTcpTransporter as r, parseMeshAuthMode as rt, enrollMeshNode as s, setProcessMeshEnforcer as st, systemConfigMeshActions as t, getProcessMeshAuthStatus as tt, processJoinTokenJtiStore as u, parseCidrList as ut, renewMeshCertificate as v, MESH_NODE_CERTIFICATES_COLUMNS as w, openMeshTlsWhenRevocationsLoaded as x, revokeMeshCertificate as y, generateMeshCa as z };