infracensus-collector 1.3.0 → 1.4.1

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 (47) hide show
  1. package/dist/a10-index.js +2 -2
  2. package/dist/azure-index.js +6 -6
  3. package/dist/{chunk-UQJQKUAU.js → chunk-5ULN62DY.js} +2 -2
  4. package/dist/{chunk-QNHCMF7Z.js → chunk-62F6ZKS6.js} +2 -2
  5. package/dist/{chunk-XAV5QW4Y.js → chunk-7T2LVP6E.js} +270 -17
  6. package/dist/chunk-7T2LVP6E.js.map +7 -0
  7. package/dist/{chunk-6GUY42OX.js → chunk-CBNDR2GQ.js} +2 -2
  8. package/dist/{chunk-GD4YRP6B.js → chunk-CQYV43KA.js} +2 -2
  9. package/dist/{chunk-JYSJ52ZS.js → chunk-CZST5VMO.js} +2 -2
  10. package/dist/{chunk-RCSMKY5W.js → chunk-F5DB2SXP.js} +10 -3600
  11. package/dist/chunk-F5DB2SXP.js.map +7 -0
  12. package/dist/{chunk-I6CYXRMJ.js → chunk-IVE2QR5M.js} +2 -2
  13. package/dist/{chunk-NM4U7JPT.js → chunk-PFZRGWEF.js} +2 -2
  14. package/dist/{chunk-JB7TTPKL.js → chunk-QITZIOFD.js} +9 -7
  15. package/dist/{chunk-JB7TTPKL.js.map → chunk-QITZIOFD.js.map} +1 -1
  16. package/dist/{chunk-P635KMDV.js → chunk-RKZ6PTVB.js} +2 -2
  17. package/dist/{chunk-4MDLMXYN.js → chunk-TQXSTE4N.js} +3 -3
  18. package/dist/{chunk-ZTA7536B.js → chunk-UV737N5S.js} +40 -25
  19. package/dist/chunk-UV737N5S.js.map +7 -0
  20. package/dist/cli.js +5 -5
  21. package/dist/{config-WRCKIKEN.js → config-OIFTEYLP.js} +3 -3
  22. package/dist/configimport-index.js +6 -6
  23. package/dist/hyperv-index.js +6 -6
  24. package/dist/identity-index.js +6 -6
  25. package/dist/index.js +378 -134
  26. package/dist/index.js.map +4 -4
  27. package/dist/sensitive-data-index.js +5 -5
  28. package/dist/sql-index.js +6 -6
  29. package/dist/{status-3XYANSK6.js → status-MWP2NOBY.js} +5 -5
  30. package/dist/vmware-index.js +6 -6
  31. package/dist/voice-index.js +6 -6
  32. package/package.json +1 -1
  33. package/scripts/doctor.mjs +39 -1
  34. package/dist/chunk-RCSMKY5W.js.map +0 -7
  35. package/dist/chunk-XAV5QW4Y.js.map +0 -7
  36. package/dist/chunk-ZTA7536B.js.map +0 -7
  37. /package/dist/{chunk-UQJQKUAU.js.map → chunk-5ULN62DY.js.map} +0 -0
  38. /package/dist/{chunk-QNHCMF7Z.js.map → chunk-62F6ZKS6.js.map} +0 -0
  39. /package/dist/{chunk-6GUY42OX.js.map → chunk-CBNDR2GQ.js.map} +0 -0
  40. /package/dist/{chunk-GD4YRP6B.js.map → chunk-CQYV43KA.js.map} +0 -0
  41. /package/dist/{chunk-JYSJ52ZS.js.map → chunk-CZST5VMO.js.map} +0 -0
  42. /package/dist/{chunk-I6CYXRMJ.js.map → chunk-IVE2QR5M.js.map} +0 -0
  43. /package/dist/{chunk-NM4U7JPT.js.map → chunk-PFZRGWEF.js.map} +0 -0
  44. /package/dist/{chunk-P635KMDV.js.map → chunk-RKZ6PTVB.js.map} +0 -0
  45. /package/dist/{chunk-4MDLMXYN.js.map → chunk-TQXSTE4N.js.map} +0 -0
  46. /package/dist/{config-WRCKIKEN.js.map → config-OIFTEYLP.js.map} +0 -0
  47. /package/dist/{status-3XYANSK6.js.map → status-MWP2NOBY.js.map} +0 -0
