s3db.js 13.5.1 → 13.6.0
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/README.md +25 -10
- package/dist/{s3db.cjs.js → s3db.cjs} +30323 -24958
- package/dist/s3db.cjs.map +1 -0
- package/dist/s3db.es.js +24026 -18654
- package/dist/s3db.es.js.map +1 -1
- package/package.json +216 -20
- package/src/concerns/id.js +90 -6
- package/src/concerns/index.js +2 -1
- package/src/concerns/password-hashing.js +150 -0
- package/src/database.class.js +4 -0
- package/src/plugins/api/auth/basic-auth.js +23 -1
- package/src/plugins/api/auth/index.js +49 -3
- package/src/plugins/api/auth/oauth2-auth.js +171 -0
- package/src/plugins/api/auth/oidc-auth.js +789 -0
- package/src/plugins/api/auth/oidc-client.js +462 -0
- package/src/plugins/api/auth/path-auth-matcher.js +284 -0
- package/src/plugins/api/concerns/event-emitter.js +134 -0
- package/src/plugins/api/concerns/failban-manager.js +651 -0
- package/src/plugins/api/concerns/guards-helpers.js +402 -0
- package/src/plugins/api/concerns/metrics-collector.js +346 -0
- package/src/plugins/api/index.js +503 -54
- package/src/plugins/api/middlewares/failban.js +305 -0
- package/src/plugins/api/middlewares/rate-limit.js +301 -0
- package/src/plugins/api/middlewares/request-id.js +74 -0
- package/src/plugins/api/middlewares/security-headers.js +120 -0
- package/src/plugins/api/middlewares/session-tracking.js +194 -0
- package/src/plugins/api/routes/auth-routes.js +23 -3
- package/src/plugins/api/routes/resource-routes.js +71 -29
- package/src/plugins/api/server.js +1017 -94
- package/src/plugins/api/utils/guards.js +213 -0
- package/src/plugins/api/utils/mime-types.js +154 -0
- package/src/plugins/api/utils/openapi-generator.js +44 -11
- package/src/plugins/api/utils/path-matcher.js +173 -0
- package/src/plugins/api/utils/static-filesystem.js +262 -0
- package/src/plugins/api/utils/static-s3.js +231 -0
- package/src/plugins/api/utils/template-engine.js +188 -0
- package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +853 -0
- package/src/plugins/cloud-inventory/drivers/aws-driver.js +2554 -0
- package/src/plugins/cloud-inventory/drivers/azure-driver.js +637 -0
- package/src/plugins/cloud-inventory/drivers/base-driver.js +99 -0
- package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +620 -0
- package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +698 -0
- package/src/plugins/cloud-inventory/drivers/gcp-driver.js +645 -0
- package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +559 -0
- package/src/plugins/cloud-inventory/drivers/linode-driver.js +614 -0
- package/src/plugins/cloud-inventory/drivers/mock-drivers.js +449 -0
- package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +771 -0
- package/src/plugins/cloud-inventory/drivers/oracle-driver.js +768 -0
- package/src/plugins/cloud-inventory/drivers/vultr-driver.js +636 -0
- package/src/plugins/cloud-inventory/index.js +20 -0
- package/src/plugins/cloud-inventory/registry.js +146 -0
- package/src/plugins/cloud-inventory/terraform-exporter.js +362 -0
- package/src/plugins/cloud-inventory.plugin.js +1333 -0
- package/src/plugins/concerns/plugin-dependencies.js +61 -1
- package/src/plugins/eventual-consistency/analytics.js +1 -0
- package/src/plugins/identity/README.md +335 -0
- package/src/plugins/identity/concerns/mfa-manager.js +204 -0
- package/src/plugins/identity/concerns/password.js +138 -0
- package/src/plugins/identity/concerns/resource-schemas.js +273 -0
- package/src/plugins/identity/concerns/token-generator.js +172 -0
- package/src/plugins/identity/email-service.js +422 -0
- package/src/plugins/identity/index.js +1052 -0
- package/src/plugins/identity/oauth2-server.js +1033 -0
- package/src/plugins/identity/oidc-discovery.js +285 -0
- package/src/plugins/identity/rsa-keys.js +323 -0
- package/src/plugins/identity/server.js +500 -0
- package/src/plugins/identity/session-manager.js +453 -0
- package/src/plugins/identity/ui/layouts/base.js +251 -0
- package/src/plugins/identity/ui/middleware.js +135 -0
- package/src/plugins/identity/ui/pages/admin/client-form.js +247 -0
- package/src/plugins/identity/ui/pages/admin/clients.js +179 -0
- package/src/plugins/identity/ui/pages/admin/dashboard.js +181 -0
- package/src/plugins/identity/ui/pages/admin/user-form.js +283 -0
- package/src/plugins/identity/ui/pages/admin/users.js +263 -0
- package/src/plugins/identity/ui/pages/consent.js +262 -0
- package/src/plugins/identity/ui/pages/forgot-password.js +104 -0
- package/src/plugins/identity/ui/pages/login.js +144 -0
- package/src/plugins/identity/ui/pages/mfa-backup-codes.js +180 -0
- package/src/plugins/identity/ui/pages/mfa-enrollment.js +187 -0
- package/src/plugins/identity/ui/pages/mfa-verification.js +178 -0
- package/src/plugins/identity/ui/pages/oauth-error.js +225 -0
- package/src/plugins/identity/ui/pages/profile.js +361 -0
- package/src/plugins/identity/ui/pages/register.js +226 -0
- package/src/plugins/identity/ui/pages/reset-password.js +128 -0
- package/src/plugins/identity/ui/pages/verify-email.js +172 -0
- package/src/plugins/identity/ui/routes.js +2541 -0
- package/src/plugins/identity/ui/styles/main.css +465 -0
- package/src/plugins/index.js +4 -1
- package/src/plugins/ml/base-model.class.js +32 -7
- package/src/plugins/ml/classification-model.class.js +1 -1
- package/src/plugins/ml/timeseries-model.class.js +3 -1
- package/src/plugins/ml.plugin.js +124 -32
- package/src/plugins/shared/error-handler.js +147 -0
- package/src/plugins/shared/index.js +9 -0
- package/src/plugins/shared/middlewares/compression.js +117 -0
- package/src/plugins/shared/middlewares/cors.js +49 -0
- package/src/plugins/shared/middlewares/index.js +11 -0
- package/src/plugins/shared/middlewares/logging.js +54 -0
- package/src/plugins/shared/middlewares/rate-limit.js +73 -0
- package/src/plugins/shared/middlewares/security.js +158 -0
- package/src/plugins/shared/response-formatter.js +264 -0
- package/src/resource.class.js +140 -12
- package/src/schema.class.js +30 -1
- package/src/validator.class.js +57 -6
- 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
|
+
}
|