@camstack/system 1.2.428 → 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
@@ -3,10 +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-BQG3QFg7.js");
6
+ const require_dist = require("../../dist-Bgy_AmwX.js");
7
7
  const require_safe_buffer$1 = require("../../safe-buffer-D3aoa-7X.js");
8
- const require_constants$8 = require("../../constants-CCSJGnjs.js");
9
8
  const require_x509_es = require("../../x509.es-Dzl0BbmG.js");
9
+ const require_agent_https_names = require("../../agent-https-names-CpUSuP4w.js");
10
+ const require_constants$8 = require("../../constants-RI-hGP5S.js");
10
11
  const require_src$1 = require("../../src-CnAqG6EF.js");
11
12
  let zod = require("zod");
12
13
  let node_crypto = require("node:crypto");
@@ -425,7 +426,7 @@ async function exportMeshPrivateKeyPem(key) {
425
426
  }
426
427
  //#endregion
427
428
  //#region src/kernel/moleculer/mesh-mtls/mesh-ca-keeper.ts
428
- var SLOT$2 = Symbol.for("camstack.mesh.ca");
429
+ var SLOT$3 = Symbol.for("camstack.mesh.ca");
429
430
  var NOT_LOADED = {
430
431
  state: "unavailable",
431
432
  reason: "not-loaded"
@@ -437,10 +438,10 @@ function isState(value) {
437
438
  return state === "unavailable" && typeof Reflect.get(value, "reason") === "string";
438
439
  }
439
440
  function setProcessMeshCa(state) {
440
- Reflect.set(globalThis, SLOT$2, state);
441
+ Reflect.set(globalThis, SLOT$3, state);
441
442
  }
442
443
  function getProcessMeshCaState() {
443
- const value = Reflect.get(globalThis, SLOT$2);
444
+ const value = Reflect.get(globalThis, SLOT$3);
444
445
  return isState(value) ? value : NOT_LOADED;
445
446
  }
446
447
  function requireProcessMeshCa() {
@@ -582,10 +583,10 @@ async function loadOrGenerateMeshCa(deps) {
582
583
  var MeshCrl = class {
583
584
  serials = /* @__PURE__ */ new Set();
584
585
  revoke(serialNumber) {
585
- this.serials.add(normalizeSerial(serialNumber));
586
+ this.serials.add(normalizeSerial$1(serialNumber));
586
587
  }
587
588
  isRevoked(serialNumber) {
588
- return this.serials.has(normalizeSerial(serialNumber));
589
+ return this.serials.has(normalizeSerial$1(serialNumber));
589
590
  }
590
591
  list() {
591
592
  return [...this.serials];
@@ -593,21 +594,650 @@ var MeshCrl = class {
593
594
  /** Restore from persisted serials (system-settings). */
594
595
  replaceAll(serials) {
595
596
  this.serials.clear();
596
- for (const s of serials) this.serials.add(normalizeSerial(s));
597
+ for (const s of serials) this.serials.add(normalizeSerial$1(s));
597
598
  }
598
599
  };
599
- function normalizeSerial(serial) {
600
+ function normalizeSerial$1(serial) {
600
601
  return serial.replace(/^0x/i, "").replace(/:/g, "").toLowerCase();
601
602
  }
602
- var SLOT$1 = Symbol.for("camstack.mesh.crl");
603
+ var SLOT$2 = Symbol.for("camstack.mesh.crl");
603
604
  function processMeshCrl() {
604
- const existing = Reflect.get(globalThis, SLOT$1);
605
+ const existing = Reflect.get(globalThis, SLOT$2);
605
606
  if (existing instanceof MeshCrl) return existing;
606
607
  const crl = new MeshCrl();
607
- Reflect.set(globalThis, SLOT$1, crl);
608
+ Reflect.set(globalThis, SLOT$2, crl);
608
609
  return crl;
609
610
  }
610
611
  //#endregion
612
+ //#region src/kernel/moleculer/mesh-mtls/mesh-certificate-registry.ts
613
+ /**
614
+ * The hub's record of every mesh leaf (D856): which certificate each node holds,
615
+ * when it expires, and which serials are revoked.
616
+ *
617
+ * Three ways a row appears:
618
+ * - `enroll` / `renewal` / `boot` - the hub issued it and wrote it down at the
619
+ * moment of issuing (the authoritative record);
620
+ * - `observed` - a node enrolled before this record existed (or by a path that
621
+ * did not write it) presented this leaf on its LIVE mutual-TLS connection.
622
+ *
623
+ * Revoked serials are persisted here and replayed into the in-memory CRL at boot,
624
+ * so a revocation survives a hub restart. Hub only: agents persist none of it.
625
+ */
626
+ /**
627
+ * @durable class=config owner=system-config
628
+ * write="the hub records every mesh leaf it issues (enroll, renewal, boot) and every
629
+ * leaf a connected node is observed presenting; a revocation stamps revokedAt"
630
+ * retention="a revoked or expired row is kept 90 days past its end, then reaped; a
631
+ * live row is never deleted. Forgetting a node does not delete its rows."
632
+ */
633
+ var MESH_NODE_CERTIFICATES_COLLECTION = "mesh_node_certificates";
634
+ var MESH_NODE_CERTIFICATES_COLUMNS = [
635
+ {
636
+ name: "id",
637
+ type: "TEXT",
638
+ primaryKey: true,
639
+ notNull: true
640
+ },
641
+ {
642
+ name: "nodeId",
643
+ type: "TEXT",
644
+ notNull: true
645
+ },
646
+ {
647
+ name: "serial",
648
+ type: "TEXT",
649
+ notNull: true
650
+ },
651
+ {
652
+ name: "fingerprint",
653
+ type: "TEXT",
654
+ notNull: true
655
+ },
656
+ {
657
+ name: "notBefore",
658
+ type: "INTEGER",
659
+ notNull: true
660
+ },
661
+ {
662
+ name: "notAfter",
663
+ type: "INTEGER",
664
+ notNull: true
665
+ },
666
+ {
667
+ name: "issuedAt",
668
+ type: "INTEGER",
669
+ notNull: true
670
+ },
671
+ {
672
+ name: "issuedVia",
673
+ type: "TEXT",
674
+ notNull: true
675
+ },
676
+ {
677
+ name: "revokedAt",
678
+ type: "INTEGER"
679
+ },
680
+ {
681
+ name: "revokedReason",
682
+ type: "TEXT"
683
+ }
684
+ ];
685
+ var RETENTION_MS = 90 * 864e5;
686
+ /**
687
+ * A leaf the hub signed for a renewal that did not complete. It must not be the node's
688
+ * `current` leaf, but it is NOT on the CRL: the node may still hold it (the swap
689
+ * could have landed without the hub seeing it), and cutting it off would be the
690
+ * outage the hand-over exists to prevent. If the node is later seen presenting it, it
691
+ * is reinstated.
692
+ */
693
+ var RENEWAL_ABANDONED = "renewal-abandoned";
694
+ var VIAS = [
695
+ "enroll",
696
+ "renewal",
697
+ "boot",
698
+ "observed"
699
+ ];
700
+ /** CN, serial, fingerprint and validity of a leaf PEM (or DER). */
701
+ function describeMeshLeaf(cert) {
702
+ const x509 = new node_crypto.X509Certificate(cert);
703
+ return {
704
+ cn: subjectCn(x509.subject) ?? "",
705
+ serial: x509.serialNumber,
706
+ fingerprint: x509.fingerprint256,
707
+ notBefore: Date.parse(x509.validFrom),
708
+ notAfter: Date.parse(x509.validTo)
709
+ };
710
+ }
711
+ function normalizeSerial(serial) {
712
+ return serial.replace(/^0x/i, "").replace(/:/g, "").toLowerCase();
713
+ }
714
+ function parse$1(data) {
715
+ const str = (k) => {
716
+ const v = data[k];
717
+ return typeof v === "string" && v.length > 0 ? v : null;
718
+ };
719
+ const num = (k) => {
720
+ const v = data[k];
721
+ return typeof v === "number" && Number.isFinite(v) ? v : null;
722
+ };
723
+ const nodeId = str("nodeId");
724
+ const serial = str("serial");
725
+ const fingerprint = str("fingerprint");
726
+ const notBefore = num("notBefore");
727
+ const notAfter = num("notAfter");
728
+ const issuedAt = num("issuedAt");
729
+ if (nodeId === null || serial === null || fingerprint === null || notBefore === null || notAfter === null || issuedAt === null) return null;
730
+ return {
731
+ nodeId,
732
+ serial,
733
+ fingerprint,
734
+ notBefore,
735
+ notAfter,
736
+ issuedAt,
737
+ issuedVia: VIAS.find((v) => v === data["issuedVia"]) ?? "observed",
738
+ revokedAt: num("revokedAt"),
739
+ revokedReason: str("revokedReason")
740
+ };
741
+ }
742
+ var MeshCertificateRegistry = class {
743
+ store;
744
+ deps;
745
+ rows = /* @__PURE__ */ new Map();
746
+ constructor(store, deps = {}) {
747
+ this.store = store;
748
+ this.deps = deps;
749
+ }
750
+ now() {
751
+ return (this.deps.now ?? Date.now)();
752
+ }
753
+ async load() {
754
+ const rows = await this.store.query.query({ collection: MESH_NODE_CERTIFICATES_COLLECTION });
755
+ for (const row of rows) {
756
+ const record = parse$1(row.data);
757
+ if (record !== null) this.rows.set(record.fingerprint, record);
758
+ }
759
+ }
760
+ /** Write down a leaf the hub just issued. Idempotent per fingerprint. */
761
+ async recordIssued(nodeId, certPem, via) {
762
+ const leaf = describeMeshLeaf(certPem);
763
+ const existing = this.rows.get(leaf.fingerprint);
764
+ if (existing !== void 0) return existing;
765
+ const record = {
766
+ nodeId,
767
+ serial: leaf.serial,
768
+ fingerprint: leaf.fingerprint,
769
+ notBefore: leaf.notBefore,
770
+ notAfter: leaf.notAfter,
771
+ issuedAt: this.now(),
772
+ issuedVia: via,
773
+ revokedAt: null,
774
+ revokedReason: null
775
+ };
776
+ await this.insert(record);
777
+ this.deps.logger?.info("Mesh certificate recorded", {
778
+ tags: { nodeId },
779
+ meta: {
780
+ via,
781
+ serial: leaf.serial,
782
+ notAfter: new Date(leaf.notAfter).toISOString()
783
+ }
784
+ });
785
+ return record;
786
+ }
787
+ /**
788
+ * A node was seen presenting `leaf` on its live connection. A leaf the hub already
789
+ * knows is left alone; an unknown one is recorded `observed` (a node enrolled
790
+ * before this record existed).
791
+ */
792
+ async recordObserved(nodeId, leaf) {
793
+ const known = this.rows.get(leaf.fingerprint);
794
+ if (known !== void 0) {
795
+ if (known.revokedReason !== "renewal-abandoned") return false;
796
+ const reinstated = {
797
+ ...known,
798
+ revokedAt: null,
799
+ revokedReason: null
800
+ };
801
+ this.rows.set(known.fingerprint, reinstated);
802
+ await this.persist(reinstated);
803
+ this.deps.logger?.warn("An abandoned renewal leaf is in use - reinstated", {
804
+ tags: { nodeId },
805
+ meta: { serial: known.serial }
806
+ });
807
+ return true;
808
+ }
809
+ const record = {
810
+ nodeId,
811
+ serial: leaf.serial,
812
+ fingerprint: leaf.fingerprint,
813
+ notBefore: leaf.notBefore,
814
+ notAfter: leaf.notAfter,
815
+ issuedAt: this.now(),
816
+ issuedVia: "observed",
817
+ revokedAt: null,
818
+ revokedReason: null
819
+ };
820
+ await this.insert(record);
821
+ this.deps.logger?.info("Mesh certificate observed on a live connection and recorded", {
822
+ tags: { nodeId },
823
+ meta: {
824
+ serial: leaf.serial,
825
+ fingerprint: leaf.fingerprint
826
+ }
827
+ });
828
+ return true;
829
+ }
830
+ list(nodeId) {
831
+ return [...this.rows.values()].filter((r) => nodeId === void 0 || r.nodeId === nodeId).toSorted((a, b) => b.issuedAt - a.issuedAt);
832
+ }
833
+ /** The newest non-revoked leaf of a node, or `undefined`. */
834
+ currentFor(nodeId) {
835
+ return this.list(nodeId).find((r) => r.revokedAt === null);
836
+ }
837
+ /** Every non-revoked leaf of a node (a renewal in flight has two). */
838
+ liveFor(nodeId) {
839
+ return this.list(nodeId).filter((r) => r.revokedAt === null);
840
+ }
841
+ /** Stamp a leaf revoked (and mirror it into `crl`). `false` when unknown or already revoked. */
842
+ async revoke(fingerprint, reason, crl) {
843
+ const record = this.rows.get(fingerprint);
844
+ if (record === void 0 || record.revokedAt !== null) return false;
845
+ const revoked = {
846
+ ...record,
847
+ revokedAt: this.now(),
848
+ revokedReason: reason
849
+ };
850
+ this.rows.set(fingerprint, revoked);
851
+ crl?.revoke(record.serial);
852
+ await this.persist(revoked);
853
+ this.deps.logger?.warn("Mesh certificate revoked", {
854
+ tags: { nodeId: record.nodeId },
855
+ meta: {
856
+ serial: record.serial,
857
+ reason
858
+ }
859
+ });
860
+ return true;
861
+ }
862
+ /**
863
+ * Operator revocation of a whole node: EVERY leaf of the node that is not already on the
864
+ * CRL goes on it - the current one, an observed one, a renewal one, and a leaf that was set
865
+ * aside (`renewal-abandoned`: a copy of it may sit on a stolen box, and it must not stay
866
+ * valid just because the hub once set it aside). Returns the serials put on the CRL.
867
+ * A leaf revoked this way is never reinstated by `recordObserved`.
868
+ */
869
+ async revokeAllForNode(nodeId, reason, crl) {
870
+ const serials = [];
871
+ for (const record of this.list(nodeId)) {
872
+ if (record.revokedAt !== null && record.revokedReason !== "renewal-abandoned") continue;
873
+ const revoked = {
874
+ ...record,
875
+ revokedAt: record.revokedAt ?? this.now(),
876
+ revokedReason: reason
877
+ };
878
+ this.rows.set(record.fingerprint, revoked);
879
+ crl.revoke(record.serial);
880
+ await this.persist(revoked);
881
+ serials.push(record.serial);
882
+ }
883
+ if (serials.length > 0) this.deps.logger?.warn("Every mesh certificate of the node revoked", {
884
+ tags: { nodeId },
885
+ meta: {
886
+ serials,
887
+ reason
888
+ }
889
+ });
890
+ return serials;
891
+ }
892
+ /**
893
+ * At boot: a node with TWO live leaves had a renewal in flight when the hub stopped. The
894
+ * newer (renewal) one is set aside - it is not the node's current leaf until the node is
895
+ * seen presenting it, which reinstates it. It is NOT put on the CRL.
896
+ */
897
+ async settleInterruptedRenewals() {
898
+ let settled = 0;
899
+ const byNode = /* @__PURE__ */ new Map();
900
+ for (const record of this.rows.values()) {
901
+ if (record.revokedAt !== null) continue;
902
+ byNode.set(record.nodeId, [...byNode.get(record.nodeId) ?? [], record]);
903
+ }
904
+ for (const records of byNode.values()) {
905
+ if (records.length < 2) continue;
906
+ for (const record of records) if (record.issuedVia === "renewal" && await this.abandon(record.fingerprint)) settled += 1;
907
+ }
908
+ return settled;
909
+ }
910
+ /** Set a never-completed renewal leaf aside (not current, NOT on the CRL). */
911
+ async abandon(fingerprint) {
912
+ const record = this.rows.get(fingerprint);
913
+ if (record === void 0 || record.revokedAt !== null) return false;
914
+ const abandoned = {
915
+ ...record,
916
+ revokedAt: this.now(),
917
+ revokedReason: RENEWAL_ABANDONED
918
+ };
919
+ this.rows.set(fingerprint, abandoned);
920
+ await this.persist(abandoned);
921
+ this.deps.logger?.warn("Renewal leaf set aside (not current, not on the CRL)", {
922
+ tags: { nodeId: record.nodeId },
923
+ meta: { serial: record.serial }
924
+ });
925
+ return true;
926
+ }
927
+ /** Replay every persisted revocation into `crl` (hub boot). Set-aside leaves are not revocations. */
928
+ restoreCrl(crl) {
929
+ let restored = 0;
930
+ for (const record of this.rows.values()) {
931
+ if (record.revokedAt === null || record.revokedReason === "renewal-abandoned") continue;
932
+ crl.revoke(record.serial);
933
+ restored += 1;
934
+ }
935
+ return restored;
936
+ }
937
+ /** Is `serial` listed as revoked here? */
938
+ isRevoked(serial) {
939
+ const wanted = normalizeSerial(serial);
940
+ for (const record of this.rows.values()) if (record.revokedAt !== null && record.revokedReason !== "renewal-abandoned" && normalizeSerial(record.serial) === wanted) return true;
941
+ return false;
942
+ }
943
+ async reap() {
944
+ const cutoff = this.now() - RETENTION_MS;
945
+ const dead = [...this.rows.values()].filter((r) => (r.revokedAt ?? r.notAfter) < cutoff);
946
+ for (const r of dead) {
947
+ this.rows.delete(r.fingerprint);
948
+ await this.store.delete.mutate({
949
+ collection: MESH_NODE_CERTIFICATES_COLLECTION,
950
+ key: r.fingerprint
951
+ });
952
+ }
953
+ return dead.length;
954
+ }
955
+ async insert(record) {
956
+ this.rows.set(record.fingerprint, record);
957
+ await this.store.insert.mutate({
958
+ collection: MESH_NODE_CERTIFICATES_COLLECTION,
959
+ record: {
960
+ id: record.fingerprint,
961
+ data: { ...record }
962
+ }
963
+ });
964
+ }
965
+ async persist(record) {
966
+ try {
967
+ await this.store.update.mutate({
968
+ collection: MESH_NODE_CERTIFICATES_COLLECTION,
969
+ id: record.fingerprint,
970
+ data: { ...record }
971
+ });
972
+ } catch (error) {
973
+ this.deps.logger?.error("Mesh certificate write failed - the change holds until restart", {
974
+ tags: { nodeId: record.nodeId },
975
+ meta: { error: error instanceof Error ? error.message : String(error) }
976
+ });
977
+ }
978
+ }
979
+ };
980
+ var SLOT$1 = Symbol.for("camstack.mesh.certificate-registry");
981
+ function setProcessMeshCertificateRegistry(registry) {
982
+ if (registry === null) Reflect.deleteProperty(globalThis, SLOT$1);
983
+ else Reflect.set(globalThis, SLOT$1, registry);
984
+ }
985
+ function getProcessMeshCertificateRegistry() {
986
+ const value = Reflect.get(globalThis, SLOT$1);
987
+ return value !== null && typeof value === "object" && typeof Reflect.get(value, "recordIssued") === "function" ? value : null;
988
+ }
989
+ //#endregion
990
+ //#region src/kernel/moleculer/mesh-mtls/mesh-tls-gate.ts
991
+ var STATE_SLOT = Symbol.for("camstack.mesh.tls-listener-state");
992
+ function setProcessMeshTlsListenerState(state) {
993
+ if (state === null) Reflect.deleteProperty(globalThis, STATE_SLOT);
994
+ else Reflect.set(globalThis, STATE_SLOT, state);
995
+ }
996
+ /** `null` before the gate has run at all (no mesh CA yet). */
997
+ function getProcessMeshTlsListenerState() {
998
+ const value = Reflect.get(globalThis, STATE_SLOT);
999
+ if (value === null || typeof value !== "object") return null;
1000
+ const state = Reflect.get(value, "state");
1001
+ return state === "open" || state === "closed" ? value : null;
1002
+ }
1003
+ var DEFAULT_RETRY_MS = 15e3;
1004
+ /**
1005
+ * Nothing in `run` can reject: a load or an open that throws is reported and retried, so a
1006
+ * failure can never become an unhandled rejection that leaves the listener closed in silence.
1007
+ */
1008
+ function openMeshTlsWhenRevocationsLoaded(deps) {
1009
+ let attempt = 0;
1010
+ let opened = false;
1011
+ const now = deps.now ?? Date.now;
1012
+ let closedSince = null;
1013
+ const closed = (reason) => {
1014
+ closedSince ??= now();
1015
+ deps.onState?.({
1016
+ state: "closed",
1017
+ reason,
1018
+ since: closedSince
1019
+ });
1020
+ };
1021
+ const run = async () => {
1022
+ if (opened) return true;
1023
+ attempt += 1;
1024
+ let registry = null;
1025
+ try {
1026
+ registry = await deps.loadRegistry();
1027
+ } catch (error) {
1028
+ deps.onError?.(error);
1029
+ }
1030
+ if (registry === null) {
1031
+ closed("the revocation registry has not loaded - agents are refused");
1032
+ deps.onWaiting?.(attempt);
1033
+ deps.retry(() => void run(), deps.retryMs ?? DEFAULT_RETRY_MS);
1034
+ return false;
1035
+ }
1036
+ try {
1037
+ await deps.open(registry);
1038
+ } catch (error) {
1039
+ deps.onError?.(error);
1040
+ closed("the listener failed to open - retrying");
1041
+ deps.retry(() => void run(), deps.retryMs ?? DEFAULT_RETRY_MS);
1042
+ return false;
1043
+ }
1044
+ opened = true;
1045
+ deps.onState?.({ state: "open" });
1046
+ return true;
1047
+ };
1048
+ return run();
1049
+ }
1050
+ //#endregion
1051
+ //#region src/kernel/moleculer/mesh-mtls/mesh-renewal.ts
1052
+ var DEFAULT_RECONNECT_TIMEOUT_MS = 45e3;
1053
+ var DEFAULT_CONFIRM_RETRY_MS = 500;
1054
+ function defaultSetTimer(fn, ms) {
1055
+ const timer = setTimeout(fn, ms);
1056
+ timer.unref?.();
1057
+ return { cancel: () => clearTimeout(timer) };
1058
+ }
1059
+ function defaultSleep(ms) {
1060
+ return new Promise((resolve) => setTimeout(resolve, ms));
1061
+ }
1062
+ function message$1(error) {
1063
+ return error instanceof Error ? error.message : String(error);
1064
+ }
1065
+ async function renewMeshCertificate(deps) {
1066
+ const { nodeId, log } = deps;
1067
+ const tags = { nodeId };
1068
+ const sleep = deps.sleep ?? defaultSleep;
1069
+ const confirmRetryMs = deps.confirmRetryMs ?? DEFAULT_CONFIRM_RETRY_MS;
1070
+ const setTimer = deps.setTimer ?? defaultSetTimer;
1071
+ const timeoutMs = deps.reconnectTimeoutMs ?? DEFAULT_RECONNECT_TIMEOUT_MS;
1072
+ const now = deps.now ?? Date.now;
1073
+ const live = deps.peers.peerLeaf(nodeId);
1074
+ const previous = live ?? deps.registry.currentFor(nodeId);
1075
+ if (live !== void 0) await deps.registry.recordObserved(nodeId, {
1076
+ cn: nodeId,
1077
+ serial: live.serial,
1078
+ fingerprint: live.fingerprint,
1079
+ notBefore: live.notBefore,
1080
+ notAfter: live.notAfter
1081
+ });
1082
+ log.info("Mesh renewal: starting", {
1083
+ tags,
1084
+ meta: {
1085
+ currentSerial: previous?.serial ?? null,
1086
+ connected: live !== void 0
1087
+ }
1088
+ });
1089
+ const fail = (stage, reason, keptPreviousLeaf) => {
1090
+ log.error("Mesh renewal: failed", {
1091
+ tags,
1092
+ meta: {
1093
+ stage,
1094
+ reason,
1095
+ keptPreviousLeaf
1096
+ }
1097
+ });
1098
+ return {
1099
+ ok: false,
1100
+ stage,
1101
+ reason,
1102
+ keptPreviousLeaf
1103
+ };
1104
+ };
1105
+ let csrPem;
1106
+ try {
1107
+ csrPem = (await deps.client.prepareMeshRenewal()).csrPem;
1108
+ log.info("Mesh renewal: the node generated a new key and sent its CSR", { tags });
1109
+ } catch (error) {
1110
+ return fail("prepare", message$1(error), true);
1111
+ }
1112
+ let issued;
1113
+ try {
1114
+ issued = await issueMeshLeafFromCsr(deps.ca, csrPem, { expectedNodeId: nodeId });
1115
+ await deps.registry.recordIssued(nodeId, issued.certPem, "renewal");
1116
+ log.info("Mesh renewal: new leaf signed and recorded", {
1117
+ tags,
1118
+ meta: {
1119
+ serial: issued.serialNumber,
1120
+ notAfter: issued.notAfter.toISOString()
1121
+ }
1122
+ });
1123
+ } catch (error) {
1124
+ return fail("issue", message$1(error), true);
1125
+ }
1126
+ const newLeaf = describeMeshLeaf(issued.certPem);
1127
+ let resolveRegistered = () => void 0;
1128
+ const registeredPromise = new Promise((resolve) => {
1129
+ resolveRegistered = resolve;
1130
+ });
1131
+ const unsubscribe = deps.registrations.subscribe((id) => {
1132
+ if (id === nodeId && deps.peers.peerLeaf(nodeId)?.fingerprint === newLeaf.fingerprint) resolveRegistered(true);
1133
+ });
1134
+ const deadlineTimer = setTimer(() => resolveRegistered(false), timeoutMs);
1135
+ registeredPromise.then(() => deadlineTimer.cancel());
1136
+ try {
1137
+ await deps.client.installMeshLeaf({
1138
+ certPem: issued.certPem,
1139
+ caCertPem: deps.ca.certPem,
1140
+ confirmWithinMs: timeoutMs + 3e4
1141
+ });
1142
+ log.info("Mesh renewal: the node installed the new leaf and is reconnecting", { tags });
1143
+ } catch (error) {
1144
+ unsubscribe();
1145
+ deadlineTimer.cancel();
1146
+ await deps.client.rollbackMeshLeaf().catch(() => void 0);
1147
+ await deps.registry.abandon(newLeaf.fingerprint);
1148
+ return fail("install", message$1(error), true);
1149
+ }
1150
+ const deadline = now() + timeoutMs;
1151
+ const registered = await registeredPromise;
1152
+ unsubscribe();
1153
+ if (!registered) {
1154
+ const back = await deps.client.rollbackMeshLeaf().catch(() => ({ rolledBack: false }));
1155
+ log.warn("Mesh renewal: the node was not back and registered with the new leaf in time", {
1156
+ tags,
1157
+ meta: {
1158
+ timeoutMs,
1159
+ rolledBack: back.rolledBack
1160
+ }
1161
+ });
1162
+ await deps.registry.abandon(newLeaf.fingerprint);
1163
+ 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);
1164
+ }
1165
+ log.info("Mesh renewal: the node registered presenting the new leaf", { tags });
1166
+ let confirmError = "the node did not confirm the new leaf";
1167
+ let confirmedByNode = false;
1168
+ for (;;) {
1169
+ try {
1170
+ if ((await deps.client.confirmMeshLeaf({ fingerprintSha256: newLeaf.fingerprint })).confirmed) {
1171
+ confirmedByNode = true;
1172
+ break;
1173
+ }
1174
+ confirmError = "the node did not confirm the new leaf";
1175
+ } catch (error) {
1176
+ confirmError = message$1(error);
1177
+ log.warn("Mesh renewal: confirm not accepted yet", {
1178
+ tags,
1179
+ meta: { error: confirmError }
1180
+ });
1181
+ }
1182
+ if (now() >= deadline) break;
1183
+ await sleep(confirmRetryMs);
1184
+ }
1185
+ if (!confirmedByNode) return fail("confirm", confirmError, false);
1186
+ if (previous !== void 0 && previous.serial !== newLeaf.serial) {
1187
+ await deps.registry.revoke(previous.fingerprint, "replaced-by-renewal", deps.crl);
1188
+ deps.crl.revoke(previous.serial);
1189
+ log.info("Mesh renewal: the previous leaf is revoked", {
1190
+ tags,
1191
+ meta: { oldSerial: previous.serial }
1192
+ });
1193
+ }
1194
+ log.info("Mesh renewal: done", {
1195
+ tags,
1196
+ meta: {
1197
+ newSerial: newLeaf.serial,
1198
+ notAfter: new Date(newLeaf.notAfter).toISOString()
1199
+ }
1200
+ });
1201
+ return {
1202
+ ok: true,
1203
+ oldSerial: previous?.serial ?? null,
1204
+ newSerial: newLeaf.serial,
1205
+ newNotAfter: newLeaf.notAfter
1206
+ };
1207
+ }
1208
+ /**
1209
+ * Revoke every live mesh leaf of `nodeId` and cut its connection. The node cannot
1210
+ * reconnect: it must re-join with a new join token. The node is NOT forgotten
1211
+ * (that stays `clusterNodes.forgetNode`).
1212
+ */
1213
+ async function revokeMeshCertificate(deps) {
1214
+ const { nodeId, log } = deps;
1215
+ const tags = { nodeId };
1216
+ const live = deps.peers.peerLeaf(nodeId);
1217
+ if (live !== void 0) await deps.registry.recordObserved(nodeId, {
1218
+ cn: nodeId,
1219
+ serial: live.serial,
1220
+ fingerprint: live.fingerprint,
1221
+ notBefore: live.notBefore,
1222
+ notAfter: live.notAfter
1223
+ });
1224
+ const serials = [...await deps.registry.revokeAllForNode(nodeId, "revoked-by-operator", deps.crl)];
1225
+ const disconnected = deps.peers.dropPeer(nodeId);
1226
+ log.warn("Mesh certificate revoked and the node disconnected", {
1227
+ tags,
1228
+ meta: {
1229
+ serials,
1230
+ disconnected
1231
+ }
1232
+ });
1233
+ return {
1234
+ nodeId,
1235
+ revokedSerials: serials,
1236
+ disconnected,
1237
+ 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."
1238
+ };
1239
+ }
1240
+ //#endregion
611
1241
  //#region src/kernel/moleculer/mesh-mtls/mesh-peer-transport.ts
612
1242
  var LOOKUP_SLOT = Symbol.for("camstack.cluster.mesh-peer-transport-lookup");
613
1243
  /** Published by the hub broker owner; cleared on shutdown. */
@@ -626,6 +1256,36 @@ function getProcessMeshPeerTransportLookup() {
626
1256
  function getProcessNodeMeshTransport(nodeId) {
627
1257
  return getProcessMeshPeerTransportLookup()?.transportOf(nodeId);
628
1258
  }
1259
+ /** The leaf `nodeId` presented on its live connection, or `undefined`. */
1260
+ function getProcessPeerLeaf(nodeId) {
1261
+ return getProcessMeshPeerTransportLookup()?.peerLeaf?.(nodeId);
1262
+ }
1263
+ /** Disconnect `nodeId` (closes its mutual-TLS sockets). `0` when none were open. */
1264
+ function dropProcessPeer(nodeId) {
1265
+ return getProcessMeshPeerTransportLookup()?.dropPeer?.(nodeId) ?? 0;
1266
+ }
1267
+ var REGISTRATION_SLOT = Symbol.for("camstack.cluster.node-registration-listeners");
1268
+ function registrationListeners() {
1269
+ const existing = Reflect.get(globalThis, REGISTRATION_SLOT);
1270
+ if (existing instanceof Set) return existing;
1271
+ const created = /* @__PURE__ */ new Set();
1272
+ Reflect.set(globalThis, REGISTRATION_SLOT, created);
1273
+ return created;
1274
+ }
1275
+ /** Subscribe to "node X finished registering". Returns the unsubscribe. */
1276
+ function onProcessNodeRegistered(listener) {
1277
+ const listeners = registrationListeners();
1278
+ listeners.add(listener);
1279
+ return () => {
1280
+ listeners.delete(listener);
1281
+ };
1282
+ }
1283
+ /** Called by the hub when a node's `registerNode` has been applied. */
1284
+ function emitProcessNodeRegistered(nodeId) {
1285
+ for (const listener of [...registrationListeners()]) try {
1286
+ listener(nodeId);
1287
+ } catch {}
1288
+ }
629
1289
  //#endregion
630
1290
  //#region ../../node_modules/jose/dist/webapi/lib/buffer_utils.js
631
1291
  var encoder = new TextEncoder(), decoder = new TextDecoder(), strictDecoder = new TextDecoder("utf-8", { fatal: !0 });
@@ -1475,6 +2135,11 @@ async function enrollMeshNode(body, deps) {
1475
2135
  status: 400
1476
2136
  };
1477
2137
  }
2138
+ try {
2139
+ await deps.onIssued?.(leaf.commonName, leaf.certPem);
2140
+ } catch (err) {
2141
+ deps.onRecordFailed?.(leaf.commonName, err);
2142
+ }
1478
2143
  return {
1479
2144
  ok: true,
1480
2145
  cert: leaf.certPem,
@@ -86792,6 +87457,23 @@ function peekSender(transporter, type, message) {
86792
87457
  return null;
86793
87458
  }
86794
87459
  }
87460
+ function peerLeafOf(socket) {
87461
+ const cert = socket.getPeerCertificate(true);
87462
+ if (cert == null || typeof cert !== "object" || !("raw" in cert) || !Buffer.isBuffer(cert.raw)) return null;
87463
+ try {
87464
+ const x509 = new node_crypto.X509Certificate(cert.raw);
87465
+ const remote = socket.remoteAddress;
87466
+ return {
87467
+ ...remote !== void 0 ? { remoteAddress: remote.replace(/^::ffff:/, "") } : {},
87468
+ serial: x509.serialNumber,
87469
+ fingerprint: x509.fingerprint256,
87470
+ notBefore: Date.parse(x509.validFrom),
87471
+ notAfter: Date.parse(x509.validTo)
87472
+ };
87473
+ } catch {
87474
+ return null;
87475
+ }
87476
+ }
86795
87477
  function peerIdentity(socket) {
86796
87478
  const cert = socket.getPeerCertificate(true);
86797
87479
  if (cert == null || typeof cert !== "object" || !("raw" in cert) || !cert.raw) return null;
@@ -86819,6 +87501,8 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
86819
87501
  tlsSockets = /* @__PURE__ */ new Set();
86820
87502
  /** Node id (certificate CN) → serial of every peer that completed a mutual-TLS handshake. */
86821
87503
  tlsPeers = /* @__PURE__ */ new Map();
87504
+ /** Node id → the leaf it presented at its last handshake (hub inventory, D856). */
87505
+ peerLeaves = /* @__PURE__ */ new Map();
86822
87506
  clearDisabled = false;
86823
87507
  hubTlsOnly;
86824
87508
  hubOnlyPeers;
@@ -86932,6 +87616,7 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
86932
87616
  tlsSock.meshPeerCn = identity.cn;
86933
87617
  tlsSock.meshPeerSerial = identity.serial;
86934
87618
  self.tlsPeers.set(identity.cn, identity.serial);
87619
+ self.rememberPeerLeaf(identity.cn, tlsSock);
86935
87620
  self.tlsSockets.add(tlsSock);
86936
87621
  tlsSock.on("close", () => {
86937
87622
  self.tlsSockets.delete(tlsSock);
@@ -86984,6 +87669,25 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
86984
87669
  forgetPeer(nodeId) {
86985
87670
  for (const socket of this.tlsSockets) if (socket.meshPeerCn === nodeId && !socket.destroyed) return;
86986
87671
  this.tlsPeers.delete(nodeId);
87672
+ this.peerLeaves.delete(nodeId);
87673
+ }
87674
+ rememberPeerLeaf(nodeId, socket) {
87675
+ const leaf = peerLeafOf(socket);
87676
+ if (leaf !== null) this.peerLeaves.set(nodeId, leaf);
87677
+ }
87678
+ /** The leaf `nodeId` presented at its last mutual-TLS handshake. */
87679
+ peerLeaf(nodeId) {
87680
+ return this.peerLeaves.get(nodeId);
87681
+ }
87682
+ /** Close every live mutual-TLS socket of `nodeId` (revocation, renewal). Returns how many. */
87683
+ dropPeer(nodeId) {
87684
+ let closed = 0;
87685
+ for (const socket of this.tlsSockets) {
87686
+ if (socket.meshPeerCn !== nodeId || socket.destroyed) continue;
87687
+ socket.destroy();
87688
+ closed += 1;
87689
+ }
87690
+ return closed;
86987
87691
  }
86988
87692
  /** True once the mutual-TLS listener is bound. */
86989
87693
  isTlsListening() {
@@ -87038,6 +87742,7 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
87038
87742
  } catch {}
87039
87743
  this.tlsSockets.clear();
87040
87744
  this.tlsPeers.clear();
87745
+ this.peerLeaves.clear();
87041
87746
  return super.disconnect();
87042
87747
  }
87043
87748
  /**
@@ -87166,6 +87871,7 @@ var TlsTcpTransporter = class extends TcpTransporterBase {
87166
87871
  socket.meshPeerSerial = identity.serial;
87167
87872
  this.tlsSockets.add(socket);
87168
87873
  this.tlsPeers.set(identity.cn, identity.serial);
87874
+ this.rememberPeerLeaf(identity.cn, socket);
87169
87875
  const reader = this.reader;
87170
87876
  socket.on("close", () => {
87171
87877
  this.tlsSockets.delete(socket);
@@ -87387,6 +88093,8 @@ var systemConfigMeshActions = require_dist.defineCustomActions({
87387
88093
  });
87388
88094
  //#endregion
87389
88095
  //#region src/builtins/system-config/system-config.addon.ts
88096
+ /** How often superseded and expired mesh certificate rows are reaped. */
88097
+ var MESH_REAP_MS = 24 * 36e5;
87390
88098
  /**
87391
88099
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
87392
88100
  *
@@ -87464,6 +88172,10 @@ var SystemConfigAddon = class extends require_dist.BaseAddon {
87464
88172
  id = "system-config";
87465
88173
  clusterSecretKeeper = null;
87466
88174
  meshCaKeeper = null;
88175
+ meshReapTimer = null;
88176
+ meshGateTimers = /* @__PURE__ */ new Set();
88177
+ stoppedMesh = false;
88178
+ loggedNoSettingsStore = false;
87467
88179
  constructor() {
87468
88180
  super({});
87469
88181
  }
@@ -87503,19 +88215,107 @@ var SystemConfigAddon = class extends require_dist.BaseAddon {
87503
88215
  }
87504
88216
  async onShutdown() {
87505
88217
  setProcessMeshEnforcer(null);
88218
+ setProcessMeshCertificateRegistry(null);
88219
+ setProcessMeshTlsListenerState(null);
88220
+ require_agent_https_names.setProcessAgentHttpsNames(null);
88221
+ this.stoppedMesh = true;
88222
+ if (this.meshReapTimer !== null) clearInterval(this.meshReapTimer);
88223
+ this.meshReapTimer = null;
88224
+ for (const timer of this.meshGateTimers) clearTimeout(timer);
88225
+ this.meshGateTimers.clear();
87506
88226
  this.meshCaKeeper?.stop();
87507
88227
  this.meshCaKeeper = null;
87508
88228
  this.clusterSecretKeeper?.stop();
87509
88229
  this.clusterSecretKeeper = null;
87510
88230
  }
88231
+ /**
88232
+ * The hub's record of mesh leaves (D856): declared and loaded BEFORE the TLS
88233
+ * listener opens, so persisted revocations are in the CRL before any node can
88234
+ * connect. Best effort: a hub whose settings store is not up still serves the
88235
+ * mesh (the registry then fills from the leaves nodes present).
88236
+ */
88237
+ async initMeshCertificateRegistry() {
88238
+ const store = this.ctx.api?.settingsStore;
88239
+ if (!store) {
88240
+ if (!this.loggedNoSettingsStore) {
88241
+ this.loggedNoSettingsStore = true;
88242
+ 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)");
88243
+ }
88244
+ return null;
88245
+ }
88246
+ this.loadAgentHttpsNames(store);
88247
+ try {
88248
+ await store.declareCollection.mutate({
88249
+ collection: MESH_NODE_CERTIFICATES_COLLECTION,
88250
+ columns: MESH_NODE_CERTIFICATES_COLUMNS,
88251
+ indexes: [{
88252
+ name: "idx_mesh_node_certificates_node",
88253
+ columns: ["nodeId"]
88254
+ }]
88255
+ });
88256
+ const registry = new MeshCertificateRegistry(store, { logger: this.ctx.logger });
88257
+ await registry.load();
88258
+ const restored = registry.restoreCrl(processMeshCrl());
88259
+ const settled = await registry.settleInterruptedRenewals();
88260
+ if (settled > 0) this.ctx.logger.warn("A mesh renewal was interrupted by a hub restart - its new leaf was set aside", { meta: { settled } });
88261
+ setProcessMeshCertificateRegistry(registry);
88262
+ if (restored > 0) this.ctx.logger.info("Mesh revocations restored into the CRL", { meta: { restored } });
88263
+ return registry;
88264
+ } catch (err) {
88265
+ this.ctx.logger.error("Mesh certificate registry unavailable", { meta: { error: err instanceof Error ? err.message : String(err) } });
88266
+ return null;
88267
+ }
88268
+ }
88269
+ /** Which names the hub certified to which agent. Best effort; nothing waits on it. */
88270
+ async loadAgentHttpsNames(store) {
88271
+ try {
88272
+ await store.declareCollection.mutate({
88273
+ collection: require_agent_https_names.AGENT_HTTPS_NAMES_COLLECTION,
88274
+ columns: require_agent_https_names.AGENT_HTTPS_NAMES_COLUMNS
88275
+ });
88276
+ const names = new require_agent_https_names.AgentHttpsNames(store);
88277
+ await names.load();
88278
+ require_agent_https_names.setProcessAgentHttpsNames(names);
88279
+ } catch (err) {
88280
+ 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) } });
88281
+ }
88282
+ }
88283
+ /**
88284
+ * The mesh TLS listener opens ONLY after the revocation registry has loaded and its
88285
+ * revocations are in the CRL (D856): until then every agent is refused, because an
88286
+ * empty CRL would accept a revoked leaf. Retries until the settings store answers.
88287
+ */
87511
88288
  async onMeshCaReady(ca) {
87512
- const hubLeaf = await issueMeshLeafForNode(ca, "hub");
87513
- await requestProcessMeshTlsEnable({
87514
- caCertPem: ca.certPem,
87515
- certPem: hubLeaf.certPem,
87516
- keyPem: hubLeaf.keyPem
88289
+ await openMeshTlsWhenRevocationsLoaded({
88290
+ loadRegistry: () => this.initMeshCertificateRegistry(),
88291
+ open: async (registry) => {
88292
+ const hubLeaf = await issueMeshLeafForNode(ca, "hub");
88293
+ await registry.recordIssued("hub", hubLeaf.certPem, "boot").catch((err) => {
88294
+ this.ctx.logger.warn("Hub mesh leaf could not be recorded", { meta: { error: err instanceof Error ? err.message : String(err) } });
88295
+ });
88296
+ await registry.reap().catch(() => void 0);
88297
+ if (this.meshReapTimer !== null) clearInterval(this.meshReapTimer);
88298
+ this.meshReapTimer = setInterval(() => void registry.reap().catch(() => void 0), MESH_REAP_MS);
88299
+ this.meshReapTimer.unref?.();
88300
+ await requestProcessMeshTlsEnable({
88301
+ caCertPem: ca.certPem,
88302
+ certPem: hubLeaf.certPem,
88303
+ keyPem: hubLeaf.keyPem
88304
+ });
88305
+ this.ctx.logger.info("Mesh CA ready — revocations loaded, TLS material offered to the transporter");
88306
+ },
88307
+ retry: (fn, ms) => {
88308
+ if (this.stoppedMesh) return;
88309
+ const timer = setTimeout(fn, ms);
88310
+ timer.unref?.();
88311
+ this.meshGateTimers.add(timer);
88312
+ },
88313
+ onState: (state) => setProcessMeshTlsListenerState(state),
88314
+ onWaiting: (attempt) => {
88315
+ 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 } });
88316
+ },
88317
+ onError: (err) => this.ctx.logger.error("Mesh certificate registry failed to load", { meta: { error: err instanceof Error ? err.message : String(err) } })
87517
88318
  });
87518
- this.ctx.logger.info("Mesh CA ready — TLS material offered to the transporter");
87519
88319
  }
87520
88320
  async mintJoinToken(input) {
87521
88321
  const ca = requireProcessMeshCa();
@@ -87864,9 +88664,13 @@ exports.AGENT_UI_UNLOCK_VERSION = AGENT_UI_UNLOCK_VERSION;
87864
88664
  exports.Errors = Errors;
87865
88665
  exports.JoinTokenJtiStore = JoinTokenJtiStore;
87866
88666
  exports.MESH_AUTH_MODE_FIELD = MESH_AUTH_MODE_FIELD;
88667
+ exports.MESH_NODE_CERTIFICATES_COLLECTION = MESH_NODE_CERTIFICATES_COLLECTION;
88668
+ exports.MESH_NODE_CERTIFICATES_COLUMNS = MESH_NODE_CERTIFICATES_COLUMNS;
87867
88669
  exports.MESH_TLS_PORT_HELLO_FIELD = MESH_TLS_PORT_HELLO_FIELD;
87868
88670
  exports.MeshCaKeeper = MeshCaKeeper;
88671
+ exports.MeshCertificateRegistry = MeshCertificateRegistry;
87869
88672
  exports.MeshCrl = MeshCrl;
88673
+ exports.RENEWAL_ABANDONED = RENEWAL_ABANDONED;
87870
88674
  exports.SYSTEM_CONFIG_CLUSTER_SECTION_ID = SYSTEM_CONFIG_CLUSTER_SECTION_ID;
87871
88675
  exports.ServiceBroker = ServiceBroker;
87872
88676
  exports.SystemConfigAddon = SystemConfigAddon;
@@ -87877,6 +88681,9 @@ exports.caFingerprint = caFingerprint;
87877
88681
  exports.caFingerprintCompact = caFingerprintCompact;
87878
88682
  exports.createCidrMatcher = createCidrMatcher;
87879
88683
  exports.createMeshCsrPem = createMeshCsrPem;
88684
+ exports.describeMeshLeaf = describeMeshLeaf;
88685
+ exports.dropProcessPeer = dropProcessPeer;
88686
+ exports.emitProcessNodeRegistered = emitProcessNodeRegistered;
87880
88687
  exports.enrollMeshNode = enrollMeshNode;
87881
88688
  exports.exportMeshPrivateKeyPem = exportMeshPrivateKeyPem;
87882
88689
  exports.generateMeshCa = generateMeshCa;
@@ -87884,7 +88691,10 @@ exports.generateMeshKeyPair = generateMeshKeyPair;
87884
88691
  exports.getProcessMeshAuthMode = getProcessMeshAuthMode;
87885
88692
  exports.getProcessMeshAuthStatus = getProcessMeshAuthStatus;
87886
88693
  exports.getProcessMeshCaState = getProcessMeshCaState;
88694
+ exports.getProcessMeshCertificateRegistry = getProcessMeshCertificateRegistry;
88695
+ exports.getProcessMeshTlsListenerState = getProcessMeshTlsListenerState;
87887
88696
  exports.getProcessNodeMeshTransport = getProcessNodeMeshTransport;
88697
+ exports.getProcessPeerLeaf = getProcessPeerLeaf;
87888
88698
  exports.isAllowedAgentDashboardReturnTo = isAllowedAgentDashboardReturnTo;
87889
88699
  exports.isMeshAuthMode = isMeshAuthMode;
87890
88700
  exports.issueMeshLeafForNode = issueMeshLeafForNode;
@@ -87893,19 +88703,25 @@ exports.loadMeshCa = loadMeshCa;
87893
88703
  exports.mintAgentUiUnlockTicket = mintAgentUiUnlockTicket;
87894
88704
  exports.mintMeshJoinToken = mintMeshJoinToken;
87895
88705
  exports.normalizeIp = normalizeIp;
88706
+ exports.onProcessNodeRegistered = onProcessNodeRegistered;
88707
+ exports.openMeshTlsWhenRevocationsLoaded = openMeshTlsWhenRevocationsLoaded;
87896
88708
  exports.parseAgentDashboardOrigins = parseAgentDashboardOrigins;
87897
88709
  exports.parseCidrList = parseCidrList;
87898
88710
  exports.parseMeshAuthMode = parseMeshAuthMode;
87899
88711
  exports.processJoinTokenJtiStore = processJoinTokenJtiStore;
87900
88712
  exports.processMeshCrl = processMeshCrl;
87901
88713
  exports.readCidrSetting = readCidrSetting;
88714
+ exports.renewMeshCertificate = renewMeshCertificate;
87902
88715
  exports.requestProcessMeshEnforcement = requestProcessMeshEnforcement;
87903
88716
  exports.requireProcessMeshCa = requireProcessMeshCa;
88717
+ exports.revokeMeshCertificate = revokeMeshCertificate;
87904
88718
  exports.setProcessMeshAuthMode = setProcessMeshAuthMode;
87905
88719
  exports.setProcessMeshAuthStatusSource = setProcessMeshAuthStatusSource;
88720
+ exports.setProcessMeshCertificateRegistry = setProcessMeshCertificateRegistry;
87906
88721
  exports.setProcessMeshEnforcer = setProcessMeshEnforcer;
87907
88722
  exports.setProcessMeshPeerTransportLookup = setProcessMeshPeerTransportLookup;
87908
88723
  exports.setProcessMeshTlsEnabler = setProcessMeshTlsEnabler;
88724
+ exports.setProcessMeshTlsListenerState = setProcessMeshTlsListenerState;
87909
88725
  exports.systemConfigMeshActions = systemConfigMeshActions;
87910
88726
  exports.verifyAgentUiUnlockTicket = verifyAgentUiUnlockTicket;
87911
88727
  exports.verifyMeshJoinToken = verifyMeshJoinToken;