package/dist/a10-index.js CHANGED
@@ -4,10 +4,10 @@ import {
4
4
  } from "./chunk-MN4JODQW.js";
5
5
  import {
6
6
  requireEntitlementToScan
7
- } from "./chunk-ZTA7536B.js";
7
+ } from "./chunk-UV737N5S.js";
8
8
  import {
9
9
  ROUTES
10
- } from "./chunk-RCSMKY5W.js";
10
+ } from "./chunk-F5DB2SXP.js";
11
11
  import "./chunk-CW3YCPEC.js";
12
12
 
13
13
  // src/a10-index.ts
@@ -2,18 +2,18 @@ import {
2
2
  ArmClient,
3
3
  buildAzureInventory,
4
4
  listAccessibleSubscriptions
5
- } from "./chunk-JYSJ52ZS.js";
5
+ } from "./chunk-CZST5VMO.js";
6
6
  import {
7
7
  pushAzureInventory
8
- } from "./chunk-JB7TTPKL.js";
9
- import "./chunk-6GUY42OX.js";
8
+ } from "./chunk-QITZIOFD.js";
9
+ import "./chunk-CBNDR2GQ.js";
10
10
  import {
11
11
  loadAzureConfig
12
- } from "./chunk-GD4YRP6B.js";
12
+ } from "./chunk-CQYV43KA.js";
13
13
  import {
14
14
  requireEntitlementToScan
15
- } from "./chunk-ZTA7536B.js";
16
- import "./chunk-RCSMKY5W.js";
15
+ } from "./chunk-UV737N5S.js";
16
+ import "./chunk-F5DB2SXP.js";
17
17
  import "./chunk-CW3YCPEC.js";
18
18
 
19
19
  // src/azure-index.ts
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  HypervIngestBody
3
- } from "./chunk-RCSMKY5W.js";
3
+ } from "./chunk-F5DB2SXP.js";
4
4
 
5
5
  // src/hyperv/scan.ts
6
6
  import { spawn } from "node:child_process";
@@ -455,4 +455,4 @@ export {
455
455
  scanHypervTargets,
456
456
  buildHypervInventory
457
457
  };
458
- //# sourceMappingURL=chunk-UQJQKUAU.js.map
458
+ //# sourceMappingURL=chunk-5ULN62DY.js.map
@@ -13,7 +13,7 @@ import {
13
13
  VoiceRegion,
14
14
  VoiceSystem,
15
15
  WebexCalling
16
- } from "./chunk-RCSMKY5W.js";
16
+ } from "./chunk-F5DB2SXP.js";
17
17
 
18
18
  // src/voice/axl.ts
19
19
  import { XMLParser } from "fast-xml-parser";
@@ -1940,4 +1940,4 @@ export {
1940
1940
  buildUnityInventory,
1941
1941
  buildWebexInventory
1942
1942
  };
1943
- //# sourceMappingURL=chunk-QNHCMF7Z.js.map
1943
+ //# sourceMappingURL=chunk-62F6ZKS6.js.map
@@ -4,16 +4,15 @@ import {
4
4
  import {
5
5
  configFilePath,
6
6
  vaultSettings
7
- } from "./chunk-GD4YRP6B.js";
7
+ } from "./chunk-CQYV43KA.js";
8
8
  import {
9
- LocalCredentialSet,
10
- LocalVaultContents,
11
- LocalVaultEnvelope,
9
+ CredentialSetKind,
10
+ DomainCredentialSecret,
11
+ SshCredential,
12
12
  VaultProtection,
13
- localSecretSatisfiesKind,
14
- redactCredentialSet,
15
- viewCredentialSet
16
- } from "./chunk-RCSMKY5W.js";
13
+ WindowsCredential,
14
+ domainSecretSatisfiesKind
15
+ } from "./chunk-F5DB2SXP.js";
17
16
  import {
18
17
  PKG_ROOT
19
18
  } from "./chunk-CW3YCPEC.js";
