s3db.js 13.5.1 → 13.6.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 (108) hide show
  1. package/README.md +89 -19
  2. package/dist/{s3db.cjs.js → s3db.cjs} +29780 -24384
  3. package/dist/s3db.cjs.map +1 -0
  4. package/dist/s3db.es.js +24263 -18860
  5. package/dist/s3db.es.js.map +1 -1
  6. package/package.json +227 -21
  7. package/src/concerns/id.js +90 -6
  8. package/src/concerns/index.js +2 -1
  9. package/src/concerns/password-hashing.js +150 -0
  10. package/src/database.class.js +4 -0
  11. package/src/plugins/api/auth/basic-auth.js +23 -1
  12. package/src/plugins/api/auth/index.js +49 -3
  13. package/src/plugins/api/auth/oauth2-auth.js +171 -0
  14. package/src/plugins/api/auth/oidc-auth.js +789 -0
  15. package/src/plugins/api/auth/oidc-client.js +462 -0
  16. package/src/plugins/api/auth/path-auth-matcher.js +284 -0
  17. package/src/plugins/api/concerns/event-emitter.js +134 -0
  18. package/src/plugins/api/concerns/failban-manager.js +651 -0
  19. package/src/plugins/api/concerns/guards-helpers.js +402 -0
  20. package/src/plugins/api/concerns/metrics-collector.js +346 -0
  21. package/src/plugins/api/concerns/opengraph-helper.js +116 -0
  22. package/src/plugins/api/concerns/state-machine.js +288 -0
  23. package/src/plugins/api/index.js +514 -54
  24. package/src/plugins/api/middlewares/failban.js +305 -0
  25. package/src/plugins/api/middlewares/rate-limit.js +301 -0
  26. package/src/plugins/api/middlewares/request-id.js +74 -0
  27. package/src/plugins/api/middlewares/security-headers.js +120 -0
  28. package/src/plugins/api/middlewares/session-tracking.js +194 -0
  29. package/src/plugins/api/routes/auth-routes.js +23 -3
  30. package/src/plugins/api/routes/resource-routes.js +71 -29
  31. package/src/plugins/api/server.js +1017 -94
  32. package/src/plugins/api/utils/guards.js +213 -0
  33. package/src/plugins/api/utils/mime-types.js +154 -0
  34. package/src/plugins/api/utils/openapi-generator.js +44 -11
  35. package/src/plugins/api/utils/path-matcher.js +173 -0
  36. package/src/plugins/api/utils/static-filesystem.js +262 -0
  37. package/src/plugins/api/utils/static-s3.js +231 -0
  38. package/src/plugins/api/utils/template-engine.js +262 -0
  39. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +853 -0
  40. package/src/plugins/cloud-inventory/drivers/aws-driver.js +2554 -0
  41. package/src/plugins/cloud-inventory/drivers/azure-driver.js +637 -0
  42. package/src/plugins/cloud-inventory/drivers/base-driver.js +99 -0
  43. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +620 -0
  44. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +698 -0
  45. package/src/plugins/cloud-inventory/drivers/gcp-driver.js +645 -0
  46. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +559 -0
  47. package/src/plugins/cloud-inventory/drivers/linode-driver.js +614 -0
  48. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +449 -0
  49. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +771 -0
  50. package/src/plugins/cloud-inventory/drivers/oracle-driver.js +768 -0
  51. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +636 -0
  52. package/src/plugins/cloud-inventory/index.js +20 -0
  53. package/src/plugins/cloud-inventory/registry.js +146 -0
  54. package/src/plugins/cloud-inventory/terraform-exporter.js +362 -0
  55. package/src/plugins/cloud-inventory.plugin.js +1333 -0
  56. package/src/plugins/concerns/plugin-dependencies.js +61 -1
  57. package/src/plugins/eventual-consistency/analytics.js +1 -0
  58. package/src/plugins/identity/README.md +335 -0
  59. package/src/plugins/identity/concerns/mfa-manager.js +204 -0
  60. package/src/plugins/identity/concerns/password.js +138 -0
  61. package/src/plugins/identity/concerns/resource-schemas.js +273 -0
  62. package/src/plugins/identity/concerns/token-generator.js +172 -0
  63. package/src/plugins/identity/email-service.js +422 -0
  64. package/src/plugins/identity/index.js +1052 -0
  65. package/src/plugins/identity/oauth2-server.js +1033 -0
  66. package/src/plugins/identity/oidc-discovery.js +285 -0
  67. package/src/plugins/identity/rsa-keys.js +323 -0
  68. package/src/plugins/identity/server.js +500 -0
  69. package/src/plugins/identity/session-manager.js +453 -0
  70. package/src/plugins/identity/ui/layouts/base.js +251 -0
  71. package/src/plugins/identity/ui/middleware.js +135 -0
  72. package/src/plugins/identity/ui/pages/admin/client-form.js +247 -0
  73. package/src/plugins/identity/ui/pages/admin/clients.js +179 -0
  74. package/src/plugins/identity/ui/pages/admin/dashboard.js +181 -0
  75. package/src/plugins/identity/ui/pages/admin/user-form.js +283 -0
  76. package/src/plugins/identity/ui/pages/admin/users.js +263 -0
  77. package/src/plugins/identity/ui/pages/consent.js +262 -0
  78. package/src/plugins/identity/ui/pages/forgot-password.js +104 -0
  79. package/src/plugins/identity/ui/pages/login.js +144 -0
  80. package/src/plugins/identity/ui/pages/mfa-backup-codes.js +180 -0
  81. package/src/plugins/identity/ui/pages/mfa-enrollment.js +187 -0
  82. package/src/plugins/identity/ui/pages/mfa-verification.js +178 -0
  83. package/src/plugins/identity/ui/pages/oauth-error.js +225 -0
  84. package/src/plugins/identity/ui/pages/profile.js +361 -0
  85. package/src/plugins/identity/ui/pages/register.js +226 -0
  86. package/src/plugins/identity/ui/pages/reset-password.js +128 -0
  87. package/src/plugins/identity/ui/pages/verify-email.js +172 -0
  88. package/src/plugins/identity/ui/routes.js +2541 -0
  89. package/src/plugins/identity/ui/styles/main.css +465 -0
  90. package/src/plugins/index.js +4 -1
  91. package/src/plugins/ml/base-model.class.js +32 -7
  92. package/src/plugins/ml/classification-model.class.js +1 -1
  93. package/src/plugins/ml/timeseries-model.class.js +3 -1
  94. package/src/plugins/ml.plugin.js +124 -32
  95. package/src/plugins/shared/error-handler.js +147 -0
  96. package/src/plugins/shared/index.js +9 -0
  97. package/src/plugins/shared/middlewares/compression.js +117 -0
  98. package/src/plugins/shared/middlewares/cors.js +49 -0
  99. package/src/plugins/shared/middlewares/index.js +11 -0
  100. package/src/plugins/shared/middlewares/logging.js +54 -0
  101. package/src/plugins/shared/middlewares/rate-limit.js +73 -0
  102. package/src/plugins/shared/middlewares/security.js +158 -0
  103. package/src/plugins/shared/response-formatter.js +264 -0
  104. package/src/plugins/tfstate/README.md +126 -126
  105. package/src/resource.class.js +140 -12
  106. package/src/schema.class.js +30 -1
  107. package/src/validator.class.js +57 -6
  108. package/dist/s3db.cjs.js.map +0 -1
