@camstack/system 1.2.428 → 1.2.430
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-https-names-CpUSuP4w.js +120 -0
- package/dist/agent-https-names-uetr4OQl.mjs +91 -0
- package/dist/auth/auth-audit-log.d.ts +1 -1
- package/dist/auth/device-ca-store.d.ts +57 -0
- package/dist/auth/device-ca.d.ts +75 -0
- package/dist/auth/device-credential-manager.d.ts +154 -0
- package/dist/auth/login-session-manager.d.ts +14 -2
- package/dist/auth/user-management-internal.d.ts +10 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/autotrack/index.js +1 -1
- package/dist/builtins/autotrack/index.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
- package/dist/builtins/calendars/calendars.addon.js +1 -1
- package/dist/builtins/calendars/calendars.addon.mjs +1 -1
- package/dist/builtins/camera-grid/index.js +1 -1
- package/dist/builtins/camera-grid/index.mjs +1 -1
- package/dist/builtins/composer/composer.addon.js +2 -2
- package/dist/builtins/composer/composer.addon.mjs +2 -2
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
- package/dist/builtins/device-manager/device-manager.addon.js +2 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/keychain/index.js +10 -6
- package/dist/builtins/keychain/index.mjs +8 -4
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.d.ts +6 -0
- package/dist/builtins/local-auth/local-auth.addon.js +955 -47
- package/dist/builtins/local-auth/local-auth.addon.mjs +946 -41
- package/dist/builtins/local-network/local-network.addon.js +2 -2
- package/dist/builtins/local-network/local-network.addon.mjs +2 -2
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +1 -1
- package/dist/builtins/snapshot/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/file-host-client.d.ts +11 -0
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/index.js +1 -1
- package/dist/builtins/sqlite-storage/index.mjs +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +34 -4
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +34 -4
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.d.ts +18 -0
- package/dist/builtins/system-config/system-config.addon.js +834 -18
- package/dist/builtins/system-config/system-config.addon.mjs +819 -19
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{composition-sources-BQNxXQZG.mjs → composition-sources-Cw_TXEq9.mjs} +1 -1
- package/dist/{composition-sources-DvCGZEUI.js → composition-sources-o5MPSlk6.js} +1 -1
- package/dist/{constants-CCSJGnjs.js → constants-BcNaVkvI.js} +1 -1
- package/dist/{constants-CR4If_lP.mjs → constants-CttE77VF.mjs} +1 -1
- package/dist/device-ca-B-BTxeCj.mjs +355 -0
- package/dist/device-ca-CKiqh2Iu.js +462 -0
- package/dist/{dist-DHSJV4Qc.mjs → dist-CXG6muCR.mjs} +1052 -35
- package/dist/{dist-BQG3QFg7.js → dist-DxJdci75.js} +1099 -34
- package/dist/file-host-worker.js +1 -1
- package/dist/file-host-worker.mjs +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +59 -12
- package/dist/index.mjs +10 -9
- package/dist/kernel/index.d.ts +2 -2
- package/dist/kernel/moleculer/mesh-mtls/index.d.ts +4 -1
- package/dist/kernel/moleculer/mesh-mtls/mesh-certificate-registry.d.ts +91 -0
- package/dist/kernel/moleculer/mesh-mtls/mesh-enroll.d.ts +4 -0
- package/dist/kernel/moleculer/mesh-mtls/mesh-peer-transport.d.ts +23 -0
- package/dist/kernel/moleculer/mesh-mtls/mesh-renewal.d.ts +126 -0
- package/dist/kernel/moleculer/mesh-mtls/mesh-tls-gate.d.ts +40 -0
- package/dist/kernel/moleculer/mesh-mtls/tls-tcp-transporter.d.ts +15 -0
- package/dist/{restore-archive-Dccm8lkA.js → restore-archive-CQJNoqRK.js} +1 -1
- package/dist/{restore-archive-DH1Pxq_1.mjs → restore-archive-D_wBL1OO.mjs} +1 -1
- package/dist/{retired-settings-keys-CPz7zpza.mjs → retired-settings-keys-EmDe11cP.mjs} +1 -1
- package/dist/{retired-settings-keys-BfGaSeQI.js → retired-settings-keys-g1oifXxR.js} +1 -1
- package/dist/tls/agent-https-names.d.ts +27 -0
- package/dist/tls/agent-https.d.ts +111 -0
- package/dist/tls/cert-issue.d.ts +7 -0
- package/dist/tls/index.d.ts +3 -0
- package/dist/{tls-BgImdxCO.js → tls-C5IYg3V5.js} +406 -0
- package/dist/{tls-CaOLZ09W.mjs → tls-CpcGkeJV.mjs} +314 -4
- package/dist/{wal-maintenance-DItkuOvu.js → wal-maintenance-BW1LIHi0.js} +0 -0
- package/dist/{wal-maintenance-Drqhcjit.mjs → wal-maintenance-vhPZLYuA.mjs} +0 -0
- package/package.json +4 -1
- package/dist/jwt-signing-key-BZm055IP.js +0 -174
- package/dist/jwt-signing-key-DL-qk1SM.mjs +0 -127
|
@@ -459,6 +459,35 @@ async function issueLeaf(ca, identity, commonName, validDays, now) {
|
|
|
459
459
|
keyPem: await exportKeyPem(keys.privateKey)
|
|
460
460
|
};
|
|
461
461
|
}
|
|
462
|
+
/**
|
|
463
|
+
* A server leaf for a key pair the CALLER holds (D856): an agent sends a CSR, the
|
|
464
|
+
* hub signs its public key under the local root. Same extensions as
|
|
465
|
+
* {@link issueLeaf} - `CA:FALSE`, serverAuth only - so a device that trusts the
|
|
466
|
+
* hub root trusts the agent. The private key never exists on the hub.
|
|
467
|
+
*/
|
|
468
|
+
async function issueLeafFromCsr(ca, csrPem, identity, commonName, validDays, now) {
|
|
469
|
+
const csr = new require_x509_es.Pkcs10CertificateRequest(csrPem);
|
|
470
|
+
if (!await csr.verify(node_crypto.webcrypto)) throw new Error("CSR signature is invalid");
|
|
471
|
+
const extensions = [
|
|
472
|
+
new require_x509_es.BasicConstraintsExtension(false, void 0, true),
|
|
473
|
+
new require_x509_es.KeyUsagesExtension(require_x509_es.KeyUsageFlags.digitalSignature | require_x509_es.KeyUsageFlags.keyEncipherment, true),
|
|
474
|
+
new require_x509_es.ExtendedKeyUsageExtension([SERVER_AUTH_OID]),
|
|
475
|
+
await require_x509_es.SubjectKeyIdentifierExtension.create(csr.publicKey),
|
|
476
|
+
await require_x509_es.AuthorityKeyIdentifierExtension.create(ca.cert),
|
|
477
|
+
new require_x509_es.SubjectAlternativeNameExtension(subjectAltNames(identity))
|
|
478
|
+
];
|
|
479
|
+
return toCertPem(await require_x509_es.X509CertificateGenerator.create({
|
|
480
|
+
serialNumber: freshSerial(),
|
|
481
|
+
subject: `CN=${commonName}`,
|
|
482
|
+
issuer: ca.cert.subject,
|
|
483
|
+
notBefore: now,
|
|
484
|
+
notAfter: new Date(now.getTime() + validDays * MS_PER_DAY),
|
|
485
|
+
publicKey: csr.publicKey,
|
|
486
|
+
signingKey: ca.privateKey,
|
|
487
|
+
signingAlgorithm: SIGNING_ALGORITHM,
|
|
488
|
+
extensions
|
|
489
|
+
}));
|
|
490
|
+
}
|
|
462
491
|
//#endregion
|
|
463
492
|
//#region src/tls/cert-manager.ts
|
|
464
493
|
var DEFAULT_COMMON_NAME = "camstack.local";
|
|
@@ -941,6 +970,293 @@ function applyCertificateToListener(dataDir) {
|
|
|
941
970
|
return reloadHttpsListener(loadTlsCert(certPath, keyPath));
|
|
942
971
|
}
|
|
943
972
|
//#endregion
|
|
973
|
+
//#region src/tls/agent-https.ts
|
|
974
|
+
/**
|
|
975
|
+
* HTTPS for agents (D856): a leaf from the hub's LOCAL CA, so a device that
|
|
976
|
+
* trusts the hub root trusts the agents too.
|
|
977
|
+
*
|
|
978
|
+
* The agent generates its key and a CSR; the hub signs only the PUBLIC key under
|
|
979
|
+
* the local CA and returns the certificate - no private key ever crosses the mesh.
|
|
980
|
+
* The agent keeps ONE file, `tls/agent-https.json` (0600: key + leaf + the pinned CA),
|
|
981
|
+
* written atomically (temp file + rename) so a crash can never leave a key that does
|
|
982
|
+
* not match its certificate. It persists no database.
|
|
983
|
+
*
|
|
984
|
+
* What the hub is willing to certify (the smaller safe option). The local CA is trusted
|
|
985
|
+
* by every device that trusts the hub, so a compromised agent must not obtain a name it
|
|
986
|
+
* does not own. The hub signs ONLY what it knows by itself:
|
|
987
|
+
* - the agent's node id (unique in the cluster), when it is a local-only name;
|
|
988
|
+
* - the remote address of the agent's LIVE mesh socket as the hub sees it, when it is
|
|
989
|
+
* a private address - never an address the agent reports.
|
|
990
|
+
* Anything the agent reports (host names, interface addresses) is DROPPED and logged:
|
|
991
|
+
* extra self-reported names would need an explicit operator approval flow, which does
|
|
992
|
+
* not exist, so they are not certified. A name the hub's own certificate holds, or one
|
|
993
|
+
* already certified to another node, is never signed (a NAT'd pair of agents would
|
|
994
|
+
* otherwise share an address).
|
|
995
|
+
*/
|
|
996
|
+
require_x509_es.cryptoProvider.set(node_crypto.webcrypto);
|
|
997
|
+
function agentHttpsPaths(dataDir) {
|
|
998
|
+
const dir = (0, node_path.join)(dataDir, "tls");
|
|
999
|
+
return {
|
|
1000
|
+
dir,
|
|
1001
|
+
bundlePath: (0, node_path.join)(dir, "agent-https.json"),
|
|
1002
|
+
certPath: (0, node_path.join)(dir, "agent.crt"),
|
|
1003
|
+
pendingKeyPath: (0, node_path.join)(dir, "agent.key.pending")
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* The material on disk, or `null` while no hub-issued leaf is installed. A bundle that
|
|
1008
|
+
* cannot be parsed is reported through `onCorrupt` and treated as absent: a bad file must
|
|
1009
|
+
* never take the agent API down.
|
|
1010
|
+
*/
|
|
1011
|
+
function loadAgentHttps(dataDir, onCorrupt) {
|
|
1012
|
+
const { bundlePath } = agentHttpsPaths(dataDir);
|
|
1013
|
+
if (!(0, node_fs.existsSync)(bundlePath)) return null;
|
|
1014
|
+
try {
|
|
1015
|
+
const parsed = JSON.parse((0, node_fs.readFileSync)(bundlePath, "utf8"));
|
|
1016
|
+
const field = (name) => {
|
|
1017
|
+
const v = parsed !== null && typeof parsed === "object" ? Reflect.get(parsed, name) : null;
|
|
1018
|
+
return typeof v === "string" && v.length > 0 ? v : null;
|
|
1019
|
+
};
|
|
1020
|
+
const keyPem = field("keyPem");
|
|
1021
|
+
const certPem = field("certPem");
|
|
1022
|
+
const caCertPem = field("caCertPem");
|
|
1023
|
+
if (keyPem === null || certPem === null || caCertPem === null) {
|
|
1024
|
+
onCorrupt?.("the bundle is missing a field");
|
|
1025
|
+
return null;
|
|
1026
|
+
}
|
|
1027
|
+
if (!new node_crypto.X509Certificate(certPem).checkPrivateKey((0, node_crypto.createPrivateKey)(keyPem))) {
|
|
1028
|
+
onCorrupt?.("the certificate does not match the key");
|
|
1029
|
+
return null;
|
|
1030
|
+
}
|
|
1031
|
+
return {
|
|
1032
|
+
keyPem,
|
|
1033
|
+
certPem,
|
|
1034
|
+
caCertPem
|
|
1035
|
+
};
|
|
1036
|
+
} catch (error) {
|
|
1037
|
+
onCorrupt?.(error instanceof Error ? error.message : String(error));
|
|
1038
|
+
return null;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
function collectAgentNames(nodeId, hostFacts) {
|
|
1042
|
+
const identity = collectCertIdentity({
|
|
1043
|
+
commonName: nodeId,
|
|
1044
|
+
extraSans: [],
|
|
1045
|
+
hostFacts
|
|
1046
|
+
});
|
|
1047
|
+
return {
|
|
1048
|
+
hostnames: identity.dnsNames,
|
|
1049
|
+
addresses: identity.ipAddresses
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
/** SHA-256 of the hub local CA this agent pinned, or `null` while none is. */
|
|
1053
|
+
function pinnedHttpsCaFingerprint(dataDir) {
|
|
1054
|
+
const material = loadAgentHttps(dataDir);
|
|
1055
|
+
return material === null ? null : new node_crypto.X509Certificate(material.caCertPem).fingerprint256;
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* A fresh key pair (EC P-256, kept in `agent.key.pending`, 0600) and its CSR. The
|
|
1059
|
+
* key becomes `agent.key` only when the matching certificate is installed, so a
|
|
1060
|
+
* request that is never answered changes nothing that is being served.
|
|
1061
|
+
*/
|
|
1062
|
+
async function createAgentHttpsRequest(dataDir, nodeId, names) {
|
|
1063
|
+
const paths = agentHttpsPaths(dataDir);
|
|
1064
|
+
(0, node_fs.mkdirSync)(paths.dir, { recursive: true });
|
|
1065
|
+
const keys = await node_crypto.webcrypto.subtle.generateKey({
|
|
1066
|
+
name: "ECDSA",
|
|
1067
|
+
namedCurve: "P-256"
|
|
1068
|
+
}, true, ["sign", "verify"]);
|
|
1069
|
+
const csr = await require_x509_es.Pkcs10CertificateRequestGenerator.create({
|
|
1070
|
+
name: `CN=${nodeId}`,
|
|
1071
|
+
keys,
|
|
1072
|
+
signingAlgorithm: {
|
|
1073
|
+
name: "ECDSA",
|
|
1074
|
+
hash: "SHA-256"
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
const pkcs8 = await node_crypto.webcrypto.subtle.exportKey("pkcs8", keys.privateKey);
|
|
1078
|
+
(0, _camstack_types_node.writeOwnerOnlyFileSync)(paths.pendingKeyPath, `${require_x509_es.PemConverter.encode(pkcs8, "PRIVATE KEY")}\n`);
|
|
1079
|
+
return {
|
|
1080
|
+
csrPem: `${csr.toString("pem")}\n`,
|
|
1081
|
+
...names
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
var AgentHttpsInstallError = class extends Error {
|
|
1085
|
+
constructor(message) {
|
|
1086
|
+
super(message);
|
|
1087
|
+
this.name = "AgentHttpsInstallError";
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
/**
|
|
1091
|
+
* Install the hub-signed leaf for the pending key. Refused when it is not for that key, when
|
|
1092
|
+
* it is not signed by the CA it came with, or - once a CA is pinned - when that CA is not the
|
|
1093
|
+
* one this agent already pinned (the CA that arrives with a later leaf is never trusted on its
|
|
1094
|
+
* own say: the first one came over the authenticated mesh, every later leaf must chain to it).
|
|
1095
|
+
* Key, leaf and CA are written as ONE file, atomically.
|
|
1096
|
+
*/
|
|
1097
|
+
function installAgentHttps(dataDir, input) {
|
|
1098
|
+
const paths = agentHttpsPaths(dataDir);
|
|
1099
|
+
if (!(0, node_fs.existsSync)(paths.pendingKeyPath)) throw new AgentHttpsInstallError("no pending key: prepareHttpsCertificate was not called");
|
|
1100
|
+
const keyPem = (0, node_fs.readFileSync)(paths.pendingKeyPath, "utf8");
|
|
1101
|
+
let leaf;
|
|
1102
|
+
let ca;
|
|
1103
|
+
try {
|
|
1104
|
+
leaf = new node_crypto.X509Certificate(input.certPem);
|
|
1105
|
+
ca = new node_crypto.X509Certificate(input.caCertPem);
|
|
1106
|
+
} catch {
|
|
1107
|
+
throw new AgentHttpsInstallError("the certificate or the CA is not a valid PEM certificate");
|
|
1108
|
+
}
|
|
1109
|
+
if (!leaf.checkPrivateKey((0, node_crypto.createPrivateKey)(keyPem))) throw new AgentHttpsInstallError("the certificate is not for this node's pending key");
|
|
1110
|
+
const pinned = loadAgentHttps(dataDir);
|
|
1111
|
+
if (pinned !== null && input.caRotation !== true) {
|
|
1112
|
+
const pinnedCa = new node_crypto.X509Certificate(pinned.caCertPem);
|
|
1113
|
+
if (pinnedCa.fingerprint256 !== ca.fingerprint256 || !leaf.verify(pinnedCa.publicKey)) throw new AgentHttpsInstallError("the leaf does not chain to the hub local CA this agent pinned at its first install (if the hub rotated its local CA, renew HTTPS from the hub - it signals the rotation)");
|
|
1114
|
+
}
|
|
1115
|
+
if (!leaf.verify(ca.publicKey)) throw new AgentHttpsInstallError("the certificate is not signed by the CA that came with it");
|
|
1116
|
+
if (new Date(leaf.validTo).getTime() <= Date.now()) throw new AgentHttpsInstallError("the certificate is already expired");
|
|
1117
|
+
(0, node_fs.mkdirSync)(paths.dir, { recursive: true });
|
|
1118
|
+
const tmp = `${paths.bundlePath}.tmp`;
|
|
1119
|
+
(0, _camstack_types_node.writeOwnerOnlyFileSync)(tmp, JSON.stringify({
|
|
1120
|
+
keyPem,
|
|
1121
|
+
certPem: input.certPem,
|
|
1122
|
+
caCertPem: input.caCertPem
|
|
1123
|
+
}));
|
|
1124
|
+
(0, node_fs.renameSync)(tmp, paths.bundlePath);
|
|
1125
|
+
(0, node_fs.chmodSync)(paths.bundlePath, 384);
|
|
1126
|
+
writeFileAtomic(paths.certPath, input.certPem.endsWith("\n") ? input.certPem : `${input.certPem}\n`);
|
|
1127
|
+
return {
|
|
1128
|
+
keyPem,
|
|
1129
|
+
certPem: input.certPem,
|
|
1130
|
+
caCertPem: input.caCertPem
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
function writeFileAtomic(path, content) {
|
|
1134
|
+
const tmp = `${path}.tmp`;
|
|
1135
|
+
(0, node_fs.writeFileSync)(tmp, content);
|
|
1136
|
+
(0, node_fs.renameSync)(tmp, path);
|
|
1137
|
+
}
|
|
1138
|
+
/** Fingerprint, issuer, SANs and validity of a served leaf. */
|
|
1139
|
+
function describeHttpsLeaf(certPem) {
|
|
1140
|
+
const x509 = new node_crypto.X509Certificate(certPem);
|
|
1141
|
+
return {
|
|
1142
|
+
fingerprintSha256: x509.fingerprint256,
|
|
1143
|
+
issuer: x509.issuer.replaceAll("\n", ", "),
|
|
1144
|
+
sans: (x509.subjectAltName ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0),
|
|
1145
|
+
notBefore: Date.parse(x509.validFrom),
|
|
1146
|
+
notAfter: Date.parse(x509.validTo)
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
var LOCAL_SUFFIXES = [
|
|
1150
|
+
".local",
|
|
1151
|
+
".lan",
|
|
1152
|
+
".home.arpa",
|
|
1153
|
+
".internal",
|
|
1154
|
+
".localdomain"
|
|
1155
|
+
];
|
|
1156
|
+
var LABEL_RE = /^(?!-)[a-z0-9-]{1,63}(?<!-)$/i;
|
|
1157
|
+
/** Private-network IPv4/IPv6: RFC 1918, CGNAT (Tailscale), link-local, ULA. */
|
|
1158
|
+
function isPrivateAddress(address) {
|
|
1159
|
+
const family = (0, node_net.isIP)(address);
|
|
1160
|
+
if (family === 4) {
|
|
1161
|
+
const [a, b] = address.split(".").map(Number);
|
|
1162
|
+
return a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 100 && b >= 64 && b <= 127 || a === 169 && b === 254;
|
|
1163
|
+
}
|
|
1164
|
+
if (family === 6) {
|
|
1165
|
+
const lower = address.toLowerCase();
|
|
1166
|
+
return lower.startsWith("fc") || lower.startsWith("fd") || lower.startsWith("fe80:");
|
|
1167
|
+
}
|
|
1168
|
+
return false;
|
|
1169
|
+
}
|
|
1170
|
+
/** The default docker bridge network (172.17.0.0/16): identical on every host, so it names nothing. */
|
|
1171
|
+
function isContainerBridgeAddress(address) {
|
|
1172
|
+
return /^172\.17\.\d{1,3}\.\d{1,3}$/.test(address);
|
|
1173
|
+
}
|
|
1174
|
+
/** A name that cannot collide with public DNS. */
|
|
1175
|
+
function isLocalOnlyHostname(name) {
|
|
1176
|
+
const lower = name.toLowerCase();
|
|
1177
|
+
if (lower.length === 0 || lower.length > 253) return false;
|
|
1178
|
+
if (!lower.includes(".")) return LABEL_RE.test(lower);
|
|
1179
|
+
if (!LOCAL_SUFFIXES.some((suffix) => lower.endsWith(suffix))) return false;
|
|
1180
|
+
return lower.split(".").every((label) => LABEL_RE.test(label));
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* The names the hub will sign for `nodeId`: its node id and the address of its live mesh
|
|
1184
|
+
* socket - nothing the agent says about itself. `excluded` holds every name already taken
|
|
1185
|
+
* (the hub's own certificate, other nodes' leaves); `reported` is what the agent claimed,
|
|
1186
|
+
* listed only so it can be logged as dropped.
|
|
1187
|
+
*/
|
|
1188
|
+
function acceptAgentSans(input) {
|
|
1189
|
+
const excluded = input.excluded ?? /* @__PURE__ */ new Set();
|
|
1190
|
+
const taken = (name) => excluded.has(name.toLowerCase());
|
|
1191
|
+
const rejected = (input.reported ?? []).map((n) => `${n} (self-reported, not certified)`);
|
|
1192
|
+
const dns = [];
|
|
1193
|
+
const ips = [];
|
|
1194
|
+
const id = input.nodeId.toLowerCase();
|
|
1195
|
+
if (id !== "hub" && isLocalOnlyHostname(id) && !taken(id)) dns.push(id);
|
|
1196
|
+
else rejected.push(`${input.nodeId} (node id: not a usable name)`);
|
|
1197
|
+
const address = input.observedAddress?.replace(/^::ffff:/, "");
|
|
1198
|
+
if (address !== void 0) if (address.includes("%")) rejected.push(`${address} (observed address: zone-scoped, only the node id is certified)`);
|
|
1199
|
+
else if (isContainerBridgeAddress(address)) rejected.push(`${address} (observed address: a container bridge address, only the node id is certified)`);
|
|
1200
|
+
else if ((0, node_net.isIP)(address) !== 0 && isPrivateAddress(address) && !taken(address)) ips.push(address);
|
|
1201
|
+
else rejected.push(`${address} (observed address: not private or already certified)`);
|
|
1202
|
+
return {
|
|
1203
|
+
identity: {
|
|
1204
|
+
dnsNames: dns,
|
|
1205
|
+
ipAddresses: ips,
|
|
1206
|
+
requiredDnsNames: dns,
|
|
1207
|
+
requiredIpAddresses: ips
|
|
1208
|
+
},
|
|
1209
|
+
accepted: [...dns, ...ips],
|
|
1210
|
+
rejected
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
/** The SAN values (lower-cased) of a certificate PEM: DNS names and IP addresses. */
|
|
1214
|
+
function certificateNames(certPem) {
|
|
1215
|
+
const names = /* @__PURE__ */ new Set();
|
|
1216
|
+
const san = new node_crypto.X509Certificate(certPem).subjectAltName ?? "";
|
|
1217
|
+
for (const part of san.split(",")) {
|
|
1218
|
+
const [, value] = part.trim().split(/:(.*)/s);
|
|
1219
|
+
if (value !== void 0 && value.length > 0) names.add(value.toLowerCase());
|
|
1220
|
+
}
|
|
1221
|
+
return names;
|
|
1222
|
+
}
|
|
1223
|
+
/**
|
|
1224
|
+
* Hub side: sign an agent's CSR under the local CA in `<hubDataDir>/tls`. Throws when
|
|
1225
|
+
* there is no local CA to sign with or no name survives the policy.
|
|
1226
|
+
*/
|
|
1227
|
+
async function issueAgentHttpsLeaf(input) {
|
|
1228
|
+
const caCertPath = (0, node_path.join)(input.hubDataDir, "tls", "camstack-ca.crt");
|
|
1229
|
+
const caKeyPath = (0, node_path.join)(input.hubDataDir, "tls", "camstack-ca.key");
|
|
1230
|
+
const hubLeafPath = (0, node_path.join)(input.hubDataDir, "tls", "camstack.crt");
|
|
1231
|
+
if (!(0, node_fs.existsSync)(caCertPath) || !(0, node_fs.existsSync)(caKeyPath)) throw new Error("the hub has no local CA to sign with (no tls/camstack-ca.crt + key)");
|
|
1232
|
+
const excluded = new Set(input.takenByOthers ?? []);
|
|
1233
|
+
if ((0, node_fs.existsSync)(hubLeafPath)) for (const name of certificateNames((0, node_fs.readFileSync)(hubLeafPath, "utf8"))) excluded.add(name);
|
|
1234
|
+
const sans = acceptAgentSans({
|
|
1235
|
+
nodeId: input.nodeId,
|
|
1236
|
+
observedAddress: input.observedAddress,
|
|
1237
|
+
excluded,
|
|
1238
|
+
...input.reportedNames !== void 0 ? { reported: input.reportedNames } : {}
|
|
1239
|
+
});
|
|
1240
|
+
if (sans.accepted.length === 0) throw new Error(`no name of node '${input.nodeId}' can be certified (${sans.rejected.join("; ") || "nothing offered"})`);
|
|
1241
|
+
const ca = await loadCa((0, node_fs.readFileSync)(caCertPath, "utf8"), (0, node_fs.readFileSync)(caKeyPath, "utf8"));
|
|
1242
|
+
return {
|
|
1243
|
+
certPem: await issueLeafFromCsr(ca, input.csrPem, sans.identity, input.nodeId, 397, input.now ?? /* @__PURE__ */ new Date()),
|
|
1244
|
+
caCertPem: ca.certPem,
|
|
1245
|
+
accepted: sans.accepted,
|
|
1246
|
+
rejected: sans.rejected
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
/** SHA-256 of a PEM (for logs that must not print a certificate). */
|
|
1250
|
+
function pemDigest(pem) {
|
|
1251
|
+
return (0, node_crypto.createHash)("sha256").update(pem).digest("hex").slice(0, 16);
|
|
1252
|
+
}
|
|
1253
|
+
//#endregion
|
|
1254
|
+
Object.defineProperty(exports, "AgentHttpsInstallError", {
|
|
1255
|
+
enumerable: true,
|
|
1256
|
+
get: function() {
|
|
1257
|
+
return AgentHttpsInstallError;
|
|
1258
|
+
}
|
|
1259
|
+
});
|
|
944
1260
|
Object.defineProperty(exports, "CA_COMMON_NAME", {
|
|
945
1261
|
enumerable: true,
|
|
946
1262
|
get: function() {
|
|
@@ -983,6 +1299,18 @@ Object.defineProperty(exports, "UPLOADED_CA_FILE", {
|
|
|
983
1299
|
return UPLOADED_CA_FILE;
|
|
984
1300
|
}
|
|
985
1301
|
});
|
|
1302
|
+
Object.defineProperty(exports, "acceptAgentSans", {
|
|
1303
|
+
enumerable: true,
|
|
1304
|
+
get: function() {
|
|
1305
|
+
return acceptAgentSans;
|
|
1306
|
+
}
|
|
1307
|
+
});
|
|
1308
|
+
Object.defineProperty(exports, "agentHttpsPaths", {
|
|
1309
|
+
enumerable: true,
|
|
1310
|
+
get: function() {
|
|
1311
|
+
return agentHttpsPaths;
|
|
1312
|
+
}
|
|
1313
|
+
});
|
|
986
1314
|
Object.defineProperty(exports, "allFamiliesListenHost", {
|
|
987
1315
|
enumerable: true,
|
|
988
1316
|
get: function() {
|
|
@@ -1007,6 +1335,12 @@ Object.defineProperty(exports, "bindPendingLanHttp", {
|
|
|
1007
1335
|
return bindPendingLanHttp;
|
|
1008
1336
|
}
|
|
1009
1337
|
});
|
|
1338
|
+
Object.defineProperty(exports, "certificateNames", {
|
|
1339
|
+
enumerable: true,
|
|
1340
|
+
get: function() {
|
|
1341
|
+
return certificateNames;
|
|
1342
|
+
}
|
|
1343
|
+
});
|
|
1010
1344
|
Object.defineProperty(exports, "clearHttpsListener", {
|
|
1011
1345
|
enumerable: true,
|
|
1012
1346
|
get: function() {
|
|
@@ -1019,12 +1353,30 @@ Object.defineProperty(exports, "closeLanHttp", {
|
|
|
1019
1353
|
return closeLanHttp;
|
|
1020
1354
|
}
|
|
1021
1355
|
});
|
|
1356
|
+
Object.defineProperty(exports, "collectAgentNames", {
|
|
1357
|
+
enumerable: true,
|
|
1358
|
+
get: function() {
|
|
1359
|
+
return collectAgentNames;
|
|
1360
|
+
}
|
|
1361
|
+
});
|
|
1022
1362
|
Object.defineProperty(exports, "collectCertIdentity", {
|
|
1023
1363
|
enumerable: true,
|
|
1024
1364
|
get: function() {
|
|
1025
1365
|
return collectCertIdentity;
|
|
1026
1366
|
}
|
|
1027
1367
|
});
|
|
1368
|
+
Object.defineProperty(exports, "createAgentHttpsRequest", {
|
|
1369
|
+
enumerable: true,
|
|
1370
|
+
get: function() {
|
|
1371
|
+
return createAgentHttpsRequest;
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1374
|
+
Object.defineProperty(exports, "describeHttpsLeaf", {
|
|
1375
|
+
enumerable: true,
|
|
1376
|
+
get: function() {
|
|
1377
|
+
return describeHttpsLeaf;
|
|
1378
|
+
}
|
|
1379
|
+
});
|
|
1028
1380
|
Object.defineProperty(exports, "ensureTlsCert", {
|
|
1029
1381
|
enumerable: true,
|
|
1030
1382
|
get: function() {
|
|
@@ -1037,18 +1389,66 @@ Object.defineProperty(exports, "evaluateExistingCert", {
|
|
|
1037
1389
|
return evaluateExistingCert;
|
|
1038
1390
|
}
|
|
1039
1391
|
});
|
|
1392
|
+
Object.defineProperty(exports, "installAgentHttps", {
|
|
1393
|
+
enumerable: true,
|
|
1394
|
+
get: function() {
|
|
1395
|
+
return installAgentHttps;
|
|
1396
|
+
}
|
|
1397
|
+
});
|
|
1398
|
+
Object.defineProperty(exports, "isContainerBridgeAddress", {
|
|
1399
|
+
enumerable: true,
|
|
1400
|
+
get: function() {
|
|
1401
|
+
return isContainerBridgeAddress;
|
|
1402
|
+
}
|
|
1403
|
+
});
|
|
1404
|
+
Object.defineProperty(exports, "isLocalOnlyHostname", {
|
|
1405
|
+
enumerable: true,
|
|
1406
|
+
get: function() {
|
|
1407
|
+
return isLocalOnlyHostname;
|
|
1408
|
+
}
|
|
1409
|
+
});
|
|
1410
|
+
Object.defineProperty(exports, "isPrivateAddress", {
|
|
1411
|
+
enumerable: true,
|
|
1412
|
+
get: function() {
|
|
1413
|
+
return isPrivateAddress;
|
|
1414
|
+
}
|
|
1415
|
+
});
|
|
1416
|
+
Object.defineProperty(exports, "issueAgentHttpsLeaf", {
|
|
1417
|
+
enumerable: true,
|
|
1418
|
+
get: function() {
|
|
1419
|
+
return issueAgentHttpsLeaf;
|
|
1420
|
+
}
|
|
1421
|
+
});
|
|
1040
1422
|
Object.defineProperty(exports, "leafFingerprintOf", {
|
|
1041
1423
|
enumerable: true,
|
|
1042
1424
|
get: function() {
|
|
1043
1425
|
return leafFingerprintOf;
|
|
1044
1426
|
}
|
|
1045
1427
|
});
|
|
1428
|
+
Object.defineProperty(exports, "loadAgentHttps", {
|
|
1429
|
+
enumerable: true,
|
|
1430
|
+
get: function() {
|
|
1431
|
+
return loadAgentHttps;
|
|
1432
|
+
}
|
|
1433
|
+
});
|
|
1046
1434
|
Object.defineProperty(exports, "loadTlsCert", {
|
|
1047
1435
|
enumerable: true,
|
|
1048
1436
|
get: function() {
|
|
1049
1437
|
return loadTlsCert;
|
|
1050
1438
|
}
|
|
1051
1439
|
});
|
|
1440
|
+
Object.defineProperty(exports, "pemDigest", {
|
|
1441
|
+
enumerable: true,
|
|
1442
|
+
get: function() {
|
|
1443
|
+
return pemDigest;
|
|
1444
|
+
}
|
|
1445
|
+
});
|
|
1446
|
+
Object.defineProperty(exports, "pinnedHttpsCaFingerprint", {
|
|
1447
|
+
enumerable: true,
|
|
1448
|
+
get: function() {
|
|
1449
|
+
return pinnedHttpsCaFingerprint;
|
|
1450
|
+
}
|
|
1451
|
+
});
|
|
1052
1452
|
Object.defineProperty(exports, "prepareBootTls", {
|
|
1053
1453
|
enumerable: true,
|
|
1054
1454
|
get: function() {
|
|
@@ -1133,6 +1533,12 @@ Object.defineProperty(exports, "storeUploadedCertificate", {
|
|
|
1133
1533
|
return storeUploadedCertificate;
|
|
1134
1534
|
}
|
|
1135
1535
|
});
|
|
1536
|
+
Object.defineProperty(exports, "systemHostFacts", {
|
|
1537
|
+
enumerable: true,
|
|
1538
|
+
get: function() {
|
|
1539
|
+
return systemHostFacts;
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1136
1542
|
Object.defineProperty(exports, "validateUploadedTls", {
|
|
1137
1543
|
enumerable: true,
|
|
1138
1544
|
get: function() {
|