@@ -23,6 +22,252 @@ import { readFileSync, writeFileSync, renameSync, existsSync as existsSync2 } fr
23
22
  import { dirname as dirname2, join } from "node:path";
24
23
  import { randomBytes as randomBytes4 } from "node:crypto";
25
24
 
25
+ // ../../packages/collector-contract/src/localVault.ts
26
+ import { z } from "zod";
27
+ var NetworkCredentialSecret = z.object({
28
+ /** SNMP read communities, tried in order. Each one IS a password. */
29
+ snmpCommunities: z.array(z.string()).max(50).default([]),
30
+ sshCredentials: z.array(SshCredential).max(50).default([]),
31
+ windowsCredentials: z.array(WindowsCredential).max(50).default([])
32
+ }).strict();
33
+ var LocalCredentialSecret = z.object({
34
+ /** Populated when kind === "network". */
35
+ network: NetworkCredentialSecret.optional(),
36
+ /** Populated for every other kind. Shape and rules unchanged from the plane. */
37
+ domain: DomainCredentialSecret.optional()
38
+ }).strict();
39
+ var CREDENTIAL_KIND_SHAPE = {
40
+ network: "network",
41
+ vcenter: "host",
42
+ hyperv: "host",
43
+ sql: "host",
44
+ loadbalancer: "host",
45
+ storage: "host",
46
+ firewall: "host",
47
+ cucm: "host",
48
+ unity: "host",
49
+ wlc: "host",
50
+ nac: "host",
51
+ smb: "host",
52
+ webex: "token",
53
+ graph: "tenant-app",
54
+ azure: "tenant-app"
55
+ };
56
+ function localSecretSatisfiesKind(kind, secret) {
57
+ if (CREDENTIAL_KIND_SHAPE[kind] === "network") {
58
+ const n = secret.network;
59
+ if (n === void 0) return false;
60
+ return n.snmpCommunities.length > 0 || n.sshCredentials.length > 0 || n.windowsCredentials.length > 0;
61
+ }
62
+ return domainSecretSatisfiesKind(kind, secret.domain ?? {});
63
+ }
64
+ var LocalCredentialSet = z.object({
65
+ /**
66
+ * Host-local id. Not a tenant id, not a plane id — it means something only
67
+ * inside this collector, which is what makes it safe to report upward and to
68
+ * name in `CreateScanJobBody.localCredentialSetId`.
69
+ */
70
+ id: z.string().min(1).max(200),
71
+ name: z.string().min(1).max(200),
72
+ /** Immutable after creation. Changing a kind changes which fields of the
73
+ * secret are meaningful, so it is a delete and a create, not an edit. */
74
+ kind: CredentialSetKind,
75
+ secret: LocalCredentialSecret,
76
+ createdAt: z.string(),
77
+ updatedAt: z.string(),
78
+ /** Last time a scan used it; null if never. Written by the collector. */
79
+ lastUsedAt: z.string().nullable().default(null)
80
+ }).strict();
81
+ var LocalVaultKdf = z.object({
82
+ algorithm: z.literal("scrypt"),
83
+ /** Base64. Per-vault, never shared, never derived from the hostname. */
84
+ salt: z.string().min(1),
85
+ /** CPU/memory cost. 2^15 at the time of writing. */
86
+ n: z.number().int().positive(),
87
+ r: z.number().int().positive(),
88
+ p: z.number().int().positive()
89
+ }).strict();
90
+ var LocalVaultEnvelope = z.object({
91
+ version: z.literal(1),
92
+ protection: VaultProtection,
93
+ /** Base64 DEK sealed by the OS keystore. Null only for `file-fallback`. */
94
+ wrappedKey: z.string().min(1).nullable(),
95
+ /** Present only for `file-fallback`. Null otherwise. */
96
+ kdf: LocalVaultKdf.nullable(),
97
+ /** Base64 AES-GCM nonce. Fresh on every write — never reused with one key. */
98
+ nonce: z.string().min(1),
99
+ /** Base64 AES-GCM ciphertext of a `LocalVaultContents`, tag included. */
100
+ ciphertext: z.string().min(1),
101
+ updatedAt: z.string()
102
+ }).strict().refine(
103
+ (v) => v.protection === "file-fallback" ? v.kdf !== null : v.wrappedKey !== null,
104
+ "an OS-keystore vault must carry a wrapped key; a file-fallback vault must carry its KDF parameters"
105
+ );
106
+ var LocalVaultContents = z.object({
107
+ version: z.literal(1),
108
+ sets: z.array(LocalCredentialSet).max(500),
109
+ /**
110
+ * THE COLLECTOR'S OWN KEY, WHICH IS NOT A CREDENTIAL SET.
111
+ *
112
+ * ── WHY IT IS HERE AND NOT IN `sets` ────────────────────────────────────
113
+ *
114
+ * It is the wrong shape and it would be in the wrong places. A
115
+ * `LocalCredentialSet` has a `CredentialSetKind`, and every kind is
116
+ * something a SCAN logs into — so a key that lives in `sets` appears in the
117
+ * localhost UI's credential list, in the report the control plane reads, and
118
+ * in `matchForScan`, where a network sweep could resolve to it. None of
119
+ * those are true of the collector's enrolment key, and each would have to be
120
+ * special-cased back out.
121
+ *
122
+ * ── WHY IT IS IN THE VAULT AT ALL ───────────────────────────────────────
123
+ *
124
+ * Because it was in `collector.config.json` in plaintext. The file is
125
+ * icacls'd to SYSTEM and Administrators and that is a real protection, but
126
+ * an ACL travels with a filesystem and not with a file: a backup, a copied
127
+ * folder, a support bundle, or a share that was set up in a hurry all carry
128
+ * the bytes somewhere the ACL is not. Sealed here it is bound to the OS
129
+ * keystore of the account and machine that wrote it, so the same copy is
130
+ * inert everywhere else.
131
+ *
132
+ * OPTIONAL, and it stays optional. A collector whose key is still in the
133
+ * config file works exactly as it did — the plaintext is a supported
134
+ * fallback, not a deprecated one, because the alternative is a host that
135
+ * cannot authenticate at all if its keystore is having a bad day.
136
+ */
137
+ collectorApiKey: z.string().min(1).optional()
138
+ }).strict();
139
+ var LocalCredentialSetView = z.object({
140
+ id: z.string(),
141
+ name: z.string(),
142
+ kind: CredentialSetKind,
143
+ /** Does it carry what its kind needs? Drives the UI's "incomplete" badge. */
144
+ usable: z.boolean(),
145
+ /** How many SNMP communities are stored. NEVER the community strings — an
146
+ * SNMP v2c community is the whole credential, not a username. */
147
+ snmpCommunityCount: z.number().int().nonnegative(),
148
+ /** Usernames only, in the order they are tried. Never a password. */
149
+ usernames: z.array(z.string()),
150
+ /** Endpoint metadata for the domain kinds. */
151
+ host: z.string().nullable(),
152
+ port: z.number().int().positive().nullable(),
153
+ insecure: z.boolean().nullable(),
154
+ vendor: z.string().nullable(),
155
+ /** The app registration id for graph/azure — public by design, unlike its
156
+ * secret. Null for every other kind. */
157
+ clientId: z.string().nullable(),
158
+ /** Whether a bearer token is stored. Never the token. */
159
+ hasToken: z.boolean(),
160
+ createdAt: z.string(),
161
+ updatedAt: z.string(),
162
+ lastUsedAt: z.string().nullable()
163
+ }).strict();
164
+ var LocalCredentialSetsResponse = z.object({ credentialSets: z.array(LocalCredentialSetView) }).strict();
165
+ function usernamesOf(set) {
166
+ const shape = CREDENTIAL_KIND_SHAPE[set.kind];
167
+ switch (shape) {
168
+ case "network": {
169
+ const n = set.secret.network;
170
+ if (n === void 0) return [];
171
+ return [
172
+ ...n.sshCredentials.map((c) => c.username),
173
+ ...n.windowsCredentials.map((c) => c.username)
174
+ ];
175
+ }
176
+ case "host":
177
+ return (set.secret.domain?.credentials ?? []).map((c) => c.username);
178
+ case "token":
179
+ return [];
180
+ case "tenant-app":
181
+ return set.secret.domain?.clientId ? [set.secret.domain.clientId] : [];
182
+ default: {
183
+ const unreachable = shape;
184
+ return unreachable;
185
+ }
186
+ }
187
+ }
188
+ function loginCountOf(set) {
189
+ const shape = CREDENTIAL_KIND_SHAPE[set.kind];
190
+ switch (shape) {
191
+ case "network": {
192
+ const n = set.secret.network;
193
+ if (n === void 0) return 0;
194
+ return n.sshCredentials.length + n.windowsCredentials.length;
195
+ }
196
+ case "host":
197
+ return set.secret.domain?.credentials?.length ?? 0;
198
+ case "token":
199
+ return set.secret.domain?.token ? 1 : 0;
200
+ case "tenant-app":
201
+ return set.secret.domain?.clientId && set.secret.domain?.clientSecret ? 1 : 0;
202
+ default: {
203
+ const unreachable = shape;
204
+ return unreachable;
205
+ }
206
+ }
207
+ }
208
+ function viewCredentialSet(set) {
209
+ const d = set.secret.domain;
210
+ const isTenantApp = CREDENTIAL_KIND_SHAPE[set.kind] === "tenant-app";
211
+ return {
212
+ id: set.id,
213
+ name: set.name,
214
+ kind: set.kind,
215
+ usable: localSecretSatisfiesKind(set.kind, set.secret),
216
+ snmpCommunityCount: set.secret.network?.snmpCommunities.length ?? 0,
217
+ usernames: usernamesOf(set),
218
+ host: d?.host ?? null,
219
+ port: d?.port ?? null,
220
+ insecure: d?.insecure ?? null,
221
+ vendor: d?.vendor ?? null,
222
+ clientId: isTenantApp ? d?.clientId ?? null : null,
223
+ hasToken: Boolean(d?.token),
224
+ createdAt: set.createdAt,
225
+ updatedAt: set.updatedAt,
226
+ lastUsedAt: set.lastUsedAt
227
+ };
228
+ }
229
+ function redactCredentialSet(set) {
230
+ return {
231
+ id: set.id,
232
+ name: set.name,
233
+ kind: set.kind,
234
+ usable: localSecretSatisfiesKind(set.kind, set.secret),
235
+ snmpCommunityCount: set.secret.network?.snmpCommunities.length ?? 0,
236
+ loginCount: loginCountOf(set),
237
+ createdAt: set.createdAt,
238
+ updatedAt: set.updatedAt,
239
+ lastUsedAt: set.lastUsedAt
240
+ };
241
+ }
242
+ var CreateLocalCredentialSetBody = z.object({
243
+ name: z.string().min(1).max(200),
244
+ kind: CredentialSetKind.default("network"),
245
+ secret: LocalCredentialSecret
246
+ }).strict().refine(
247
+ (b) => localSecretSatisfiesKind(b.kind, b.secret),
248
+ "the credential set does not carry the material its kind requires: the network kind needs at least one community or login; host kinds need credentials; webex needs a token; graph and azure need a clientId and clientSecret"
249
+ ).refine(
250
+ (b) => CREDENTIAL_KIND_SHAPE[b.kind] === "network" === (b.secret.network !== void 0),
251
+ "a network credential set carries `secret.network`; every other kind carries `secret.domain`"
252
+ );
253
+ var UpdateLocalCredentialSetBody = z.object({
254
+ id: z.string().min(1).max(200),
255
+ name: z.string().min(1).max(200).optional(),
256
+ secret: LocalCredentialSecret.optional()
257
+ }).strict();
258
+ var EphemeralCredentialSet = z.object({
259
+ kind: CredentialSetKind,
260
+ secret: LocalCredentialSecret
261
+ }).strict().refine(
262
+ (b) => localSecretSatisfiesKind(b.kind, b.secret),
263
+ "the credentials entered for this run do not carry the material their kind requires"
264
+ ).refine(
265
+ (b) => CREDENTIAL_KIND_SHAPE[b.kind] === "network" === (b.secret.network !== void 0),
266
+ "network credentials carry `secret.network`; every other kind carries `secret.domain`"
267
+ );
268
+ var LocalCredentialSetMutationResult = z.object({ ok: z.literal(true), credentialSet: LocalCredentialSetView }).strict();
269
+ var DOMAIN_CREDENTIAL_KINDS = Object.keys(CREDENTIAL_KIND_SHAPE).filter((k) => CREDENTIAL_KIND_SHAPE[k] !== "network");
270
+
26
271
  // src/vault/store.ts