@@ -0,0 +1,637 @@
1
+ import { BaseCloudDriver } from './base-driver.js';
2
+
3
+ /**
4
+ * Production-ready Microsoft Azure inventory driver using official @azure SDK.
5
+ *
6
+ * Covers 15+ services with 25+ resource types:
7
+ * - Compute (VMs, VM scale sets, availability sets)
8
+ * - Kubernetes (AKS clusters, node pools)
9
+ * - Storage (storage accounts, disks, snapshots)
10
+ * - Databases (SQL databases, Cosmos DB accounts)
11
+ * - Networking (VNets, subnets, load balancers, public IPs, NSGs, app gateways)
12
+ * - Container Registry (ACR)
13
+ * - DNS (zones, record sets)
14
+ * - Identity (managed identities)
15
+ *
16
+ * @see https://github.com/Azure/azure-sdk-for-js
17
+ * @see https://learn.microsoft.com/en-us/javascript/api/overview/azure/
18
+ */
19
+ export class AzureInventoryDriver extends BaseCloudDriver {
20
+ constructor(options = {}) {
21
+ super({ ...options, driver: options.driver || 'azure' });
22
+
23
+ this._credential = null;
24
+ this._subscriptionId = null;
25
+ this._accountId = this.config?.accountId || 'azure';
26
+
27
+ // Services to collect (can be filtered via config.services)
28
+ this._services = this.config?.services || [
29
+ 'compute',
30
+ 'kubernetes',
31
+ 'storage',
32
+ 'disks',
33
+ 'databases',
34
+ 'cosmosdb',
35
+ 'network',
36
+ 'containerregistry',
37
+ 'dns',
38
+ 'identity'
39
+ ];
40
+
41
+ // Resource groups to scan (null = all)
42
+ this._resourceGroups = this.config?.resourceGroups || null;
43
+ }
44
+
45
+ /**
46
+ * Initialize Azure credential and subscription.
47
+ */
48
+ async _initializeCredential() {
49
+ if (this._credential) return;
50
+
51
+ const credentials = this.credentials || {};
52
+
53
+ // Import Azure identity module
54
+ const { DefaultAzureCredential, ClientSecretCredential } = await import('@azure/identity');
55
+
56
+ // Setup authentication
57
+ if (credentials.clientId && credentials.clientSecret && credentials.tenantId) {
58
+ // Service principal authentication
59
+ this._credential = new ClientSecretCredential(
60
+ credentials.tenantId,
61
+ credentials.clientId,
62
+ credentials.clientSecret
63
+ );
64
+ } else {
65
+ // Default Azure credential (managed identity, Azure CLI, environment variables, etc.)
66
+ this._credential = new DefaultAzureCredential();
67
+ }
68
+
69
+ this._subscriptionId = credentials.subscriptionId || this.config?.subscriptionId;
70
+
71
+ if (!this._subscriptionId) {
72
+ throw new Error('Azure subscription ID is required. Provide via credentials.subscriptionId or config.subscriptionId.');
73
+ }
74
+
75
+ this.logger('info', 'Azure credential initialized', {
76
+ accountId: this._accountId,
77
+ subscriptionId: this._subscriptionId,
78
+ services: this._services.length
79
+ });
80
+ }
81
+
82
+ /**
83
+ * Main entry point - lists all resources from configured services.
84
+ */
85
+ async *listResources(options = {}) {
86
+ await this._initializeCredential();
87
+
88
+ const serviceCollectors = {
89
+ compute: () => this._collectCompute(),
90
+ kubernetes: () => this._collectKubernetes(),
91
+ storage: () => this._collectStorage(),
92
+ disks: () => this._collectDisks(),
93
+ databases: () => this._collectDatabases(),
94
+ cosmosdb: () => this._collectCosmosDB(),
95
+ network: () => this._collectNetwork(),
96
+ containerregistry: () => this._collectContainerRegistry(),
97
+ dns: () => this._collectDNS(),
98
+ identity: () => this._collectIdentity()
99
+ };
100
+
101
+ for (const service of this._services) {
102
+ const collector = serviceCollectors[service];
103
+ if (!collector) {
104
+ this.logger('warn', `Unknown Azure service: ${service}`, { service });
105
+ continue;
106
+ }
107
+
108
+ try {
109
+ this.logger('info', `Collecting Azure ${service} resources`, { service });
110
+ yield* collector();
111
+ } catch (err) {
112
+ // Continue with next service instead of failing entire sync
113
+ this.logger('error', `Azure service collection failed, skipping to next service`, {
114
+ service,
115
+ error: err.message,
116
+ errorName: err.name,
117
+ stack: err.stack
118
+ });
119
+ }
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Collect Compute resources (VMs, VM scale sets).
125
+ */
126
+ async *_collectCompute() {
127
+ try {
128
+ const { ComputeManagementClient } = await import('@azure/arm-compute');
129
+ const computeClient = new ComputeManagementClient(this._credential, this._subscriptionId);
130
+
131
+ // List all Virtual Machines
132
+ const vmsIterator = computeClient.virtualMachines.listAll();
133
+ for await (const vm of vmsIterator) {
134
+ yield {
135
+ provider: 'azure',
136
+ accountId: this._accountId,
137
+ region: vm.location,
138
+ service: 'compute',
139
+ resourceType: 'azure.compute.virtualmachine',
140
+ resourceId: vm.id,
141
+ name: vm.name,
142
+ tags: vm.tags || {},
143
+ configuration: this._sanitize(vm)
144
+ };
145
+ }
146
+
147
+ // List all VM Scale Sets
148
+ const scaleSetsIterator = computeClient.virtualMachineScaleSets.listAll();
149
+ for await (const scaleSet of scaleSetsIterator) {
150
+ yield {
151
+ provider: 'azure',
152
+ accountId: this._accountId,
153
+ region: scaleSet.location,
154
+ service: 'compute',
155
+ resourceType: 'azure.compute.vmscaleset',
156
+ resourceId: scaleSet.id,
157
+ name: scaleSet.name,
158
+ tags: scaleSet.tags || {},
159
+ configuration: this._sanitize(scaleSet)
160
+ };
161
+ }
162
+
163
+ this.logger('info', `Collected Azure compute resources`);
164
+ } catch (err) {
165
+ this.logger('error', 'Failed to collect Azure compute', {
166
+ error: err.message,
167
+ stack: err.stack
168
+ });
169
+ throw err;
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Collect Kubernetes (AKS) clusters.
175
+ */
176
+ async *_collectKubernetes() {
177
+ try {
178
+ const { ContainerServiceClient } = await import('@azure/arm-containerservice');
179
+ const aksClient = new ContainerServiceClient(this._credential, this._subscriptionId);
180
+
181
+ const clustersIterator = aksClient.managedClusters.list();
182
+ for await (const cluster of clustersIterator) {
183
+ yield {
184
+ provider: 'azure',
185
+ accountId: this._accountId,
186
+ region: cluster.location,
187
+ service: 'kubernetes',
188
+ resourceType: 'azure.kubernetes.cluster',
189
+ resourceId: cluster.id,
190
+ name: cluster.name,
191
+ tags: cluster.tags || {},
192
+ configuration: this._sanitize(cluster)
193
+ };
194
+
195
+ // Agent pools (node pools) are embedded in cluster
196
+ if (cluster.agentPoolProfiles && Array.isArray(cluster.agentPoolProfiles)) {
197
+ for (const pool of cluster.agentPoolProfiles) {
198
+ yield {
199
+ provider: 'azure',
200
+ accountId: this._accountId,
201
+ region: cluster.location,
202
+ service: 'kubernetes',
203
+ resourceType: 'azure.kubernetes.nodepool',
204
+ resourceId: `${cluster.id}/agentPools/${pool.name}`,
205
+ name: pool.name,
206
+ tags: {},
207
+ metadata: { clusterId: cluster.id, clusterName: cluster.name },
208
+ configuration: this._sanitize(pool)
209
+ };
210
+ }
211
+ }
212
+ }
213
+
214
+ this.logger('info', `Collected Azure AKS clusters`);
215
+ } catch (err) {
216
+ this.logger('error', 'Failed to collect Azure Kubernetes', {
217
+ error: err.message,
218
+ stack: err.stack
219
+ });
220
+ throw err;
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Collect Storage Accounts.
226
+ */
227
+ async *_collectStorage() {
228
+ try {
229
+ const { StorageManagementClient } = await import('@azure/arm-storage');
230
+ const storageClient = new StorageManagementClient(this._credential, this._subscriptionId);
231
+
232
+ const accountsIterator = storageClient.storageAccounts.list();
233
+ for await (const account of accountsIterator) {
234
+ yield {
235
+ provider: 'azure',
236
+ accountId: this._accountId,
237
+ region: account.location,
238
+ service: 'storage',
239
+ resourceType: 'azure.storage.account',
240
+ resourceId: account.id,
241
+ name: account.name,
242
+ tags: account.tags || {},
243
+ configuration: this._sanitize(account)
244
+ };
245
+ }
246
+
247
+ this.logger('info', `Collected Azure storage accounts`);
248
+ } catch (err) {
249
+ this.logger('error', 'Failed to collect Azure storage', {
250
+ error: err.message,
251
+ stack: err.stack
252
+ });
253
+ throw err;
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Collect Disks and Snapshots.
259
+ */
260
+ async *_collectDisks() {
261
+ try {
262
+ const { ComputeManagementClient } = await import('@azure/arm-compute');
263
+ const computeClient = new ComputeManagementClient(this._credential, this._subscriptionId);
264
+
265
+ // Disks
266
+ const disksIterator = computeClient.disks.list();
267
+ for await (const disk of disksIterator) {
268
+ yield {
269
+ provider: 'azure',
270
+ accountId: this._accountId,
271
+ region: disk.location,
272
+ service: 'disks',
273
+ resourceType: 'azure.disk',
274
+ resourceId: disk.id,
275
+ name: disk.name,
276
+ tags: disk.tags || {},
277
+ configuration: this._sanitize(disk)
278
+ };
279
+ }
280
+
281
+ // Snapshots
282
+ const snapshotsIterator = computeClient.snapshots.list();
283
+ for await (const snapshot of snapshotsIterator) {
284
+ yield {
285
+ provider: 'azure',
286
+ accountId: this._accountId,
287
+ region: snapshot.location,
288
+ service: 'disks',
289
+ resourceType: 'azure.snapshot',
290
+ resourceId: snapshot.id,
291
+ name: snapshot.name,
292
+ tags: snapshot.tags || {},
293
+ configuration: this._sanitize(snapshot)
294
+ };
295
+ }
296
+
297
+ this.logger('info', `Collected Azure disks and snapshots`);
298
+ } catch (err) {
299
+ this.logger('error', 'Failed to collect Azure disks', {
300
+ error: err.message,
301
+ stack: err.stack
302
+ });
303
+ throw err;
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Collect SQL Databases.
309
+ */
310
+ async *_collectDatabases() {
311
+ try {
312
+ const { SqlManagementClient } = await import('@azure/arm-sql');
313
+ const sqlClient = new SqlManagementClient(this._credential, this._subscriptionId);
314
+
315
+ // List all SQL servers
316
+ const serversIterator = sqlClient.servers.list();
317
+ for await (const server of serversIterator) {
318
+ yield {
319
+ provider: 'azure',
320
+ accountId: this._accountId,
321
+ region: server.location,
322
+ service: 'databases',
323
+ resourceType: 'azure.sql.server',
324
+ resourceId: server.id,
325
+ name: server.name,
326
+ tags: server.tags || {},
327
+ configuration: this._sanitize(server)
328
+ };
329
+
330
+ // List databases for this server
331
+ try {
332
+ const resourceGroupName = this._extractResourceGroup(server.id);
333
+ const databasesIterator = sqlClient.databases.listByServer(resourceGroupName, server.name);
334
+
335
+ for await (const database of databasesIterator) {
336
+ yield {
337
+ provider: 'azure',
338
+ accountId: this._accountId,
339
+ region: database.location,
340
+ service: 'databases',
341
+ resourceType: 'azure.sql.database',
342
+ resourceId: database.id,
343
+ name: database.name,
344
+ tags: database.tags || {},
345
+ metadata: { serverId: server.id, serverName: server.name },
346
+ configuration: this._sanitize(database)
347
+ };
348
+ }
349
+ } catch (dbErr) {
350
+ this.logger('warn', `Failed to collect databases for server ${server.name}`, {
351
+ serverId: server.id,
352
+ error: dbErr.message
353
+ });
354
+ }
355
+ }
356
+
357
+ this.logger('info', `Collected Azure SQL databases`);
358
+ } catch (err) {
359
+ this.logger('error', 'Failed to collect Azure databases', {
360
+ error: err.message,
361
+ stack: err.stack
362
+ });
363
+ throw err;
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Collect Cosmos DB accounts.
369
+ */
370
+ async *_collectCosmosDB() {
371
+ try {
372
+ const { CosmosDBManagementClient } = await import('@azure/arm-cosmosdb');
373
+ const cosmosClient = new CosmosDBManagementClient(this._credential, this._subscriptionId);
374
+
375
+ const accountsIterator = cosmosClient.databaseAccounts.list();
376
+ for await (const account of accountsIterator) {
377
+ yield {
378
+ provider: 'azure',
379
+ accountId: this._accountId,
380
+ region: account.location,
381
+ service: 'cosmosdb',
382
+ resourceType: 'azure.cosmosdb.account',
383
+ resourceId: account.id,
384
+ name: account.name,
385
+ tags: account.tags || {},
386
+ configuration: this._sanitize(account)
387
+ };
388
+ }
389
+
390
+ this.logger('info', `Collected Azure Cosmos DB accounts`);
391
+ } catch (err) {
392
+ this.logger('error', 'Failed to collect Azure Cosmos DB', {
393
+ error: err.message,
394
+ stack: err.stack
395
+ });
396
+ throw err;
397
+ }
398
+ }
399
+
400
+ /**
401
+ * Collect Network resources (VNets, Subnets, Load Balancers, Public IPs).
402
+ */
403
+ async *_collectNetwork() {
404
+ try {
405
+ const { NetworkManagementClient } = await import('@azure/arm-network');
406
+ const networkClient = new NetworkManagementClient(this._credential, this._subscriptionId);
407
+
408
+ // Virtual Networks
409
+ const vnetsIterator = networkClient.virtualNetworks.listAll();
410
+ for await (const vnet of vnetsIterator) {
411
+ yield {
412
+ provider: 'azure',
413
+ accountId: this._accountId,
414
+ region: vnet.location,
415
+ service: 'network',
416
+ resourceType: 'azure.network.virtualnetwork',
417
+ resourceId: vnet.id,
418
+ name: vnet.name,
419
+ tags: vnet.tags || {},
420
+ configuration: this._sanitize(vnet)
421
+ };
422
+
423
+ // Subnets
424
+ if (vnet.subnets && Array.isArray(vnet.subnets)) {
425
+ for (const subnet of vnet.subnets) {
426
+ yield {
427
+ provider: 'azure',
428
+ accountId: this._accountId,
429
+ region: vnet.location,
430
+ service: 'network',
431
+ resourceType: 'azure.network.subnet',
432
+ resourceId: subnet.id,
433
+ name: subnet.name,
434
+ tags: {},
435
+ metadata: { vnetId: vnet.id, vnetName: vnet.name },
436
+ configuration: this._sanitize(subnet)
437
+ };
438
+ }
439
+ }
440
+ }
441
+
442
+ // Load Balancers
443
+ const lbsIterator = networkClient.loadBalancers.listAll();
444
+ for await (const lb of lbsIterator) {
445
+ yield {
446
+ provider: 'azure',
447
+ accountId: this._accountId,
448
+ region: lb.location,
449
+ service: 'network',
450
+ resourceType: 'azure.network.loadbalancer',
451
+ resourceId: lb.id,
452
+ name: lb.name,
453
+ tags: lb.tags || {},
454
+ configuration: this._sanitize(lb)
455
+ };
456
+ }
457
+
458
+ // Public IP Addresses
459
+ const ipsIterator = networkClient.publicIPAddresses.listAll();
460
+ for await (const ip of ipsIterator) {
461
+ yield {
462
+ provider: 'azure',
463
+ accountId: this._accountId,
464
+ region: ip.location,
465
+ service: 'network',
466
+ resourceType: 'azure.network.publicip',
467
+ resourceId: ip.id,
468
+ name: ip.name,
469
+ tags: ip.tags || {},
470
+ configuration: this._sanitize(ip)
471
+ };
472
+ }
473
+
474
+ // Network Security Groups
475
+ const nsgsIterator = networkClient.networkSecurityGroups.listAll();
476
+ for await (const nsg of nsgsIterator) {
477
+ yield {
478
+ provider: 'azure',
479
+ accountId: this._accountId,
480
+ region: nsg.location,
481
+ service: 'network',
482
+ resourceType: 'azure.network.securitygroup',
483
+ resourceId: nsg.id,
484
+ name: nsg.name,
485
+ tags: nsg.tags || {},
486
+ configuration: this._sanitize(nsg)
487
+ };
488
+ }
489
+
490
+ this.logger('info', `Collected Azure network resources`);
491
+ } catch (err) {
492
+ this.logger('error', 'Failed to collect Azure network', {
493
+ error: err.message,
494
+ stack: err.stack
495
+ });
496
+ throw err;
497
+ }
498
+ }
499
+
500
+ /**
501
+ * Collect Container Registry.
502
+ */
503
+ async *_collectContainerRegistry() {
504
+ try {
505
+ const { ContainerRegistryManagementClient } = await import('@azure/arm-containerregistry');
506
+ const acrClient = new ContainerRegistryManagementClient(this._credential, this._subscriptionId);
507
+
508
+ const registriesIterator = acrClient.registries.list();
509
+ for await (const registry of registriesIterator) {
510
+ yield {
511
+ provider: 'azure',
512
+ accountId: this._accountId,
513
+ region: registry.location,
514
+ service: 'containerregistry',
515
+ resourceType: 'azure.containerregistry',
516
+ resourceId: registry.id,
517
+ name: registry.name,
518
+ tags: registry.tags || {},
519
+ configuration: this._sanitize(registry)
520
+ };
521
+ }
522
+
523
+ this.logger('info', `Collected Azure container registries`);
524
+ } catch (err) {
525
+ this.logger('error', 'Failed to collect Azure container registry', {
526
+ error: err.message,
527
+ stack: err.stack
528
+ });
529
+ throw err;
530
+ }
531
+ }
532
+
533
+ /**
534
+ * Collect DNS zones.
535
+ */
536
+ async *_collectDNS() {
537
+ try {
538
+ const { DnsManagementClient } = await import('@azure/arm-dns');
539
+ const dnsClient = new DnsManagementClient(this._credential, this._subscriptionId);
540
+
541
+ const zonesIterator = dnsClient.zones.list();
542
+ for await (const zone of zonesIterator) {
543
+ yield {
544
+ provider: 'azure',
545
+ accountId: this._accountId,
546
+ region: zone.location,
547
+ service: 'dns',
548
+ resourceType: 'azure.dns.zone',
549
+ resourceId: zone.id,
550
+ name: zone.name,
551
+ tags: zone.tags || {},
552
+ configuration: this._sanitize(zone)
553
+ };
554
+ }
555
+
556
+ this.logger('info', `Collected Azure DNS zones`);
557
+ } catch (err) {
558
+ this.logger('error', 'Failed to collect Azure DNS', {
559
+ error: err.message,
560
+ stack: err.stack
561
+ });
562
+ throw err;
563
+ }
564
+ }
565
+
566
+ /**
567
+ * Collect Managed Identities.
568
+ */
569
+ async *_collectIdentity() {
570
+ try {
571
+ const { ManagedServiceIdentityClient } = await import('@azure/arm-msi');
572
+ const identityClient = new ManagedServiceIdentityClient(this._credential, this._subscriptionId);
573
+
574
+ const identitiesIterator = identityClient.userAssignedIdentities.listBySubscription();
575
+ for await (const identity of identitiesIterator) {
576
+ yield {
577
+ provider: 'azure',
578
+ accountId: this._accountId,
579
+ region: identity.location,
580
+ service: 'identity',
581
+ resourceType: 'azure.identity.userassigned',
582
+ resourceId: identity.id,
583
+ name: identity.name,
584
+ tags: identity.tags || {},
585
+ configuration: this._sanitize(identity)
586
+ };
587
+ }
588
+
589
+ this.logger('info', `Collected Azure managed identities`);
590
+ } catch (err) {
591
+ this.logger('error', 'Failed to collect Azure identity', {
592
+ error: err.message,
593
+ stack: err.stack
594
+ });
595
+ throw err;
596
+ }
597
+ }
598
+
599
+ /**
600
+ * Extract resource group name from Azure resource ID.
601
+ */
602
+ _extractResourceGroup(resourceId) {
603
+ if (!resourceId) return null;
604
+ const match = resourceId.match(/\/resourceGroups\/([^\/]+)/i);
605
+ return match ? match[1] : null;
606
+ }
607
+
608
+ /**
609
+ * Sanitize configuration by removing sensitive data.
610
+ */
611
+ _sanitize(config) {
612
+ if (!config || typeof config !== 'object') return config;
613
+
614
+ const sanitized = { ...config };
615
+ const sensitiveFields = [
616
+ 'administratorLogin',
617
+ 'administratorLoginPassword',
618
+ 'password',
619
+ 'adminPassword',
620
+ 'adminUsername',
621
+ 'connectionString',
622
+ 'primaryKey',
623
+ 'secondaryKey',
624
+ 'keys',
625
+ 'secret',
626
+ 'token'
627
+ ];
628
+
629
+ for (const field of sensitiveFields) {
630
+ if (field in sanitized) {
631
+ sanitized[field] = '***REDACTED***';
632
+ }
633
+ }
634
+
635
+ return sanitized;
636
+ }
637
+ }