27
272
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
28
273
  import { existsSync } from "node:fs";
@@ -899,6 +1144,9 @@ async function primeCollectorApiKey(opts = {}) {
899
1144
  void err;
900
1145
  return "absent";
901
1146
  }
1147
+ if (settings.apiKeyInFile) {
1148
+ return "config-file";
1149
+ }
902
1150
  if (existsSync2(settings.vaultPath)) {
903
1151
  const sealed = await readSealedKey(settings, opts);
904
1152
  if (sealed.opened && sealed.key !== null) {
@@ -913,7 +1161,7 @@ async function primeCollectorApiKey(opts = {}) {
913
1161
  return "unreadable";
914
1162
  }
915
1163
  }
916
- return settings.apiKeyInFile ? "config-file" : "absent";
1164
+ return "absent";
917
1165
  }
918
1166
  async function readSealedKey(settings, opts) {
919
1167
  const open = opts.open ?? openVault;
@@ -937,14 +1185,14 @@ async function migrateCollectorApiKey(vault, opts = {}) {
937
1185
  const plaintext = settings.apiKeyInFile;
938
1186
  if (!plaintext) return { message: null, migrated: false };
939
1187
  const existing = await vault.readCollectorApiKey();
1188
+ if (existing !== null && existing === plaintext) {
1189
+ return finishByRemovingPlaintext(env, log);
1190
+ }
940
1191
  if (existing !== null) {
941
- if (existing === plaintext) {
942
- return finishByRemovingPlaintext(env, log);
943
- }
944
- return {
945
- message: `this collector holds a sealed key AND a different plaintext one in ${configFilePath(env)}. Neither was changed: only you can say which is current. Remove the wrong one.`,
946
- migrated: false
947
- };
1192
+ log?.(
1193
+ "info",
1194
+ "collector key: a newly enrolled key replaces the one sealed in the vault"
1195
+ );
948
1196
  }
949
1197
  try {
950
1198
  await vault.writeCollectorApiKey(plaintext);
@@ -1003,6 +1251,11 @@ function collectorVersion() {
1003
1251
  }
1004
1252
 
1005
1253
  export {
1254
+ CREDENTIAL_KIND_SHAPE,
1255
+ localSecretSatisfiesKind,
1256
+ CreateLocalCredentialSetBody,
1257
+ UpdateLocalCredentialSetBody,
1258
+ EphemeralCredentialSet,
1006
1259
  VaultError,
1007
1260
  openVault,
1008
1261
  primeCollectorApiKey,
@@ -1010,4 +1263,4 @@ export {
1010
1263
  plaintextKeyWarning,
1011
1264
  collectorVersion
1012
1265
  };
1013
- //# sourceMappingURL=chunk-XAV5QW4Y.js.map
1266
+ //# sourceMappingURL=chunk-7T2LVP6E.js.map