s3db.js 13.4.0 → 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.
Files changed (110) hide show
  1. package/README.md +25 -10
  2. package/dist/{s3db.cjs.js → s3db.cjs} +38801 -32446
  3. package/dist/s3db.cjs.map +1 -0
  4. package/dist/s3db.es.js +38653 -32291
  5. package/dist/s3db.es.js.map +1 -1
  6. package/package.json +218 -22
  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 +6 -2
  11. package/src/plugins/api/auth/basic-auth.js +40 -10
  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/index.js +510 -57
  22. package/src/plugins/api/middlewares/failban.js +305 -0
  23. package/src/plugins/api/middlewares/rate-limit.js +301 -0
  24. package/src/plugins/api/middlewares/request-id.js +74 -0
  25. package/src/plugins/api/middlewares/security-headers.js +120 -0
  26. package/src/plugins/api/middlewares/session-tracking.js +194 -0
  27. package/src/plugins/api/routes/auth-routes.js +119 -78
  28. package/src/plugins/api/routes/resource-routes.js +73 -30
  29. package/src/plugins/api/server.js +1139 -45
  30. package/src/plugins/api/utils/custom-routes.js +102 -0
  31. package/src/plugins/api/utils/guards.js +213 -0
  32. package/src/plugins/api/utils/mime-types.js +154 -0
  33. package/src/plugins/api/utils/openapi-generator.js +91 -12
  34. package/src/plugins/api/utils/path-matcher.js +173 -0
  35. package/src/plugins/api/utils/static-filesystem.js +262 -0
  36. package/src/plugins/api/utils/static-s3.js +231 -0
  37. package/src/plugins/api/utils/template-engine.js +188 -0
  38. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +853 -0
  39. package/src/plugins/cloud-inventory/drivers/aws-driver.js +2554 -0
  40. package/src/plugins/cloud-inventory/drivers/azure-driver.js +637 -0
  41. package/src/plugins/cloud-inventory/drivers/base-driver.js +99 -0
  42. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +620 -0
  43. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +698 -0
  44. package/src/plugins/cloud-inventory/drivers/gcp-driver.js +645 -0
  45. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +559 -0
  46. package/src/plugins/cloud-inventory/drivers/linode-driver.js +614 -0
  47. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +449 -0
  48. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +771 -0
  49. package/src/plugins/cloud-inventory/drivers/oracle-driver.js +768 -0
  50. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +636 -0
  51. package/src/plugins/cloud-inventory/index.js +20 -0
  52. package/src/plugins/cloud-inventory/registry.js +146 -0
  53. package/src/plugins/cloud-inventory/terraform-exporter.js +362 -0
  54. package/src/plugins/cloud-inventory.plugin.js +1333 -0
  55. package/src/plugins/concerns/plugin-dependencies.js +62 -2
  56. package/src/plugins/eventual-consistency/analytics.js +1 -0
  57. package/src/plugins/eventual-consistency/consolidation.js +2 -2
  58. package/src/plugins/eventual-consistency/garbage-collection.js +2 -2
  59. package/src/plugins/eventual-consistency/install.js +2 -2
  60. package/src/plugins/identity/README.md +335 -0
  61. package/src/plugins/identity/concerns/mfa-manager.js +204 -0
  62. package/src/plugins/identity/concerns/password.js +138 -0
  63. package/src/plugins/identity/concerns/resource-schemas.js +273 -0
  64. package/src/plugins/identity/concerns/token-generator.js +172 -0
  65. package/src/plugins/identity/email-service.js +422 -0
  66. package/src/plugins/identity/index.js +1052 -0
  67. package/src/plugins/identity/oauth2-server.js +1033 -0
  68. package/src/plugins/identity/oidc-discovery.js +285 -0
  69. package/src/plugins/identity/rsa-keys.js +323 -0
  70. package/src/plugins/identity/server.js +500 -0
  71. package/src/plugins/identity/session-manager.js +453 -0
  72. package/src/plugins/identity/ui/layouts/base.js +251 -0
  73. package/src/plugins/identity/ui/middleware.js +135 -0
  74. package/src/plugins/identity/ui/pages/admin/client-form.js +247 -0
  75. package/src/plugins/identity/ui/pages/admin/clients.js +179 -0
  76. package/src/plugins/identity/ui/pages/admin/dashboard.js +181 -0
  77. package/src/plugins/identity/ui/pages/admin/user-form.js +283 -0
  78. package/src/plugins/identity/ui/pages/admin/users.js +263 -0
  79. package/src/plugins/identity/ui/pages/consent.js +262 -0
  80. package/src/plugins/identity/ui/pages/forgot-password.js +104 -0
  81. package/src/plugins/identity/ui/pages/login.js +144 -0
  82. package/src/plugins/identity/ui/pages/mfa-backup-codes.js +180 -0
  83. package/src/plugins/identity/ui/pages/mfa-enrollment.js +187 -0
  84. package/src/plugins/identity/ui/pages/mfa-verification.js +178 -0
  85. package/src/plugins/identity/ui/pages/oauth-error.js +225 -0
  86. package/src/plugins/identity/ui/pages/profile.js +361 -0
  87. package/src/plugins/identity/ui/pages/register.js +226 -0
  88. package/src/plugins/identity/ui/pages/reset-password.js +128 -0
  89. package/src/plugins/identity/ui/pages/verify-email.js +172 -0
  90. package/src/plugins/identity/ui/routes.js +2541 -0
  91. package/src/plugins/identity/ui/styles/main.css +465 -0
  92. package/src/plugins/index.js +4 -1
  93. package/src/plugins/ml/base-model.class.js +65 -16
  94. package/src/plugins/ml/classification-model.class.js +1 -1
  95. package/src/plugins/ml/timeseries-model.class.js +3 -1
  96. package/src/plugins/ml.plugin.js +584 -31
  97. package/src/plugins/shared/error-handler.js +147 -0
  98. package/src/plugins/shared/index.js +9 -0
  99. package/src/plugins/shared/middlewares/compression.js +117 -0
  100. package/src/plugins/shared/middlewares/cors.js +49 -0
  101. package/src/plugins/shared/middlewares/index.js +11 -0
  102. package/src/plugins/shared/middlewares/logging.js +54 -0
  103. package/src/plugins/shared/middlewares/rate-limit.js +73 -0
  104. package/src/plugins/shared/middlewares/security.js +158 -0
  105. package/src/plugins/shared/response-formatter.js +264 -0
  106. package/src/plugins/state-machine.plugin.js +57 -2
  107. package/src/resource.class.js +140 -12
  108. package/src/schema.class.js +30 -1
  109. package/src/validator.class.js +57 -6
  110. package/dist/s3db.cjs.js.map +0 -1
@@ -0,0 +1,2554 @@
1
+ import { fromNodeProviderChain, fromIni, fromProcess } from '@aws-sdk/credential-providers';
2
+ import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts';
3
+ import {
4
+ EC2Client,
5
+ paginateDescribeInstances,
6
+ paginateDescribeVpcs,
7
+ paginateDescribeSubnets,
8
+ paginateDescribeSecurityGroups,
9
+ paginateDescribeRouteTables,
10
+ paginateDescribeInternetGateways,
11
+ paginateDescribeNatGateways,
12
+ paginateDescribeNetworkAcls
13
+ } from '@aws-sdk/client-ec2';
14
+ import {
15
+ S3Client,
16
+ ListBucketsCommand,
17
+ GetBucketLocationCommand,
18
+ GetBucketTaggingCommand
19
+ } from '@aws-sdk/client-s3';
20
+ import {
21
+ RDSClient,
22
+ paginateDescribeDBInstances,
23
+ ListTagsForResourceCommand
24
+ } from '@aws-sdk/client-rds';
25
+ import {
26
+ IAMClient,
27
+ paginateListUsers,
28
+ paginateListRoles,
29
+ ListUserTagsCommand,
30
+ ListRoleTagsCommand
31
+ } from '@aws-sdk/client-iam';
32
+ import {
33
+ LambdaClient,
34
+ paginateListFunctions,
35
+ ListTagsCommand as ListLambdaTagsCommand
36
+ } from '@aws-sdk/client-lambda';
37
+ import {
38
+ ElasticLoadBalancingClient,
39
+ paginateDescribeLoadBalancers as paginateDescribeClassicLoadBalancers,
40
+ DescribeTagsCommand as DescribeClassicLBTagsCommand
41
+ } from '@aws-sdk/client-elastic-load-balancing';
42
+ import {
43
+ ElasticLoadBalancingV2Client,
44
+ paginateDescribeLoadBalancers,
45
+ paginateDescribeTargetGroups,
46
+ DescribeTagsCommand as DescribeELBv2TagsCommand
47
+ } from '@aws-sdk/client-elastic-load-balancing-v2';
48
+ import {
49
+ DynamoDBClient,
50
+ paginateListTables,
51
+ DescribeTableCommand,
52
+ ListTagsOfResourceCommand as ListDynamoDBTagsCommand
53
+ } from '@aws-sdk/client-dynamodb';
54
+ import {
55
+ SQSClient,
56
+ paginateListQueues,
57
+ GetQueueAttributesCommand,
58
+ ListQueueTagsCommand
59
+ } from '@aws-sdk/client-sqs';
60
+ import {
61
+ SNSClient,
62
+ paginateListTopics,
63
+ GetTopicAttributesCommand,
64
+ ListTagsForResourceCommand as ListSNSTagsCommand
65
+ } from '@aws-sdk/client-sns';
66
+ import {
67
+ ECSClient,
68
+ paginateListClusters,
69
+ paginateListServices,
70
+ paginateListTaskDefinitions,
71
+ DescribeServicesCommand,
72
+ DescribeTaskDefinitionCommand,
73
+ ListTagsForResourceCommand as ListECSTagsCommand
74
+ } from '@aws-sdk/client-ecs';
75
+ import {
76
+ EKSClient,
77
+ paginateListClusters as paginateListEKSClusters,
78
+ paginateListNodegroups,
79
+ DescribeClusterCommand,
80
+ DescribeNodegroupCommand,
81
+ ListTagsForResourceCommand as ListEKSTagsCommand
82
+ } from '@aws-sdk/client-eks';
83
+ import {
84
+ APIGatewayClient,
85
+ paginateGetRestApis,
86
+ GetTagsCommand as GetAPIGatewayTagsCommand
87
+ } from '@aws-sdk/client-api-gateway';
88
+ import {
89
+ ApiGatewayV2Client,
90
+ paginateGetApis,
91
+ GetTagsCommand as GetAPIGatewayV2TagsCommand
92
+ } from '@aws-sdk/client-apigatewayv2';
93
+ import {
94
+ CloudFrontClient,
95
+ paginateListDistributions,
96
+ ListTagsForResourceCommand as ListCloudFrontTagsCommand
97
+ } from '@aws-sdk/client-cloudfront';
98
+ import {
99
+ Route53Client,
100
+ paginateListHostedZones,
101
+ ListTagsForResourceCommand as ListRoute53TagsCommand
102
+ } from '@aws-sdk/client-route-53';
103
+ import {
104
+ KMSClient,
105
+ paginateListKeys,
106
+ DescribeKeyCommand,
107
+ ListResourceTagsCommand
108
+ } from '@aws-sdk/client-kms';
109
+ import {
110
+ SecretsManagerClient,
111
+ paginateListSecrets,
112
+ DescribeSecretCommand
113
+ } from '@aws-sdk/client-secrets-manager';
114
+ import {
115
+ SSMClient,
116
+ paginateDescribeParameters,
117
+ ListTagsForResourceCommand as ListSSMTagsCommand
118
+ } from '@aws-sdk/client-ssm';
119
+ import {
120
+ ElastiCacheClient,
121
+ paginateDescribeCacheClusters,
122
+ ListTagsForResourceCommand as ListElastiCacheTagsCommand
123
+ } from '@aws-sdk/client-elasticache';
124
+ import {
125
+ EFSClient,
126
+ paginateDescribeFileSystems,
127
+ DescribeTagsCommand as DescribeEFSTagsCommand
128
+ } from '@aws-sdk/client-efs';
129
+ import {
130
+ ECRClient,
131
+ paginateDescribeRepositories,
132
+ ListTagsForResourceCommand as ListECRTagsCommand
133
+ } from '@aws-sdk/client-ecr';
134
+ import {
135
+ SFNClient,
136
+ paginateListStateMachines,
137
+ ListTagsForResourceCommand as ListSFNTagsCommand
138
+ } from '@aws-sdk/client-sfn';
139
+ import {
140
+ EventBridgeClient,
141
+ paginateListEventBuses,
142
+ paginateListRules,
143
+ ListTagsForResourceCommand as ListEventBridgeTagsCommand
144
+ } from '@aws-sdk/client-eventbridge';
145
+ import {
146
+ CloudWatchClient,
147
+ paginateDescribeAlarms,
148
+ ListTagsForResourceCommand as ListCloudWatchTagsCommand
149
+ } from '@aws-sdk/client-cloudwatch';
150
+ import {
151
+ CloudWatchLogsClient,
152
+ paginateDescribeLogGroups,
153
+ ListTagsForResourceCommand as ListCWLogsTagsCommand
154
+ } from '@aws-sdk/client-cloudwatch-logs';
155
+ import {
156
+ CloudTrailClient,
157
+ paginateListTrails,
158
+ GetTrailCommand,
159
+ ListTagsCommand as ListCloudTrailTagsCommand
160
+ } from '@aws-sdk/client-cloudtrail';
161
+ import {
162
+ ConfigServiceClient,
163
+ DescribeConfigurationRecordersCommand,
164
+ DescribeDeliveryChannelsCommand
165
+ } from '@aws-sdk/client-config-service';
166
+ import {
167
+ ACMClient,
168
+ paginateListCertificates,
169
+ DescribeCertificateCommand,
170
+ ListTagsForCertificateCommand
171
+ } from '@aws-sdk/client-acm';
172
+ import {
173
+ WAFClient,
174
+ paginateListWebACLs as paginateListWAFWebACLs,
175
+ ListTagsForResourceCommand as ListWAFTagsCommand
176
+ } from '@aws-sdk/client-waf';
177
+ import {
178
+ WAFV2Client,
179
+ paginateListWebACLs as paginateListWAFV2WebACLs,
180
+ ListTagsForResourceCommand as ListWAFV2TagsCommand
181
+ } from '@aws-sdk/client-wafv2';
182
+ import {
183
+ CognitoIdentityProviderClient,
184
+ paginateListUserPools,
185
+ DescribeUserPoolCommand,
186
+ ListTagsForResourceCommand as ListCognitoTagsCommand
187
+ } from '@aws-sdk/client-cognito-identity-provider';
188
+ import {
189
+ paginateDescribeVolumes,
190
+ paginateDescribeSnapshots as paginateDescribeEBSSnapshots,
191
+ DescribeVpnConnectionsCommand,
192
+ DescribeCustomerGatewaysCommand,
193
+ DescribeTransitGatewaysCommand,
194
+ DescribeTransitGatewayAttachmentsCommand
195
+ } from '@aws-sdk/client-ec2';
196
+ import {
197
+ BackupClient,
198
+ paginateListBackupPlans,
199
+ paginateListBackupVaults,
200
+ ListTagsCommand as ListBackupTagsCommand
201
+ } from '@aws-sdk/client-backup';
202
+ import {
203
+ KinesisClient,
204
+ paginateListStreams,
205
+ DescribeStreamCommand,
206
+ ListTagsForStreamCommand
207
+ } from '@aws-sdk/client-kinesis';
208
+
209
+ import { BaseCloudDriver } from './base-driver.js';
210
+
211
+ const DEFAULT_SERVICES = [
212
+ 'ec2', 's3', 'rds', 'iam', 'lambda',
213
+ 'vpc', 'elb', 'alb', 'nlb',
214
+ 'dynamodb', 'sqs', 'sns',
215
+ 'ecs', 'eks', 'apigateway', 'cloudfront', 'route53',
216
+ 'kms', 'secretsmanager', 'ssm',
217
+ 'elasticache', 'efs', 'ecr',
218
+ 'stepfunctions', 'eventbridge', 'cloudwatch', 'logs',
219
+ 'cloudtrail', 'config', 'acm', 'waf', 'wafv2', 'cognito',
220
+ 'ebs', 'vpn', 'transitgateway', 'backup', 'kinesis'
221
+ ];
222
+ const GLOBAL_REGION = 'us-east-1';
223
+
224
+ function normaliseServiceName(name) {
225
+ return (name || '').toString().trim().toLowerCase();
226
+ }
227
+
228
+ function buildTagObject(entries, keyKey = 'Key', valueKey = 'Value') {
229
+ if (!Array.isArray(entries) || entries.length === 0) {
230
+ return null;
231
+ }
232
+ const out = {};
233
+ for (const entry of entries) {
234
+ if (!entry || typeof entry !== 'object') continue;
235
+ const key = entry[keyKey];
236
+ if (!key) continue;
237
+ out[key] = entry[valueKey] ?? null;
238
+ }
239
+ return Object.keys(out).length ? out : null;
240
+ }
241
+
242
+ function buildCredentialProvider(credentials = {}) {
243
+ if (!credentials || typeof credentials !== 'object') {
244
+ return fromNodeProviderChain();
245
+ }
246
+
247
+ if (credentials.accessKeyId && credentials.secretAccessKey) {
248
+ const staticCredentials = {
249
+ accessKeyId: credentials.accessKeyId,
250
+ secretAccessKey: credentials.secretAccessKey,
251
+ sessionToken: credentials.sessionToken
252
+ };
253
+ return async () => staticCredentials;
254
+ }
255
+
256
+ if (credentials.profile) {
257
+ return fromIni({ profile: credentials.profile });
258
+ }
259
+
260
+ if (credentials.processProfile) {
261
+ return fromProcess({ profile: credentials.processProfile });
262
+ }
263
+
264
+ return fromNodeProviderChain();
265
+ }
266
+
267
+ function ensureArray(value, fallback = []) {
268
+ if (!value) return fallback;
269
+ if (Array.isArray(value)) return value;
270
+ return [value];
271
+ }
272
+
273
+ function shouldCollect(service, includeSet, excludeSet) {
274
+ const name = normaliseServiceName(service);
275
+ if (excludeSet.has(name)) return false;
276
+ if (includeSet.size > 0 && !includeSet.has(name)) return false;
277
+ return true;
278
+ }
279
+
280
+ function extractEc2Tags(instance) {
281
+ if (!instance?.Tags) return null;
282
+ const tags = {};
283
+ for (const { Key, Value } of instance.Tags) {
284
+ if (!Key) continue;
285
+ tags[Key] = Value ?? null;
286
+ }
287
+ return Object.keys(tags).length ? tags : null;
288
+ }
289
+
290
+ export class AwsInventoryDriver extends BaseCloudDriver {
291
+ constructor(options = {}) {
292
+ super({ ...options, driver: options.driver || 'aws' });
293
+ this._clients = {
294
+ ec2: new Map(),
295
+ s3: null,
296
+ rds: new Map(),
297
+ iam: null,
298
+ lambda: new Map(),
299
+ sts: null,
300
+ elb: new Map(),
301
+ elbv2: new Map(),
302
+ dynamodb: new Map(),
303
+ sqs: new Map(),
304
+ sns: new Map(),
305
+ ecs: new Map(),
306
+ eks: new Map(),
307
+ apigateway: new Map(),
308
+ apigatewayv2: new Map(),
309
+ cloudfront: null,
310
+ route53: null,
311
+ kms: new Map(),
312
+ secretsmanager: new Map(),
313
+ ssm: new Map(),
314
+ elasticache: new Map(),
315
+ efs: new Map(),
316
+ ecr: new Map(),
317
+ sfn: new Map(),
318
+ eventbridge: new Map(),
319
+ cloudwatch: new Map(),
320
+ logs: new Map(),
321
+ cloudtrail: new Map(),
322
+ config: new Map(),
323
+ acm: new Map(),
324
+ waf: null,
325
+ wafv2: new Map(),
326
+ cognito: new Map(),
327
+ backup: new Map(),
328
+ kinesis: new Map()
329
+ };
330
+ this._accountId = null;
331
+ this._credentialProvider = buildCredentialProvider(this.credentials);
332
+ this._services = ensureArray(this.config?.services, DEFAULT_SERVICES)
333
+ .map(normaliseServiceName)
334
+ .filter(Boolean);
335
+ if (!this._services.length) {
336
+ this._services = [...DEFAULT_SERVICES];
337
+ }
338
+ this._regions = ensureArray(this.config?.regions, [this.config?.region || GLOBAL_REGION]);
339
+ if (!this._regions.length) {
340
+ this._regions = [GLOBAL_REGION];
341
+ }
342
+ }
343
+
344
+ async initialize() {
345
+ await this._initializeSts();
346
+ this.logger('info', 'AWS driver initialized', {
347
+ accountId: this._accountId,
348
+ services: this._services,
349
+ regions: this._regions
350
+ });
351
+ }
352
+
353
+ async *_collectEc2Instances() {
354
+ for (const region of this._regions) {
355
+ const client = this._getEc2Client(region);
356
+ const paginator = paginateDescribeInstances({ client }, {});
357
+ for await (const page of paginator) {
358
+ const reservations = page.Reservations || [];
359
+ for (const reservation of reservations) {
360
+ const instances = reservation.Instances || [];
361
+ for (const instance of instances) {
362
+ const instanceId = instance.InstanceId;
363
+ if (!instanceId) continue;
364
+ yield {
365
+ provider: 'aws',
366
+ accountId: this._accountId,
367
+ region,
368
+ service: 'ec2',
369
+ resourceType: 'ec2.instance',
370
+ resourceId: instanceId,
371
+ name: extractInstanceName(instance),
372
+ tags: extractEc2Tags(instance),
373
+ configuration: sanitizeConfiguration(instance)
374
+ };
375
+ }
376
+ }
377
+ }
378
+ }
379
+ }
380
+
381
+ async *_collectS3Buckets() {
382
+ const client = this._getS3Client();
383
+ const response = await client.send(new ListBucketsCommand({}));
384
+ const buckets = response.Buckets || [];
385
+
386
+ for (const bucket of buckets) {
387
+ const bucketName = bucket.Name;
388
+ if (!bucketName) continue;
389
+
390
+ const region = await this._resolveBucketRegion(client, bucketName);
391
+ const tags = await this._resolveBucketTags(client, bucketName);
392
+
393
+ yield {
394
+ provider: 'aws',
395
+ accountId: this._accountId,
396
+ region,
397
+ service: 's3',
398
+ resourceType: 's3.bucket',
399
+ resourceId: bucketName,
400
+ name: bucketName,
401
+ tags,
402
+ configuration: sanitizeConfiguration({
403
+ ...bucket,
404
+ Region: region,
405
+ Owner: response.Owner || null
406
+ })
407
+ };
408
+ }
409
+ }
410
+
411
+ async *_collectRdsInstances() {
412
+ for (const region of this._regions) {
413
+ const client = this._getRdsClient(region);
414
+ const paginator = paginateDescribeDBInstances({ client }, {});
415
+ for await (const page of paginator) {
416
+ const instances = page.DBInstances || [];
417
+ for (const instance of instances) {
418
+ const resourceId = instance.DbiResourceId || instance.DBInstanceIdentifier;
419
+ if (!resourceId) continue;
420
+ const arn = instance.DBInstanceArn;
421
+ const tags = await this._safeListTagsForResource(client, arn);
422
+ yield {
423
+ provider: 'aws',
424
+ accountId: this._accountId,
425
+ region,
426
+ service: 'rds',
427
+ resourceType: 'rds.instance',
428
+ resourceId,
429
+ name: instance.DBInstanceIdentifier || resourceId,
430
+ tags,
431
+ configuration: sanitizeConfiguration(instance)
432
+ };
433
+ }
434
+ }
435
+ }
436
+ }
437
+
438
+ async *_collectIamIdentities() {
439
+ const client = this._getIamClient();
440
+ const userPaginator = paginateListUsers({ client }, {});
441
+ for await (const page of userPaginator) {
442
+ const users = page.Users || [];
443
+ for (const user of users) {
444
+ const tags = await this._safeListIamTags(client, new ListUserTagsCommand({ UserName: user.UserName }));
445
+ yield {
446
+ provider: 'aws',
447
+ accountId: this._accountId,
448
+ region: null,
449
+ service: 'iam',
450
+ resourceType: 'iam.user',
451
+ resourceId: user.Arn || user.UserId || user.UserName,
452
+ name: user.UserName,
453
+ tags,
454
+ configuration: sanitizeConfiguration(user)
455
+ };
456
+ }
457
+ }
458
+
459
+ const rolePaginator = paginateListRoles({ client }, {});
460
+ for await (const page of rolePaginator) {
461
+ const roles = page.Roles || [];
462
+ for (const role of roles) {
463
+ const tags = await this._safeListIamTags(client, new ListRoleTagsCommand({ RoleName: role.RoleName }));
464
+ yield {
465
+ provider: 'aws',
466
+ accountId: this._accountId,
467
+ region: null,
468
+ service: 'iam',
469
+ resourceType: 'iam.role',
470
+ resourceId: role.Arn || role.RoleId || role.RoleName,
471
+ name: role.RoleName,
472
+ tags,
473
+ configuration: sanitizeConfiguration(role)
474
+ };
475
+ }
476
+ }
477
+ }
478
+
479
+ async *_collectLambdaFunctions() {
480
+ for (const region of this._regions) {
481
+ const client = this._getLambdaClient(region);
482
+ const paginator = paginateListFunctions({ client }, {});
483
+ for await (const page of paginator) {
484
+ const functions = page.Functions || [];
485
+ for (const lambda of functions) {
486
+ const arn = lambda.FunctionArn;
487
+ let tags = null;
488
+ if (arn) {
489
+ tags = await this._safeListLambdaTags(client, arn);
490
+ }
491
+ yield {
492
+ provider: 'aws',
493
+ accountId: this._accountId,
494
+ region,
495
+ service: 'lambda',
496
+ resourceType: 'lambda.function',
497
+ resourceId: arn || lambda.FunctionName,
498
+ name: lambda.FunctionName,
499
+ tags,
500
+ configuration: sanitizeConfiguration(lambda)
501
+ };
502
+ }
503
+ }
504
+ }
505
+ }
506
+
507
+ async *_collectVpcResources() {
508
+ for (const region of this._regions) {
509
+ const client = this._getEc2Client(region);
510
+
511
+ // VPCs
512
+ const vpcPaginator = paginateDescribeVpcs({ client }, {});
513
+ for await (const page of vpcPaginator) {
514
+ const vpcs = page.Vpcs || [];
515
+ for (const vpc of vpcs) {
516
+ yield {
517
+ provider: 'aws',
518
+ accountId: this._accountId,
519
+ region,
520
+ service: 'vpc',
521
+ resourceType: 'vpc.vpc',
522
+ resourceId: vpc.VpcId,
523
+ name: extractEc2Tags(vpc)?.Name || vpc.VpcId,
524
+ tags: extractEc2Tags(vpc),
525
+ configuration: sanitizeConfiguration(vpc)
526
+ };
527
+ }
528
+ }
529
+
530
+ // Subnets
531
+ const subnetPaginator = paginateDescribeSubnets({ client }, {});
532
+ for await (const page of subnetPaginator) {
533
+ const subnets = page.Subnets || [];
534
+ for (const subnet of subnets) {
535
+ yield {
536
+ provider: 'aws',
537
+ accountId: this._accountId,
538
+ region,
539
+ service: 'vpc',
540
+ resourceType: 'vpc.subnet',
541
+ resourceId: subnet.SubnetId,
542
+ name: extractEc2Tags(subnet)?.Name || subnet.SubnetId,
543
+ tags: extractEc2Tags(subnet),
544
+ configuration: sanitizeConfiguration(subnet)
545
+ };
546
+ }
547
+ }
548
+
549
+ // Security Groups
550
+ const sgPaginator = paginateDescribeSecurityGroups({ client }, {});
551
+ for await (const page of sgPaginator) {
552
+ const groups = page.SecurityGroups || [];
553
+ for (const sg of groups) {
554
+ yield {
555
+ provider: 'aws',
556
+ accountId: this._accountId,
557
+ region,
558
+ service: 'vpc',
559
+ resourceType: 'vpc.security-group',
560
+ resourceId: sg.GroupId,
561
+ name: sg.GroupName,
562
+ tags: extractEc2Tags(sg),
563
+ configuration: sanitizeConfiguration(sg)
564
+ };
565
+ }
566
+ }
567
+
568
+ // Route Tables
569
+ const rtPaginator = paginateDescribeRouteTables({ client }, {});
570
+ for await (const page of rtPaginator) {
571
+ const tables = page.RouteTables || [];
572
+ for (const rt of tables) {
573
+ yield {
574
+ provider: 'aws',
575
+ accountId: this._accountId,
576
+ region,
577
+ service: 'vpc',
578
+ resourceType: 'vpc.route-table',
579
+ resourceId: rt.RouteTableId,
580
+ name: extractEc2Tags(rt)?.Name || rt.RouteTableId,
581
+ tags: extractEc2Tags(rt),
582
+ configuration: sanitizeConfiguration(rt)
583
+ };
584
+ }
585
+ }
586
+
587
+ // Internet Gateways
588
+ const igwPaginator = paginateDescribeInternetGateways({ client }, {});
589
+ for await (const page of igwPaginator) {
590
+ const gateways = page.InternetGateways || [];
591
+ for (const igw of gateways) {
592
+ yield {
593
+ provider: 'aws',
594
+ accountId: this._accountId,
595
+ region,
596
+ service: 'vpc',
597
+ resourceType: 'vpc.internet-gateway',
598
+ resourceId: igw.InternetGatewayId,
599
+ name: extractEc2Tags(igw)?.Name || igw.InternetGatewayId,
600
+ tags: extractEc2Tags(igw),
601
+ configuration: sanitizeConfiguration(igw)
602
+ };
603
+ }
604
+ }
605
+
606
+ // NAT Gateways
607
+ const natPaginator = paginateDescribeNatGateways({ client }, {});
608
+ for await (const page of natPaginator) {
609
+ const gateways = page.NatGateways || [];
610
+ for (const nat of gateways) {
611
+ yield {
612
+ provider: 'aws',
613
+ accountId: this._accountId,
614
+ region,
615
+ service: 'vpc',
616
+ resourceType: 'vpc.nat-gateway',
617
+ resourceId: nat.NatGatewayId,
618
+ name: extractEc2Tags(nat)?.Name || nat.NatGatewayId,
619
+ tags: extractEc2Tags(nat),
620
+ configuration: sanitizeConfiguration(nat)
621
+ };
622
+ }
623
+ }
624
+
625
+ // Network ACLs
626
+ const aclPaginator = paginateDescribeNetworkAcls({ client }, {});
627
+ for await (const page of aclPaginator) {
628
+ const acls = page.NetworkAcls || [];
629
+ for (const acl of acls) {
630
+ yield {
631
+ provider: 'aws',
632
+ accountId: this._accountId,
633
+ region,
634
+ service: 'vpc',
635
+ resourceType: 'vpc.network-acl',
636
+ resourceId: acl.NetworkAclId,
637
+ name: extractEc2Tags(acl)?.Name || acl.NetworkAclId,
638
+ tags: extractEc2Tags(acl),
639
+ configuration: sanitizeConfiguration(acl)
640
+ };
641
+ }
642
+ }
643
+ }
644
+ }
645
+
646
+ async *_collectLoadBalancers() {
647
+ for (const region of this._regions) {
648
+ // Classic Load Balancers
649
+ const elbClient = this._getElbClient(region);
650
+ const classicPaginator = paginateDescribeClassicLoadBalancers({ client: elbClient }, {});
651
+ for await (const page of classicPaginator) {
652
+ const lbs = page.LoadBalancerDescriptions || [];
653
+ for (const lb of lbs) {
654
+ const tags = await this._safeListClassicLBTags(elbClient, lb.LoadBalancerName);
655
+ yield {
656
+ provider: 'aws',
657
+ accountId: this._accountId,
658
+ region,
659
+ service: 'elb',
660
+ resourceType: 'elb.classic',
661
+ resourceId: lb.LoadBalancerName,
662
+ name: lb.LoadBalancerName,
663
+ tags,
664
+ configuration: sanitizeConfiguration(lb)
665
+ };
666
+ }
667
+ }
668
+
669
+ // ALB/NLB (ELBv2)
670
+ const elbv2Client = this._getElbv2Client(region);
671
+ const v2Paginator = paginateDescribeLoadBalancers({ client: elbv2Client }, {});
672
+ for await (const page of v2Paginator) {
673
+ const lbs = page.LoadBalancers || [];
674
+ for (const lb of lbs) {
675
+ const arn = lb.LoadBalancerArn;
676
+ const tags = await this._safeListELBv2Tags(elbv2Client, [arn]);
677
+ const lbType = lb.Type === 'application' ? 'alb' : lb.Type === 'network' ? 'nlb' : 'elb';
678
+ yield {
679
+ provider: 'aws',
680
+ accountId: this._accountId,
681
+ region,
682
+ service: lbType,
683
+ resourceType: `${lbType}.load-balancer`,
684
+ resourceId: arn,
685
+ name: lb.LoadBalancerName,
686
+ tags,
687
+ configuration: sanitizeConfiguration(lb)
688
+ };
689
+ }
690
+ }
691
+
692
+ // Target Groups
693
+ const tgPaginator = paginateDescribeTargetGroups({ client: elbv2Client }, {});
694
+ for await (const page of tgPaginator) {
695
+ const groups = page.TargetGroups || [];
696
+ for (const tg of groups) {
697
+ const arn = tg.TargetGroupArn;
698
+ const tags = await this._safeListELBv2Tags(elbv2Client, [arn]);
699
+ yield {
700
+ provider: 'aws',
701
+ accountId: this._accountId,
702
+ region,
703
+ service: 'elb',
704
+ resourceType: 'elb.target-group',
705
+ resourceId: arn,
706
+ name: tg.TargetGroupName,
707
+ tags,
708
+ configuration: sanitizeConfiguration(tg)
709
+ };
710
+ }
711
+ }
712
+ }
713
+ }
714
+
715
+ async *_collectDynamoDBTables() {
716
+ for (const region of this._regions) {
717
+ const client = this._getDynamoDBClient(region);
718
+ const paginator = paginateListTables({ client }, {});
719
+ for await (const page of paginator) {
720
+ const tables = page.TableNames || [];
721
+ for (const tableName of tables) {
722
+ const description = await client.send(new DescribeTableCommand({ TableName: tableName }));
723
+ const table = description.Table;
724
+ const arn = table?.TableArn;
725
+ let tags = null;
726
+ if (arn) {
727
+ tags = await this._safeListDynamoDBTags(client, arn);
728
+ }
729
+ yield {
730
+ provider: 'aws',
731
+ accountId: this._accountId,
732
+ region,
733
+ service: 'dynamodb',
734
+ resourceType: 'dynamodb.table',
735
+ resourceId: arn || tableName,
736
+ name: tableName,
737
+ tags,
738
+ configuration: sanitizeConfiguration(table)
739
+ };
740
+ }
741
+ }
742
+ }
743
+ }
744
+
745
+ async *_collectSQSQueues() {
746
+ for (const region of this._regions) {
747
+ const client = this._getSqsClient(region);
748
+ const paginator = paginateListQueues({ client }, {});
749
+ for await (const page of paginator) {
750
+ const urls = page.QueueUrls || [];
751
+ for (const queueUrl of urls) {
752
+ const attributes = await this._safeGetQueueAttributes(client, queueUrl);
753
+ const tags = await this._safeListQueueTags(client, queueUrl);
754
+ const queueName = queueUrl.split('/').pop();
755
+ const arn = attributes?.QueueArn;
756
+ yield {
757
+ provider: 'aws',
758
+ accountId: this._accountId,
759
+ region,
760
+ service: 'sqs',
761
+ resourceType: 'sqs.queue',
762
+ resourceId: arn || queueUrl,
763
+ name: queueName,
764
+ tags,
765
+ configuration: sanitizeConfiguration({ ...attributes, QueueUrl: queueUrl })
766
+ };
767
+ }
768
+ }
769
+ }
770
+ }
771
+
772
+ async *_collectSNSTopics() {
773
+ for (const region of this._regions) {
774
+ const client = this._getSnsClient(region);
775
+ const paginator = paginateListTopics({ client }, {});
776
+ for await (const page of paginator) {
777
+ const topics = page.Topics || [];
778
+ for (const topic of topics) {
779
+ const arn = topic.TopicArn;
780
+ const attributes = await this._safeGetTopicAttributes(client, arn);
781
+ const tags = await this._safeListSNSTags(client, arn);
782
+ const topicName = arn?.split(':').pop();
783
+ yield {
784
+ provider: 'aws',
785
+ accountId: this._accountId,
786
+ region,
787
+ service: 'sns',
788
+ resourceType: 'sns.topic',
789
+ resourceId: arn,
790
+ name: topicName,
791
+ tags,
792
+ configuration: sanitizeConfiguration({ ...attributes, TopicArn: arn })
793
+ };
794
+ }
795
+ }
796
+ }
797
+ }
798
+
799
+ async *_collectECSResources() {
800
+ for (const region of this._regions) {
801
+ const client = this._getEcsClient(region);
802
+
803
+ // ECS Clusters
804
+ const clusterPaginator = paginateListClusters({ client }, {});
805
+ for await (const page of clusterPaginator) {
806
+ const arns = page.clusterArns || [];
807
+ for (const arn of arns) {
808
+ const tags = await this._safeListECSTags(client, arn);
809
+ const name = arn.split('/').pop();
810
+ yield {
811
+ provider: 'aws',
812
+ accountId: this._accountId,
813
+ region,
814
+ service: 'ecs',
815
+ resourceType: 'ecs.cluster',
816
+ resourceId: arn,
817
+ name,
818
+ tags,
819
+ configuration: sanitizeConfiguration({ clusterArn: arn })
820
+ };
821
+
822
+ // Services per cluster
823
+ const servicePaginator = paginateListServices({ client }, { cluster: arn });
824
+ for await (const servicePage of servicePaginator) {
825
+ const serviceArns = servicePage.serviceArns || [];
826
+ if (serviceArns.length === 0) continue;
827
+ const described = await client.send(new DescribeServicesCommand({
828
+ cluster: arn,
829
+ services: serviceArns,
830
+ include: ['TAGS']
831
+ }));
832
+ const services = described.services || [];
833
+ for (const service of services) {
834
+ yield {
835
+ provider: 'aws',
836
+ accountId: this._accountId,
837
+ region,
838
+ service: 'ecs',
839
+ resourceType: 'ecs.service',
840
+ resourceId: service.serviceArn,
841
+ name: service.serviceName,
842
+ tags: buildTagObject(service.tags),
843
+ configuration: sanitizeConfiguration(service)
844
+ };
845
+ }
846
+ }
847
+ }
848
+ }
849
+
850
+ // Task Definitions
851
+ const taskDefPaginator = paginateListTaskDefinitions({ client }, {});
852
+ for await (const page of taskDefPaginator) {
853
+ const arns = page.taskDefinitionArns || [];
854
+ for (const arn of arns) {
855
+ const described = await client.send(new DescribeTaskDefinitionCommand({
856
+ taskDefinition: arn,
857
+ include: ['TAGS']
858
+ }));
859
+ const taskDef = described.taskDefinition;
860
+ const tags = buildTagObject(described.tags);
861
+ yield {
862
+ provider: 'aws',
863
+ accountId: this._accountId,
864
+ region,
865
+ service: 'ecs',
866
+ resourceType: 'ecs.task-definition',
867
+ resourceId: taskDef?.taskDefinitionArn || arn,
868
+ name: taskDef?.family,
869
+ tags,
870
+ configuration: sanitizeConfiguration(taskDef)
871
+ };
872
+ }
873
+ }
874
+ }
875
+ }
876
+
877
+ async *_collectEKSClusters() {
878
+ for (const region of this._regions) {
879
+ const client = this._getEksClient(region);
880
+ const clusterPaginator = paginateListEKSClusters({ client }, {});
881
+ for await (const page of clusterPaginator) {
882
+ const names = page.clusters || [];
883
+ for (const name of names) {
884
+ const described = await client.send(new DescribeClusterCommand({ name }));
885
+ const cluster = described.cluster;
886
+ const arn = cluster?.arn;
887
+ const tags = await this._safeListEKSTags(client, arn);
888
+ yield {
889
+ provider: 'aws',
890
+ accountId: this._accountId,
891
+ region,
892
+ service: 'eks',
893
+ resourceType: 'eks.cluster',
894
+ resourceId: arn || name,
895
+ name,
896
+ tags,
897
+ configuration: sanitizeConfiguration(cluster)
898
+ };
899
+
900
+ // Node Groups
901
+ const ngPaginator = paginateListNodegroups({ client }, { clusterName: name });
902
+ for await (const ngPage of ngPaginator) {
903
+ const nodegroups = ngPage.nodegroups || [];
904
+ for (const ngName of nodegroups) {
905
+ const ngDescribed = await client.send(new DescribeNodegroupCommand({
906
+ clusterName: name,
907
+ nodegroupName: ngName
908
+ }));
909
+ const ng = ngDescribed.nodegroup;
910
+ const ngArn = ng?.nodegroupArn;
911
+ const ngTags = await this._safeListEKSTags(client, ngArn);
912
+ yield {
913
+ provider: 'aws',
914
+ accountId: this._accountId,
915
+ region,
916
+ service: 'eks',
917
+ resourceType: 'eks.nodegroup',
918
+ resourceId: ngArn || ngName,
919
+ name: ngName,
920
+ tags: ngTags,
921
+ configuration: sanitizeConfiguration(ng)
922
+ };
923
+ }
924
+ }
925
+ }
926
+ }
927
+ }
928
+ }
929
+
930
+ async *_collectAPIGateways() {
931
+ for (const region of this._regions) {
932
+ // REST APIs (v1)
933
+ const v1Client = this._getApiGatewayClient(region);
934
+ const v1Paginator = paginateGetRestApis({ client: v1Client }, {});
935
+ for await (const page of v1Paginator) {
936
+ const apis = page.items || [];
937
+ for (const api of apis) {
938
+ const tags = await this._safeGetAPIGatewayTags(v1Client, api.id);
939
+ yield {
940
+ provider: 'aws',
941
+ accountId: this._accountId,
942
+ region,
943
+ service: 'apigateway',
944
+ resourceType: 'apigateway.rest-api',
945
+ resourceId: api.id,
946
+ name: api.name,
947
+ tags,
948
+ configuration: sanitizeConfiguration(api)
949
+ };
950
+ }
951
+ }
952
+
953
+ // HTTP/WebSocket APIs (v2)
954
+ const v2Client = this._getApiGatewayV2Client(region);
955
+ const v2Paginator = paginateGetApis({ client: v2Client }, {});
956
+ for await (const page of v2Paginator) {
957
+ const apis = page.Items || [];
958
+ for (const api of apis) {
959
+ const tags = await this._safeGetAPIGatewayV2Tags(v2Client, api.ApiId);
960
+ const type = api.ProtocolType?.toLowerCase() || 'http';
961
+ yield {
962
+ provider: 'aws',
963
+ accountId: this._accountId,
964
+ region,
965
+ service: 'apigateway',
966
+ resourceType: `apigateway.${type}-api`,
967
+ resourceId: api.ApiId,
968
+ name: api.Name,
969
+ tags,
970
+ configuration: sanitizeConfiguration(api)
971
+ };
972
+ }
973
+ }
974
+ }
975
+ }
976
+
977
+ async *_collectCloudFrontDistributions() {
978
+ const client = this._getCloudFrontClient();
979
+ const paginator = paginateListDistributions({ client }, {});
980
+ for await (const page of paginator) {
981
+ const items = page.DistributionList?.Items || [];
982
+ for (const dist of items) {
983
+ const arn = dist.ARN;
984
+ const tags = await this._safeListCloudFrontTags(client, arn);
985
+ yield {
986
+ provider: 'aws',
987
+ accountId: this._accountId,
988
+ region: null,
989
+ service: 'cloudfront',
990
+ resourceType: 'cloudfront.distribution',
991
+ resourceId: dist.Id,
992
+ name: dist.DomainName,
993
+ tags,
994
+ configuration: sanitizeConfiguration(dist)
995
+ };
996
+ }
997
+ }
998
+ }
999
+
1000
+ async *_collectRoute53HostedZones() {
1001
+ const client = this._getRoute53Client();
1002
+ const paginator = paginateListHostedZones({ client }, {});
1003
+ for await (const page of paginator) {
1004
+ const zones = page.HostedZones || [];
1005
+ for (const zone of zones) {
1006
+ const zoneId = zone.Id?.replace('/hostedzone/', '');
1007
+ const tags = await this._safeListRoute53Tags(client, zone.Id);
1008
+ yield {
1009
+ provider: 'aws',
1010
+ accountId: this._accountId,
1011
+ region: null,
1012
+ service: 'route53',
1013
+ resourceType: 'route53.hosted-zone',
1014
+ resourceId: zoneId,
1015
+ name: zone.Name,
1016
+ tags,
1017
+ configuration: sanitizeConfiguration(zone)
1018
+ };
1019
+ }
1020
+ }
1021
+ }
1022
+
1023
+ async *_collectKMSKeys() {
1024
+ for (const region of this._regions) {
1025
+ const client = this._getKmsClient(region);
1026
+ const paginator = paginateListKeys({ client }, {});
1027
+ for await (const page of paginator) {
1028
+ const keys = page.Keys || [];
1029
+ for (const key of keys) {
1030
+ const described = await client.send(new DescribeKeyCommand({ KeyId: key.KeyId }));
1031
+ const metadata = described.KeyMetadata;
1032
+ const tags = await this._safeListKMSTags(client, key.KeyId);
1033
+ yield {
1034
+ provider: 'aws',
1035
+ accountId: this._accountId,
1036
+ region,
1037
+ service: 'kms',
1038
+ resourceType: 'kms.key',
1039
+ resourceId: metadata?.Arn || key.KeyId,
1040
+ name: metadata?.Description || key.KeyId,
1041
+ tags,
1042
+ configuration: sanitizeConfiguration(metadata)
1043
+ };
1044
+ }
1045
+ }
1046
+ }
1047
+ }
1048
+
1049
+ async *_collectSecretsManagerSecrets() {
1050
+ for (const region of this._regions) {
1051
+ const client = this._getSecretsManagerClient(region);
1052
+ const paginator = paginateListSecrets({ client }, {});
1053
+ for await (const page of paginator) {
1054
+ const secrets = page.SecretList || [];
1055
+ for (const secret of secrets) {
1056
+ const described = await client.send(new DescribeSecretCommand({ SecretId: secret.ARN }));
1057
+ const tags = buildTagObject(described.Tags);
1058
+ yield {
1059
+ provider: 'aws',
1060
+ accountId: this._accountId,
1061
+ region,
1062
+ service: 'secretsmanager',
1063
+ resourceType: 'secretsmanager.secret',
1064
+ resourceId: described.ARN || secret.ARN,
1065
+ name: described.Name || secret.Name,
1066
+ tags,
1067
+ configuration: sanitizeConfiguration(described)
1068
+ };
1069
+ }
1070
+ }
1071
+ }
1072
+ }
1073
+
1074
+ async *_collectSSMParameters() {
1075
+ for (const region of this._regions) {
1076
+ const client = this._getSsmClient(region);
1077
+ const paginator = paginateDescribeParameters({ client }, {});
1078
+ for await (const page of paginator) {
1079
+ const params = page.Parameters || [];
1080
+ for (const param of params) {
1081
+ const tags = await this._safeListSSMTags(client, param.Name);
1082
+ yield {
1083
+ provider: 'aws',
1084
+ accountId: this._accountId,
1085
+ region,
1086
+ service: 'ssm',
1087
+ resourceType: 'ssm.parameter',
1088
+ resourceId: param.Name,
1089
+ name: param.Name,
1090
+ tags,
1091
+ configuration: sanitizeConfiguration(param)
1092
+ };
1093
+ }
1094
+ }
1095
+ }
1096
+ }
1097
+
1098
+ async *_collectElastiCacheClusters() {
1099
+ for (const region of this._regions) {
1100
+ const client = this._getElastiCacheClient(region);
1101
+ const paginator = paginateDescribeCacheClusters({ client }, { ShowCacheNodeInfo: true });
1102
+ for await (const page of paginator) {
1103
+ const clusters = page.CacheClusters || [];
1104
+ for (const cluster of clusters) {
1105
+ const arn = cluster.ARN;
1106
+ const tags = await this._safeListElastiCacheTags(client, arn);
1107
+ yield {
1108
+ provider: 'aws',
1109
+ accountId: this._accountId,
1110
+ region,
1111
+ service: 'elasticache',
1112
+ resourceType: 'elasticache.cluster',
1113
+ resourceId: arn || cluster.CacheClusterId,
1114
+ name: cluster.CacheClusterId,
1115
+ tags,
1116
+ configuration: sanitizeConfiguration(cluster)
1117
+ };
1118
+ }
1119
+ }
1120
+ }
1121
+ }
1122
+
1123
+ async *_collectEFSFileSystems() {
1124
+ for (const region of this._regions) {
1125
+ const client = this._getEfsClient(region);
1126
+ const paginator = paginateDescribeFileSystems({ client }, {});
1127
+ for await (const page of paginator) {
1128
+ const filesystems = page.FileSystems || [];
1129
+ for (const fs of filesystems) {
1130
+ const tags = await this._safeDescribeEFSTags(client, fs.FileSystemId);
1131
+ yield {
1132
+ provider: 'aws',
1133
+ accountId: this._accountId,
1134
+ region,
1135
+ service: 'efs',
1136
+ resourceType: 'efs.filesystem',
1137
+ resourceId: fs.FileSystemArn || fs.FileSystemId,
1138
+ name: fs.Name || fs.FileSystemId,
1139
+ tags,
1140
+ configuration: sanitizeConfiguration(fs)
1141
+ };
1142
+ }
1143
+ }
1144
+ }
1145
+ }
1146
+
1147
+ async *_collectECRRepositories() {
1148
+ for (const region of this._regions) {
1149
+ const client = this._getEcrClient(region);
1150
+ const paginator = paginateDescribeRepositories({ client }, {});
1151
+ for await (const page of paginator) {
1152
+ const repos = page.repositories || [];
1153
+ for (const repo of repos) {
1154
+ const tags = await this._safeListECRTags(client, repo.repositoryArn);
1155
+ yield {
1156
+ provider: 'aws',
1157
+ accountId: this._accountId,
1158
+ region,
1159
+ service: 'ecr',
1160
+ resourceType: 'ecr.repository',
1161
+ resourceId: repo.repositoryArn,
1162
+ name: repo.repositoryName,
1163
+ tags,
1164
+ configuration: sanitizeConfiguration(repo)
1165
+ };
1166
+ }
1167
+ }
1168
+ }
1169
+ }
1170
+
1171
+ async *_collectStepFunctionsStateMachines() {
1172
+ for (const region of this._regions) {
1173
+ const client = this._getSfnClient(region);
1174
+ const paginator = paginateListStateMachines({ client }, {});
1175
+ for await (const page of paginator) {
1176
+ const machines = page.stateMachines || [];
1177
+ for (const machine of machines) {
1178
+ const tags = await this._safeListSFNTags(client, machine.stateMachineArn);
1179
+ yield {
1180
+ provider: 'aws',
1181
+ accountId: this._accountId,
1182
+ region,
1183
+ service: 'stepfunctions',
1184
+ resourceType: 'stepfunctions.statemachine',
1185
+ resourceId: machine.stateMachineArn,
1186
+ name: machine.name,
1187
+ tags,
1188
+ configuration: sanitizeConfiguration(machine)
1189
+ };
1190
+ }
1191
+ }
1192
+ }
1193
+ }
1194
+
1195
+ async *_collectEventBridgeResources() {
1196
+ for (const region of this._regions) {
1197
+ const client = this._getEventBridgeClient(region);
1198
+
1199
+ // Event Buses
1200
+ const busPaginator = paginateListEventBuses({ client }, {});
1201
+ for await (const page of busPaginator) {
1202
+ const buses = page.EventBuses || [];
1203
+ for (const bus of buses) {
1204
+ const tags = await this._safeListEventBridgeTags(client, bus.Arn);
1205
+ yield {
1206
+ provider: 'aws',
1207
+ accountId: this._accountId,
1208
+ region,
1209
+ service: 'eventbridge',
1210
+ resourceType: 'eventbridge.bus',
1211
+ resourceId: bus.Arn || bus.Name,
1212
+ name: bus.Name,
1213
+ tags,
1214
+ configuration: sanitizeConfiguration(bus)
1215
+ };
1216
+ }
1217
+ }
1218
+
1219
+ // Rules
1220
+ const rulePaginator = paginateListRules({ client }, {});
1221
+ for await (const page of rulePaginator) {
1222
+ const rules = page.Rules || [];
1223
+ for (const rule of rules) {
1224
+ const tags = await this._safeListEventBridgeTags(client, rule.Arn);
1225
+ yield {
1226
+ provider: 'aws',
1227
+ accountId: this._accountId,
1228
+ region,
1229
+ service: 'eventbridge',
1230
+ resourceType: 'eventbridge.rule',
1231
+ resourceId: rule.Arn || rule.Name,
1232
+ name: rule.Name,
1233
+ tags,
1234
+ configuration: sanitizeConfiguration(rule)
1235
+ };
1236
+ }
1237
+ }
1238
+ }
1239
+ }
1240
+
1241
+ async *_collectCloudWatchResources() {
1242
+ for (const region of this._regions) {
1243
+ // CloudWatch Alarms
1244
+ const cwClient = this._getCloudWatchClient(region);
1245
+ const alarmPaginator = paginateDescribeAlarms({ client: cwClient }, {});
1246
+ for await (const page of alarmPaginator) {
1247
+ const alarms = page.MetricAlarms || [];
1248
+ for (const alarm of alarms) {
1249
+ const tags = await this._safeListCloudWatchTags(cwClient, alarm.AlarmArn);
1250
+ yield {
1251
+ provider: 'aws',
1252
+ accountId: this._accountId,
1253
+ region,
1254
+ service: 'cloudwatch',
1255
+ resourceType: 'cloudwatch.alarm',
1256
+ resourceId: alarm.AlarmArn || alarm.AlarmName,
1257
+ name: alarm.AlarmName,
1258
+ tags,
1259
+ configuration: sanitizeConfiguration(alarm)
1260
+ };
1261
+ }
1262
+ }
1263
+
1264
+ // CloudWatch Log Groups
1265
+ const logsClient = this._getCloudWatchLogsClient(region);
1266
+ const logsPaginator = paginateDescribeLogGroups({ client: logsClient }, {});
1267
+ for await (const page of logsPaginator) {
1268
+ const groups = page.logGroups || [];
1269
+ for (const group of groups) {
1270
+ const tags = await this._safeListCWLogsTags(logsClient, group.logGroupName);
1271
+ yield {
1272
+ provider: 'aws',
1273
+ accountId: this._accountId,
1274
+ region,
1275
+ service: 'logs',
1276
+ resourceType: 'logs.group',
1277
+ resourceId: group.arn || group.logGroupName,
1278
+ name: group.logGroupName,
1279
+ tags,
1280
+ configuration: sanitizeConfiguration(group)
1281
+ };
1282
+ }
1283
+ }
1284
+ }
1285
+ }
1286
+
1287
+ async *_collectCloudTrails() {
1288
+ for (const region of this._regions) {
1289
+ const client = this._getCloudTrailClient(region);
1290
+ const paginator = paginateListTrails({ client }, {});
1291
+ for await (const page of paginator) {
1292
+ const trails = page.Trails || [];
1293
+ for (const trailInfo of trails) {
1294
+ const described = await client.send(new GetTrailCommand({ Name: trailInfo.TrailARN || trailInfo.Name }));
1295
+ const trail = described.Trail;
1296
+ const tags = await this._safeListCloudTrailTags(client, trail?.TrailARN);
1297
+ yield {
1298
+ provider: 'aws',
1299
+ accountId: this._accountId,
1300
+ region,
1301
+ service: 'cloudtrail',
1302
+ resourceType: 'cloudtrail.trail',
1303
+ resourceId: trail?.TrailARN || trail?.Name,
1304
+ name: trail?.Name,
1305
+ tags,
1306
+ configuration: sanitizeConfiguration(trail)
1307
+ };
1308
+ }
1309
+ }
1310
+ }
1311
+ }
1312
+
1313
+ async *_collectConfigResources() {
1314
+ for (const region of this._regions) {
1315
+ const client = this._getConfigServiceClient(region);
1316
+
1317
+ // Configuration Recorders
1318
+ const recorderResponse = await client.send(new DescribeConfigurationRecordersCommand({}));
1319
+ const recorders = recorderResponse.ConfigurationRecorders || [];
1320
+ for (const recorder of recorders) {
1321
+ yield {
1322
+ provider: 'aws',
1323
+ accountId: this._accountId,
1324
+ region,
1325
+ service: 'config',
1326
+ resourceType: 'config.recorder',
1327
+ resourceId: recorder.name,
1328
+ name: recorder.name,
1329
+ tags: null,
1330
+ configuration: sanitizeConfiguration(recorder)
1331
+ };
1332
+ }
1333
+
1334
+ // Delivery Channels
1335
+ const channelResponse = await client.send(new DescribeDeliveryChannelsCommand({}));
1336
+ const channels = channelResponse.DeliveryChannels || [];
1337
+ for (const channel of channels) {
1338
+ yield {
1339
+ provider: 'aws',
1340
+ accountId: this._accountId,
1341
+ region,
1342
+ service: 'config',
1343
+ resourceType: 'config.delivery-channel',
1344
+ resourceId: channel.name,
1345
+ name: channel.name,
1346
+ tags: null,
1347
+ configuration: sanitizeConfiguration(channel)
1348
+ };
1349
+ }
1350
+ }
1351
+ }
1352
+
1353
+ async *_collectACMCertificates() {
1354
+ for (const region of this._regions) {
1355
+ const client = this._getAcmClient(region);
1356
+ const paginator = paginateListCertificates({ client }, {});
1357
+ for await (const page of paginator) {
1358
+ const certs = page.CertificateSummaryList || [];
1359
+ for (const certSummary of certs) {
1360
+ const arn = certSummary.CertificateArn;
1361
+ const described = await client.send(new DescribeCertificateCommand({ CertificateArn: arn }));
1362
+ const cert = described.Certificate;
1363
+ const tags = await this._safeListACMTags(client, arn);
1364
+ yield {
1365
+ provider: 'aws',
1366
+ accountId: this._accountId,
1367
+ region,
1368
+ service: 'acm',
1369
+ resourceType: 'acm.certificate',
1370
+ resourceId: arn,
1371
+ name: cert?.DomainName,
1372
+ tags,
1373
+ configuration: sanitizeConfiguration(cert)
1374
+ };
1375
+ }
1376
+ }
1377
+ }
1378
+ }
1379
+
1380
+ async *_collectWAFResources() {
1381
+ // WAF Classic (global)
1382
+ const wafClient = this._getWafClient();
1383
+ const classicPaginator = paginateListWAFWebACLs({ client: wafClient }, {});
1384
+ for await (const page of classicPaginator) {
1385
+ const webACLs = page.WebACLs || [];
1386
+ for (const acl of webACLs) {
1387
+ const tags = await this._safeListWAFTags(wafClient, acl.WebACLId);
1388
+ yield {
1389
+ provider: 'aws',
1390
+ accountId: this._accountId,
1391
+ region: null,
1392
+ service: 'waf',
1393
+ resourceType: 'waf.webacl',
1394
+ resourceId: acl.WebACLId,
1395
+ name: acl.Name,
1396
+ tags,
1397
+ configuration: sanitizeConfiguration(acl)
1398
+ };
1399
+ }
1400
+ }
1401
+ }
1402
+
1403
+ async *_collectWAFV2Resources() {
1404
+ for (const region of this._regions) {
1405
+ const client = this._getWafv2Client(region);
1406
+
1407
+ // Regional WebACLs
1408
+ const regionalPaginator = paginateListWAFV2WebACLs({ client }, { Scope: 'REGIONAL' });
1409
+ for await (const page of regionalPaginator) {
1410
+ const webACLs = page.WebACLs || [];
1411
+ for (const acl of webACLs) {
1412
+ const tags = await this._safeListWAFV2Tags(client, acl.ARN);
1413
+ yield {
1414
+ provider: 'aws',
1415
+ accountId: this._accountId,
1416
+ region,
1417
+ service: 'wafv2',
1418
+ resourceType: 'wafv2.webacl',
1419
+ resourceId: acl.ARN,
1420
+ name: acl.Name,
1421
+ tags,
1422
+ configuration: sanitizeConfiguration(acl)
1423
+ };
1424
+ }
1425
+ }
1426
+ }
1427
+
1428
+ // CloudFront WebACLs (us-east-1 only)
1429
+ const cfClient = this._getWafv2Client(GLOBAL_REGION);
1430
+ const cfPaginator = paginateListWAFV2WebACLs({ client: cfClient }, { Scope: 'CLOUDFRONT' });
1431
+ for await (const page of cfPaginator) {
1432
+ const webACLs = page.WebACLs || [];
1433
+ for (const acl of webACLs) {
1434
+ const tags = await this._safeListWAFV2Tags(cfClient, acl.ARN);
1435
+ yield {
1436
+ provider: 'aws',
1437
+ accountId: this._accountId,
1438
+ region: GLOBAL_REGION,
1439
+ service: 'wafv2',
1440
+ resourceType: 'wafv2.webacl-cloudfront',
1441
+ resourceId: acl.ARN,
1442
+ name: acl.Name,
1443
+ tags,
1444
+ configuration: sanitizeConfiguration(acl)
1445
+ };
1446
+ }
1447
+ }
1448
+ }
1449
+
1450
+ async *_collectCognitoUserPools() {
1451
+ for (const region of this._regions) {
1452
+ const client = this._getCognitoClient(region);
1453
+ const paginator = paginateListUserPools({ client }, { MaxResults: 60 });
1454
+ for await (const page of paginator) {
1455
+ const pools = page.UserPools || [];
1456
+ for (const pool of pools) {
1457
+ const described = await client.send(new DescribeUserPoolCommand({ UserPoolId: pool.Id }));
1458
+ const fullPool = described.UserPool;
1459
+ const tags = await this._safeListCognitoTags(client, fullPool?.Arn);
1460
+ yield {
1461
+ provider: 'aws',
1462
+ accountId: this._accountId,
1463
+ region,
1464
+ service: 'cognito',
1465
+ resourceType: 'cognito.userpool',
1466
+ resourceId: fullPool?.Arn || pool.Id,
1467
+ name: pool.Name,
1468
+ tags,
1469
+ configuration: sanitizeConfiguration(fullPool)
1470
+ };
1471
+ }
1472
+ }
1473
+ }
1474
+ }
1475
+
1476
+ async *_collectEBSResources() {
1477
+ for (const region of this._regions) {
1478
+ const client = this._getEc2Client(region);
1479
+
1480
+ // EBS Volumes
1481
+ const volPaginator = paginateDescribeVolumes({ client }, {});
1482
+ for await (const page of volPaginator) {
1483
+ const volumes = page.Volumes || [];
1484
+ for (const volume of volumes) {
1485
+ yield {
1486
+ provider: 'aws',
1487
+ accountId: this._accountId,
1488
+ region,
1489
+ service: 'ebs',
1490
+ resourceType: 'ebs.volume',
1491
+ resourceId: volume.VolumeId,
1492
+ name: extractEc2Tags(volume)?.Name || volume.VolumeId,
1493
+ tags: extractEc2Tags(volume),
1494
+ configuration: sanitizeConfiguration(volume)
1495
+ };
1496
+ }
1497
+ }
1498
+
1499
+ // EBS Snapshots
1500
+ const snapPaginator = paginateDescribeEBSSnapshots({ client }, { OwnerIds: ['self'] });
1501
+ for await (const page of snapPaginator) {
1502
+ const snapshots = page.Snapshots || [];
1503
+ for (const snapshot of snapshots) {
1504
+ yield {
1505
+ provider: 'aws',
1506
+ accountId: this._accountId,
1507
+ region,
1508
+ service: 'ebs',
1509
+ resourceType: 'ebs.snapshot',
1510
+ resourceId: snapshot.SnapshotId,
1511
+ name: extractEc2Tags(snapshot)?.Name || snapshot.SnapshotId,
1512
+ tags: extractEc2Tags(snapshot),
1513
+ configuration: sanitizeConfiguration(snapshot)
1514
+ };
1515
+ }
1516
+ }
1517
+ }
1518
+ }
1519
+
1520
+ async *_collectVPNResources() {
1521
+ for (const region of this._regions) {
1522
+ const client = this._getEc2Client(region);
1523
+
1524
+ // VPN Connections
1525
+ const vpnResult = await client.send(new DescribeVpnConnectionsCommand({}));
1526
+ const connections = vpnResult.VpnConnections || [];
1527
+ for (const vpn of connections) {
1528
+ yield {
1529
+ provider: 'aws',
1530
+ accountId: this._accountId,
1531
+ region,
1532
+ service: 'vpn',
1533
+ resourceType: 'vpn.connection',
1534
+ resourceId: vpn.VpnConnectionId,
1535
+ name: extractEc2Tags(vpn)?.Name || vpn.VpnConnectionId,
1536
+ tags: extractEc2Tags(vpn),
1537
+ configuration: sanitizeConfiguration(vpn)
1538
+ };
1539
+ }
1540
+
1541
+ // Customer Gateways
1542
+ const cgwResult = await client.send(new DescribeCustomerGatewaysCommand({}));
1543
+ const gateways = cgwResult.CustomerGateways || [];
1544
+ for (const cgw of gateways) {
1545
+ yield {
1546
+ provider: 'aws',
1547
+ accountId: this._accountId,
1548
+ region,
1549
+ service: 'vpn',
1550
+ resourceType: 'vpn.customer-gateway',
1551
+ resourceId: cgw.CustomerGatewayId,
1552
+ name: extractEc2Tags(cgw)?.Name || cgw.CustomerGatewayId,
1553
+ tags: extractEc2Tags(cgw),
1554
+ configuration: sanitizeConfiguration(cgw)
1555
+ };
1556
+ }
1557
+ }
1558
+ }
1559
+
1560
+ async *_collectTransitGatewayResources() {
1561
+ for (const region of this._regions) {
1562
+ const client = this._getEc2Client(region);
1563
+
1564
+ // Transit Gateways
1565
+ const tgwResult = await client.send(new DescribeTransitGatewaysCommand({}));
1566
+ const gateways = tgwResult.TransitGateways || [];
1567
+ for (const tgw of gateways) {
1568
+ yield {
1569
+ provider: 'aws',
1570
+ accountId: this._accountId,
1571
+ region,
1572
+ service: 'transitgateway',
1573
+ resourceType: 'transitgateway.gateway',
1574
+ resourceId: tgw.TransitGatewayId,
1575
+ name: extractEc2Tags(tgw)?.Name || tgw.TransitGatewayId,
1576
+ tags: extractEc2Tags(tgw),
1577
+ configuration: sanitizeConfiguration(tgw)
1578
+ };
1579
+ }
1580
+
1581
+ // Transit Gateway Attachments
1582
+ const attResult = await client.send(new DescribeTransitGatewayAttachmentsCommand({}));
1583
+ const attachments = attResult.TransitGatewayAttachments || [];
1584
+ for (const att of attachments) {
1585
+ yield {
1586
+ provider: 'aws',
1587
+ accountId: this._accountId,
1588
+ region,
1589
+ service: 'transitgateway',
1590
+ resourceType: 'transitgateway.attachment',
1591
+ resourceId: att.TransitGatewayAttachmentId,
1592
+ name: extractEc2Tags(att)?.Name || att.TransitGatewayAttachmentId,
1593
+ tags: extractEc2Tags(att),
1594
+ configuration: sanitizeConfiguration(att)
1595
+ };
1596
+ }
1597
+ }
1598
+ }
1599
+
1600
+ async *_collectBackupResources() {
1601
+ for (const region of this._regions) {
1602
+ const client = this._getBackupClient(region);
1603
+
1604
+ // Backup Plans
1605
+ const planPaginator = paginateListBackupPlans({ client }, {});
1606
+ for await (const page of planPaginator) {
1607
+ const plans = page.BackupPlansList || [];
1608
+ for (const plan of plans) {
1609
+ const tags = await this._safeListBackupTags(client, plan.BackupPlanArn);
1610
+ yield {
1611
+ provider: 'aws',
1612
+ accountId: this._accountId,
1613
+ region,
1614
+ service: 'backup',
1615
+ resourceType: 'backup.plan',
1616
+ resourceId: plan.BackupPlanArn,
1617
+ name: plan.BackupPlanName,
1618
+ tags,
1619
+ configuration: sanitizeConfiguration(plan)
1620
+ };
1621
+ }
1622
+ }
1623
+
1624
+ // Backup Vaults
1625
+ const vaultPaginator = paginateListBackupVaults({ client }, {});
1626
+ for await (const page of vaultPaginator) {
1627
+ const vaults = page.BackupVaultList || [];
1628
+ for (const vault of vaults) {
1629
+ const tags = await this._safeListBackupTags(client, vault.BackupVaultArn);
1630
+ yield {
1631
+ provider: 'aws',
1632
+ accountId: this._accountId,
1633
+ region,
1634
+ service: 'backup',
1635
+ resourceType: 'backup.vault',
1636
+ resourceId: vault.BackupVaultArn,
1637
+ name: vault.BackupVaultName,
1638
+ tags,
1639
+ configuration: sanitizeConfiguration(vault)
1640
+ };
1641
+ }
1642
+ }
1643
+ }
1644
+ }
1645
+
1646
+ async *_collectKinesisStreams() {
1647
+ for (const region of this._regions) {
1648
+ const client = this._getKinesisClient(region);
1649
+ const paginator = paginateListStreams({ client }, {});
1650
+ for await (const page of paginator) {
1651
+ const streamNames = page.StreamNames || [];
1652
+ for (const streamName of streamNames) {
1653
+ const described = await client.send(new DescribeStreamCommand({ StreamName: streamName }));
1654
+ const stream = described.StreamDescription;
1655
+ const arn = stream?.StreamARN;
1656
+ const tags = await this._safeListKinesisTags(client, streamName);
1657
+ yield {
1658
+ provider: 'aws',
1659
+ accountId: this._accountId,
1660
+ region,
1661
+ service: 'kinesis',
1662
+ resourceType: 'kinesis.stream',
1663
+ resourceId: arn || streamName,
1664
+ name: streamName,
1665
+ tags,
1666
+ configuration: sanitizeConfiguration(stream)
1667
+ };
1668
+ }
1669
+ }
1670
+ }
1671
+ }
1672
+
1673
+ async *listResources(options = {}) {
1674
+ const discoveryInclude = ensureArray(options.discovery?.include)
1675
+ .map(normaliseServiceName)
1676
+ .filter(Boolean);
1677
+ const discoveryExclude = ensureArray(options.discovery?.exclude)
1678
+ .map(normaliseServiceName)
1679
+ .filter(Boolean);
1680
+
1681
+ const includeSet = new Set(discoveryInclude);
1682
+ const excludeSet = new Set(discoveryExclude);
1683
+
1684
+ const runtime = options.runtime || {};
1685
+ const emitProgress = typeof runtime.emitProgress === 'function'
1686
+ ? runtime.emitProgress.bind(runtime)
1687
+ : null;
1688
+
1689
+ const collectors = {
1690
+ ec2: this._collectEc2Instances.bind(this),
1691
+ s3: this._collectS3Buckets.bind(this),
1692
+ rds: this._collectRdsInstances.bind(this),
1693
+ iam: this._collectIamIdentities.bind(this),
1694
+ lambda: this._collectLambdaFunctions.bind(this),
1695
+ vpc: this._collectVpcResources.bind(this),
1696
+ elb: this._collectLoadBalancers.bind(this),
1697
+ alb: this._collectLoadBalancers.bind(this),
1698
+ nlb: this._collectLoadBalancers.bind(this),
1699
+ dynamodb: this._collectDynamoDBTables.bind(this),
1700
+ sqs: this._collectSQSQueues.bind(this),
1701
+ sns: this._collectSNSTopics.bind(this),
1702
+ ecs: this._collectECSResources.bind(this),
1703
+ eks: this._collectEKSClusters.bind(this),
1704
+ apigateway: this._collectAPIGateways.bind(this),
1705
+ cloudfront: this._collectCloudFrontDistributions.bind(this),
1706
+ route53: this._collectRoute53HostedZones.bind(this),
1707
+ kms: this._collectKMSKeys.bind(this),
1708
+ secretsmanager: this._collectSecretsManagerSecrets.bind(this),
1709
+ ssm: this._collectSSMParameters.bind(this),
1710
+ elasticache: this._collectElastiCacheClusters.bind(this),
1711
+ efs: this._collectEFSFileSystems.bind(this),
1712
+ ecr: this._collectECRRepositories.bind(this),
1713
+ stepfunctions: this._collectStepFunctionsStateMachines.bind(this),
1714
+ eventbridge: this._collectEventBridgeResources.bind(this),
1715
+ cloudwatch: this._collectCloudWatchResources.bind(this),
1716
+ logs: this._collectCloudWatchResources.bind(this),
1717
+ cloudtrail: this._collectCloudTrails.bind(this),
1718
+ config: this._collectConfigResources.bind(this),
1719
+ acm: this._collectACMCertificates.bind(this),
1720
+ waf: this._collectWAFResources.bind(this),
1721
+ wafv2: this._collectWAFV2Resources.bind(this),
1722
+ cognito: this._collectCognitoUserPools.bind(this),
1723
+ ebs: this._collectEBSResources.bind(this),
1724
+ vpn: this._collectVPNResources.bind(this),
1725
+ transitgateway: this._collectTransitGatewayResources.bind(this),
1726
+ backup: this._collectBackupResources.bind(this),
1727
+ kinesis: this._collectKinesisStreams.bind(this)
1728
+ };
1729
+
1730
+ for (const service of this._services) {
1731
+ if (!collectors[service]) {
1732
+ this.logger('debug', 'AWS service collector not implemented, skipping', { service });
1733
+ continue;
1734
+ }
1735
+ if (!shouldCollect(service, includeSet, excludeSet)) {
1736
+ this.logger('debug', 'AWS service filtered out', { service });
1737
+ continue;
1738
+ }
1739
+
1740
+ try {
1741
+ for await (const resource of collectors[service]()) {
1742
+ if (emitProgress) {
1743
+ emitProgress({
1744
+ service,
1745
+ resourceId: resource.resourceId,
1746
+ resourceType: resource.resourceType
1747
+ });
1748
+ }
1749
+ yield resource;
1750
+ }
1751
+ } catch (err) {
1752
+ this.logger('error', 'AWS service collection failed, skipping to next service', {
1753
+ service,
1754
+ error: err.message,
1755
+ errorName: err.name,
1756
+ stack: err.stack
1757
+ });
1758
+ // Continue with next service instead of failing entire sync
1759
+ }
1760
+ }
1761
+ }
1762
+
1763
+ async _initializeSts() {
1764
+ if (this._accountId) return;
1765
+ const client = this._getStsClient();
1766
+ const response = await client.send(new GetCallerIdentityCommand({}));
1767
+ this._accountId = response.Account || null;
1768
+ }
1769
+
1770
+ _getStsClient() {
1771
+ if (!this._clients.sts) {
1772
+ this._clients.sts = new STSClient({
1773
+ region: this.config?.region || GLOBAL_REGION,
1774
+ credentials: this._credentialProvider
1775
+ });
1776
+ }
1777
+ return this._clients.sts;
1778
+ }
1779
+
1780
+ _getEc2Client(region) {
1781
+ if (!this._clients.ec2.has(region)) {
1782
+ this._clients.ec2.set(region, new EC2Client({
1783
+ region,
1784
+ credentials: this._credentialProvider
1785
+ }));
1786
+ }
1787
+ return this._clients.ec2.get(region);
1788
+ }
1789
+
1790
+ _getS3Client() {
1791
+ if (!this._clients.s3) {
1792
+ this._clients.s3 = new S3Client({
1793
+ region: this.config?.s3Region || GLOBAL_REGION,
1794
+ credentials: this._credentialProvider
1795
+ });
1796
+ }
1797
+ return this._clients.s3;
1798
+ }
1799
+
1800
+ _getRdsClient(region) {
1801
+ if (!this._clients.rds.has(region)) {
1802
+ this._clients.rds.set(region, new RDSClient({
1803
+ region,
1804
+ credentials: this._credentialProvider
1805
+ }));
1806
+ }
1807
+ return this._clients.rds.get(region);
1808
+ }
1809
+
1810
+ _getIamClient() {
1811
+ if (!this._clients.iam) {
1812
+ this._clients.iam = new IAMClient({
1813
+ region: this.config?.iamRegion || GLOBAL_REGION,
1814
+ credentials: this._credentialProvider
1815
+ });
1816
+ }
1817
+ return this._clients.iam;
1818
+ }
1819
+
1820
+ _getLambdaClient(region) {
1821
+ if (!this._clients.lambda.has(region)) {
1822
+ this._clients.lambda.set(region, new LambdaClient({
1823
+ region,
1824
+ credentials: this._credentialProvider
1825
+ }));
1826
+ }
1827
+ return this._clients.lambda.get(region);
1828
+ }
1829
+
1830
+ _getElbClient(region) {
1831
+ if (!this._clients.elb.has(region)) {
1832
+ this._clients.elb.set(region, new ElasticLoadBalancingClient({
1833
+ region,
1834
+ credentials: this._credentialProvider
1835
+ }));
1836
+ }
1837
+ return this._clients.elb.get(region);
1838
+ }
1839
+
1840
+ _getElbv2Client(region) {
1841
+ if (!this._clients.elbv2.has(region)) {
1842
+ this._clients.elbv2.set(region, new ElasticLoadBalancingV2Client({
1843
+ region,
1844
+ credentials: this._credentialProvider
1845
+ }));
1846
+ }
1847
+ return this._clients.elbv2.get(region);
1848
+ }
1849
+
1850
+ _getDynamoDBClient(region) {
1851
+ if (!this._clients.dynamodb.has(region)) {
1852
+ this._clients.dynamodb.set(region, new DynamoDBClient({
1853
+ region,
1854
+ credentials: this._credentialProvider
1855
+ }));
1856
+ }
1857
+ return this._clients.dynamodb.get(region);
1858
+ }
1859
+
1860
+ _getSqsClient(region) {
1861
+ if (!this._clients.sqs.has(region)) {
1862
+ this._clients.sqs.set(region, new SQSClient({
1863
+ region,
1864
+ credentials: this._credentialProvider
1865
+ }));
1866
+ }
1867
+ return this._clients.sqs.get(region);
1868
+ }
1869
+
1870
+ _getSnsClient(region) {
1871
+ if (!this._clients.sns.has(region)) {
1872
+ this._clients.sns.set(region, new SNSClient({
1873
+ region,
1874
+ credentials: this._credentialProvider
1875
+ }));
1876
+ }
1877
+ return this._clients.sns.get(region);
1878
+ }
1879
+
1880
+ _getEcsClient(region) {
1881
+ if (!this._clients.ecs.has(region)) {
1882
+ this._clients.ecs.set(region, new ECSClient({
1883
+ region,
1884
+ credentials: this._credentialProvider
1885
+ }));
1886
+ }
1887
+ return this._clients.ecs.get(region);
1888
+ }
1889
+
1890
+ _getEksClient(region) {
1891
+ if (!this._clients.eks.has(region)) {
1892
+ this._clients.eks.set(region, new EKSClient({
1893
+ region,
1894
+ credentials: this._credentialProvider
1895
+ }));
1896
+ }
1897
+ return this._clients.eks.get(region);
1898
+ }
1899
+
1900
+ _getApiGatewayClient(region) {
1901
+ if (!this._clients.apigateway.has(region)) {
1902
+ this._clients.apigateway.set(region, new APIGatewayClient({
1903
+ region,
1904
+ credentials: this._credentialProvider
1905
+ }));
1906
+ }
1907
+ return this._clients.apigateway.get(region);
1908
+ }
1909
+
1910
+ _getApiGatewayV2Client(region) {
1911
+ if (!this._clients.apigatewayv2.has(region)) {
1912
+ this._clients.apigatewayv2.set(region, new ApiGatewayV2Client({
1913
+ region,
1914
+ credentials: this._credentialProvider
1915
+ }));
1916
+ }
1917
+ return this._clients.apigatewayv2.get(region);
1918
+ }
1919
+
1920
+ _getCloudFrontClient() {
1921
+ if (!this._clients.cloudfront) {
1922
+ this._clients.cloudfront = new CloudFrontClient({
1923
+ region: GLOBAL_REGION,
1924
+ credentials: this._credentialProvider
1925
+ });
1926
+ }
1927
+ return this._clients.cloudfront;
1928
+ }
1929
+
1930
+ _getRoute53Client() {
1931
+ if (!this._clients.route53) {
1932
+ this._clients.route53 = new Route53Client({
1933
+ region: GLOBAL_REGION,
1934
+ credentials: this._credentialProvider
1935
+ });
1936
+ }
1937
+ return this._clients.route53;
1938
+ }
1939
+
1940
+ _getKmsClient(region) {
1941
+ if (!this._clients.kms.has(region)) {
1942
+ this._clients.kms.set(region, new KMSClient({
1943
+ region,
1944
+ credentials: this._credentialProvider
1945
+ }));
1946
+ }
1947
+ return this._clients.kms.get(region);
1948
+ }
1949
+
1950
+ _getSecretsManagerClient(region) {
1951
+ if (!this._clients.secretsmanager.has(region)) {
1952
+ this._clients.secretsmanager.set(region, new SecretsManagerClient({
1953
+ region,
1954
+ credentials: this._credentialProvider
1955
+ }));
1956
+ }
1957
+ return this._clients.secretsmanager.get(region);
1958
+ }
1959
+
1960
+ _getSsmClient(region) {
1961
+ if (!this._clients.ssm.has(region)) {
1962
+ this._clients.ssm.set(region, new SSMClient({
1963
+ region,
1964
+ credentials: this._credentialProvider
1965
+ }));
1966
+ }
1967
+ return this._clients.ssm.get(region);
1968
+ }
1969
+
1970
+ _getElastiCacheClient(region) {
1971
+ if (!this._clients.elasticache.has(region)) {
1972
+ this._clients.elasticache.set(region, new ElastiCacheClient({
1973
+ region,
1974
+ credentials: this._credentialProvider
1975
+ }));
1976
+ }
1977
+ return this._clients.elasticache.get(region);
1978
+ }
1979
+
1980
+ _getEfsClient(region) {
1981
+ if (!this._clients.efs.has(region)) {
1982
+ this._clients.efs.set(region, new EFSClient({
1983
+ region,
1984
+ credentials: this._credentialProvider
1985
+ }));
1986
+ }
1987
+ return this._clients.efs.get(region);
1988
+ }
1989
+
1990
+ _getEcrClient(region) {
1991
+ if (!this._clients.ecr.has(region)) {
1992
+ this._clients.ecr.set(region, new ECRClient({
1993
+ region,
1994
+ credentials: this._credentialProvider
1995
+ }));
1996
+ }
1997
+ return this._clients.ecr.get(region);
1998
+ }
1999
+
2000
+ _getSfnClient(region) {
2001
+ if (!this._clients.sfn.has(region)) {
2002
+ this._clients.sfn.set(region, new SFNClient({
2003
+ region,
2004
+ credentials: this._credentialProvider
2005
+ }));
2006
+ }
2007
+ return this._clients.sfn.get(region);
2008
+ }
2009
+
2010
+ _getEventBridgeClient(region) {
2011
+ if (!this._clients.eventbridge.has(region)) {
2012
+ this._clients.eventbridge.set(region, new EventBridgeClient({
2013
+ region,
2014
+ credentials: this._credentialProvider
2015
+ }));
2016
+ }
2017
+ return this._clients.eventbridge.get(region);
2018
+ }
2019
+
2020
+ _getCloudWatchClient(region) {
2021
+ if (!this._clients.cloudwatch.has(region)) {
2022
+ this._clients.cloudwatch.set(region, new CloudWatchClient({
2023
+ region,
2024
+ credentials: this._credentialProvider
2025
+ }));
2026
+ }
2027
+ return this._clients.cloudwatch.get(region);
2028
+ }
2029
+
2030
+ _getCloudWatchLogsClient(region) {
2031
+ if (!this._clients.logs.has(region)) {
2032
+ this._clients.logs.set(region, new CloudWatchLogsClient({
2033
+ region,
2034
+ credentials: this._credentialProvider
2035
+ }));
2036
+ }
2037
+ return this._clients.logs.get(region);
2038
+ }
2039
+
2040
+ _getCloudTrailClient(region) {
2041
+ if (!this._clients.cloudtrail.has(region)) {
2042
+ this._clients.cloudtrail.set(region, new CloudTrailClient({
2043
+ region,
2044
+ credentials: this._credentialProvider
2045
+ }));
2046
+ }
2047
+ return this._clients.cloudtrail.get(region);
2048
+ }
2049
+
2050
+ _getConfigServiceClient(region) {
2051
+ if (!this._clients.config.has(region)) {
2052
+ this._clients.config.set(region, new ConfigServiceClient({
2053
+ region,
2054
+ credentials: this._credentialProvider
2055
+ }));
2056
+ }
2057
+ return this._clients.config.get(region);
2058
+ }
2059
+
2060
+ _getAcmClient(region) {
2061
+ if (!this._clients.acm.has(region)) {
2062
+ this._clients.acm.set(region, new ACMClient({
2063
+ region,
2064
+ credentials: this._credentialProvider
2065
+ }));
2066
+ }
2067
+ return this._clients.acm.get(region);
2068
+ }
2069
+
2070
+ _getWafClient() {
2071
+ if (!this._clients.waf) {
2072
+ this._clients.waf = new WAFClient({
2073
+ region: GLOBAL_REGION,
2074
+ credentials: this._credentialProvider
2075
+ });
2076
+ }
2077
+ return this._clients.waf;
2078
+ }
2079
+
2080
+ _getWafv2Client(region) {
2081
+ if (!this._clients.wafv2.has(region)) {
2082
+ this._clients.wafv2.set(region, new WAFV2Client({
2083
+ region,
2084
+ credentials: this._credentialProvider
2085
+ }));
2086
+ }
2087
+ return this._clients.wafv2.get(region);
2088
+ }
2089
+
2090
+ _getCognitoClient(region) {
2091
+ if (!this._clients.cognito.has(region)) {
2092
+ this._clients.cognito.set(region, new CognitoIdentityProviderClient({
2093
+ region,
2094
+ credentials: this._credentialProvider
2095
+ }));
2096
+ }
2097
+ return this._clients.cognito.get(region);
2098
+ }
2099
+
2100
+ _getBackupClient(region) {
2101
+ if (!this._clients.backup.has(region)) {
2102
+ this._clients.backup.set(region, new BackupClient({
2103
+ region,
2104
+ credentials: this._credentialProvider
2105
+ }));
2106
+ }
2107
+ return this._clients.backup.get(region);
2108
+ }
2109
+
2110
+ _getKinesisClient(region) {
2111
+ if (!this._clients.kinesis.has(region)) {
2112
+ this._clients.kinesis.set(region, new KinesisClient({
2113
+ region,
2114
+ credentials: this._credentialProvider
2115
+ }));
2116
+ }
2117
+ return this._clients.kinesis.get(region);
2118
+ }
2119
+
2120
+ async _resolveBucketRegion(client, bucketName) {
2121
+ try {
2122
+ const output = await client.send(new GetBucketLocationCommand({ Bucket: bucketName }));
2123
+ if (!output?.LocationConstraint) return 'us-east-1';
2124
+ if (output.LocationConstraint === 'EU') return 'eu-west-1';
2125
+ return output.LocationConstraint;
2126
+ } catch (err) {
2127
+ this.logger('warn', 'Failed to resolve bucket region', { bucketName, error: err.message });
2128
+ return null;
2129
+ }
2130
+ }
2131
+
2132
+ async _resolveBucketTags(client, bucketName) {
2133
+ try {
2134
+ const output = await client.send(new GetBucketTaggingCommand({ Bucket: bucketName }));
2135
+ return buildTagObject(output?.TagSet);
2136
+ } catch (err) {
2137
+ if (err?.name === 'NoSuchTagSet' || err?.$metadata?.httpStatusCode === 404) {
2138
+ return null;
2139
+ }
2140
+ this.logger('warn', 'Failed to resolve bucket tags', { bucketName, error: err.message });
2141
+ return null;
2142
+ }
2143
+ }
2144
+
2145
+ async _safeListTagsForResource(client, arn) {
2146
+ if (!arn) return null;
2147
+ try {
2148
+ const output = await client.send(new ListTagsForResourceCommand({ ResourceName: arn }));
2149
+ return buildTagObject(output?.TagList);
2150
+ } catch (err) {
2151
+ this.logger('warn', 'Failed to list RDS tags', { arn, error: err.message });
2152
+ return null;
2153
+ }
2154
+ }
2155
+
2156
+ async _safeListIamTags(client, command) {
2157
+ try {
2158
+ const output = await client.send(command);
2159
+ return buildTagObject(output?.Tags);
2160
+ } catch (err) {
2161
+ if (err?.name === 'NoSuchEntity') {
2162
+ return null;
2163
+ }
2164
+ this.logger('warn', 'Failed to list IAM tags', { error: err.message });
2165
+ return null;
2166
+ }
2167
+ }
2168
+
2169
+ async _safeListLambdaTags(client, functionArn) {
2170
+ try {
2171
+ const output = await client.send(new ListLambdaTagsCommand({ Resource: functionArn }));
2172
+ return output?.Tags || null;
2173
+ } catch (err) {
2174
+ this.logger('warn', 'Failed to list Lambda tags', { functionArn, error: err.message });
2175
+ return null;
2176
+ }
2177
+ }
2178
+
2179
+ async _safeListClassicLBTags(client, loadBalancerName) {
2180
+ try {
2181
+ const output = await client.send(new DescribeClassicLBTagsCommand({
2182
+ LoadBalancerNames: [loadBalancerName]
2183
+ }));
2184
+ const tagDescriptions = output?.TagDescriptions?.[0];
2185
+ return buildTagObject(tagDescriptions?.Tags);
2186
+ } catch (err) {
2187
+ this.logger('warn', 'Failed to list Classic LB tags', { loadBalancerName, error: err.message });
2188
+ return null;
2189
+ }
2190
+ }
2191
+
2192
+ async _safeListELBv2Tags(client, resourceArns) {
2193
+ try {
2194
+ const output = await client.send(new DescribeELBv2TagsCommand({
2195
+ ResourceArns: resourceArns
2196
+ }));
2197
+ const tagDescription = output?.TagDescriptions?.[0];
2198
+ return buildTagObject(tagDescription?.Tags);
2199
+ } catch (err) {
2200
+ this.logger('warn', 'Failed to list ELBv2 tags', { error: err.message });
2201
+ return null;
2202
+ }
2203
+ }
2204
+
2205
+ async _safeListDynamoDBTags(client, resourceArn) {
2206
+ try {
2207
+ const output = await client.send(new ListDynamoDBTagsCommand({
2208
+ ResourceArn: resourceArn
2209
+ }));
2210
+ return buildTagObject(output?.Tags);
2211
+ } catch (err) {
2212
+ this.logger('warn', 'Failed to list DynamoDB tags', { resourceArn, error: err.message });
2213
+ return null;
2214
+ }
2215
+ }
2216
+
2217
+ async _safeGetQueueAttributes(client, queueUrl) {
2218
+ try {
2219
+ const output = await client.send(new GetQueueAttributesCommand({
2220
+ QueueUrl: queueUrl,
2221
+ AttributeNames: ['All']
2222
+ }));
2223
+ return output?.Attributes || null;
2224
+ } catch (err) {
2225
+ this.logger('warn', 'Failed to get queue attributes', { queueUrl, error: err.message });
2226
+ return null;
2227
+ }
2228
+ }
2229
+
2230
+ async _safeListQueueTags(client, queueUrl) {
2231
+ try {
2232
+ const output = await client.send(new ListQueueTagsCommand({
2233
+ QueueUrl: queueUrl
2234
+ }));
2235
+ return output?.Tags || null;
2236
+ } catch (err) {
2237
+ if (err?.name === 'QueueDoesNotExist') {
2238
+ return null;
2239
+ }
2240
+ this.logger('warn', 'Failed to list queue tags', { queueUrl, error: err.message });
2241
+ return null;
2242
+ }
2243
+ }
2244
+
2245
+ async _safeGetTopicAttributes(client, topicArn) {
2246
+ try {
2247
+ const output = await client.send(new GetTopicAttributesCommand({
2248
+ TopicArn: topicArn
2249
+ }));
2250
+ return output?.Attributes || null;
2251
+ } catch (err) {
2252
+ this.logger('warn', 'Failed to get topic attributes', { topicArn, error: err.message });
2253
+ return null;
2254
+ }
2255
+ }
2256
+
2257
+ async _safeListSNSTags(client, resourceArn) {
2258
+ try {
2259
+ const output = await client.send(new ListSNSTagsCommand({
2260
+ ResourceArn: resourceArn
2261
+ }));
2262
+ return buildTagObject(output?.Tags);
2263
+ } catch (err) {
2264
+ this.logger('warn', 'Failed to list SNS tags', { resourceArn, error: err.message });
2265
+ return null;
2266
+ }
2267
+ }
2268
+
2269
+ async _safeListECSTags(client, resourceArn) {
2270
+ try {
2271
+ const output = await client.send(new ListECSTagsCommand({
2272
+ resourceArn
2273
+ }));
2274
+ return buildTagObject(output?.tags);
2275
+ } catch (err) {
2276
+ this.logger('warn', 'Failed to list ECS tags', { resourceArn, error: err.message });
2277
+ return null;
2278
+ }
2279
+ }
2280
+
2281
+ async _safeListEKSTags(client, resourceArn) {
2282
+ if (!resourceArn) return null;
2283
+ try {
2284
+ const output = await client.send(new ListEKSTagsCommand({
2285
+ resourceArn
2286
+ }));
2287
+ return output?.tags || null;
2288
+ } catch (err) {
2289
+ this.logger('warn', 'Failed to list EKS tags', { resourceArn, error: err.message });
2290
+ return null;
2291
+ }
2292
+ }
2293
+
2294
+ async _safeGetAPIGatewayTags(client, resourceId) {
2295
+ try {
2296
+ const output = await client.send(new GetAPIGatewayTagsCommand({
2297
+ resourceArn: `arn:aws:apigateway:${this._regions[0]}::/restapis/${resourceId}`
2298
+ }));
2299
+ return output?.tags || null;
2300
+ } catch (err) {
2301
+ this.logger('warn', 'Failed to get API Gateway tags', { resourceId, error: err.message });
2302
+ return null;
2303
+ }
2304
+ }
2305
+
2306
+ async _safeGetAPIGatewayV2Tags(client, resourceId) {
2307
+ try {
2308
+ const output = await client.send(new GetAPIGatewayV2TagsCommand({
2309
+ ResourceArn: resourceId
2310
+ }));
2311
+ return output?.Tags || null;
2312
+ } catch (err) {
2313
+ this.logger('warn', 'Failed to get API Gateway v2 tags', { resourceId, error: err.message });
2314
+ return null;
2315
+ }
2316
+ }
2317
+
2318
+ async _safeListCloudFrontTags(client, resourceArn) {
2319
+ try {
2320
+ const output = await client.send(new ListCloudFrontTagsCommand({
2321
+ Resource: resourceArn
2322
+ }));
2323
+ return buildTagObject(output?.Tags?.Items);
2324
+ } catch (err) {
2325
+ this.logger('warn', 'Failed to list CloudFront tags', { resourceArn, error: err.message });
2326
+ return null;
2327
+ }
2328
+ }
2329
+
2330
+ async _safeListRoute53Tags(client, resourceId) {
2331
+ try {
2332
+ const output = await client.send(new ListRoute53TagsCommand({
2333
+ ResourceType: 'hostedzone',
2334
+ ResourceId: resourceId.replace('/hostedzone/', '')
2335
+ }));
2336
+ return buildTagObject(output?.ResourceTagSet?.Tags);
2337
+ } catch (err) {
2338
+ this.logger('warn', 'Failed to list Route53 tags', { resourceId, error: err.message });
2339
+ return null;
2340
+ }
2341
+ }
2342
+
2343
+ async _safeListKMSTags(client, keyId) {
2344
+ try {
2345
+ const output = await client.send(new ListResourceTagsCommand({
2346
+ KeyId: keyId
2347
+ }));
2348
+ return buildTagObject(output?.Tags);
2349
+ } catch (err) {
2350
+ this.logger('warn', 'Failed to list KMS tags', { keyId, error: err.message });
2351
+ return null;
2352
+ }
2353
+ }
2354
+
2355
+ async _safeListSSMTags(client, resourceId) {
2356
+ try {
2357
+ const output = await client.send(new ListSSMTagsCommand({
2358
+ ResourceType: 'Parameter',
2359
+ ResourceId: resourceId
2360
+ }));
2361
+ return buildTagObject(output?.TagList);
2362
+ } catch (err) {
2363
+ this.logger('warn', 'Failed to list SSM tags', { resourceId, error: err.message });
2364
+ return null;
2365
+ }
2366
+ }
2367
+
2368
+ async _safeListElastiCacheTags(client, resourceArn) {
2369
+ if (!resourceArn) return null;
2370
+ try {
2371
+ const output = await client.send(new ListElastiCacheTagsCommand({
2372
+ ResourceName: resourceArn
2373
+ }));
2374
+ return buildTagObject(output?.TagList);
2375
+ } catch (err) {
2376
+ this.logger('warn', 'Failed to list ElastiCache tags', { resourceArn, error: err.message });
2377
+ return null;
2378
+ }
2379
+ }
2380
+
2381
+ async _safeDescribeEFSTags(client, fileSystemId) {
2382
+ try {
2383
+ const output = await client.send(new DescribeEFSTagsCommand({
2384
+ FileSystemId: fileSystemId
2385
+ }));
2386
+ return buildTagObject(output?.Tags);
2387
+ } catch (err) {
2388
+ this.logger('warn', 'Failed to describe EFS tags', { fileSystemId, error: err.message });
2389
+ return null;
2390
+ }
2391
+ }
2392
+
2393
+ async _safeListECRTags(client, resourceArn) {
2394
+ try {
2395
+ const output = await client.send(new ListECRTagsCommand({
2396
+ resourceArn
2397
+ }));
2398
+ return buildTagObject(output?.tags);
2399
+ } catch (err) {
2400
+ this.logger('warn', 'Failed to list ECR tags', { resourceArn, error: err.message });
2401
+ return null;
2402
+ }
2403
+ }
2404
+
2405
+ async _safeListSFNTags(client, resourceArn) {
2406
+ try {
2407
+ const output = await client.send(new ListSFNTagsCommand({
2408
+ resourceArn
2409
+ }));
2410
+ return buildTagObject(output?.tags);
2411
+ } catch (err) {
2412
+ this.logger('warn', 'Failed to list Step Functions tags', { resourceArn, error: err.message });
2413
+ return null;
2414
+ }
2415
+ }
2416
+
2417
+ async _safeListEventBridgeTags(client, resourceArn) {
2418
+ if (!resourceArn) return null;
2419
+ try {
2420
+ const output = await client.send(new ListEventBridgeTagsCommand({
2421
+ ResourceARN: resourceArn
2422
+ }));
2423
+ return buildTagObject(output?.Tags);
2424
+ } catch (err) {
2425
+ this.logger('warn', 'Failed to list EventBridge tags', { resourceArn, error: err.message });
2426
+ return null;
2427
+ }
2428
+ }
2429
+
2430
+ async _safeListCloudWatchTags(client, resourceArn) {
2431
+ if (!resourceArn) return null;
2432
+ try {
2433
+ const output = await client.send(new ListCloudWatchTagsCommand({
2434
+ ResourceARN: resourceArn
2435
+ }));
2436
+ return buildTagObject(output?.Tags);
2437
+ } catch (err) {
2438
+ this.logger('warn', 'Failed to list CloudWatch tags', { resourceArn, error: err.message });
2439
+ return null;
2440
+ }
2441
+ }
2442
+
2443
+ async _safeListCWLogsTags(client, logGroupName) {
2444
+ try {
2445
+ const output = await client.send(new ListCWLogsTagsCommand({
2446
+ resourceArn: logGroupName
2447
+ }));
2448
+ return output?.tags || null;
2449
+ } catch (err) {
2450
+ this.logger('warn', 'Failed to list CloudWatch Logs tags', { logGroupName, error: err.message });
2451
+ return null;
2452
+ }
2453
+ }
2454
+
2455
+ async _safeListCloudTrailTags(client, resourceArn) {
2456
+ if (!resourceArn) return null;
2457
+ try {
2458
+ const output = await client.send(new ListCloudTrailTagsCommand({
2459
+ ResourceIdList: [resourceArn]
2460
+ }));
2461
+ const tagsList = output?.ResourceTagList?.[0]?.TagsList;
2462
+ return buildTagObject(tagsList);
2463
+ } catch (err) {
2464
+ this.logger('warn', 'Failed to list CloudTrail tags', { resourceArn, error: err.message });
2465
+ return null;
2466
+ }
2467
+ }
2468
+
2469
+ async _safeListACMTags(client, certificateArn) {
2470
+ try {
2471
+ const output = await client.send(new ListTagsForCertificateCommand({
2472
+ CertificateArn: certificateArn
2473
+ }));
2474
+ return buildTagObject(output?.Tags);
2475
+ } catch (err) {
2476
+ this.logger('warn', 'Failed to list ACM tags', { certificateArn, error: err.message });
2477
+ return null;
2478
+ }
2479
+ }
2480
+
2481
+ async _safeListWAFTags(client, resourceId) {
2482
+ try {
2483
+ const output = await client.send(new ListWAFTagsCommand({
2484
+ ResourceARN: `arn:aws:waf::${this._accountId}:webacl/${resourceId}`
2485
+ }));
2486
+ return buildTagObject(output?.Tags);
2487
+ } catch (err) {
2488
+ this.logger('warn', 'Failed to list WAF tags', { resourceId, error: err.message });
2489
+ return null;
2490
+ }
2491
+ }
2492
+
2493
+ async _safeListWAFV2Tags(client, resourceArn) {
2494
+ try {
2495
+ const output = await client.send(new ListWAFV2TagsCommand({
2496
+ ResourceARN: resourceArn
2497
+ }));
2498
+ return buildTagObject(output?.TagInfoForResource?.TagList);
2499
+ } catch (err) {
2500
+ this.logger('warn', 'Failed to list WAFv2 tags', { resourceArn, error: err.message });
2501
+ return null;
2502
+ }
2503
+ }
2504
+
2505
+ async _safeListCognitoTags(client, resourceArn) {
2506
+ if (!resourceArn) return null;
2507
+ try {
2508
+ const output = await client.send(new ListCognitoTagsCommand({
2509
+ ResourceArn: resourceArn
2510
+ }));
2511
+ return output?.Tags || null;
2512
+ } catch (err) {
2513
+ this.logger('warn', 'Failed to list Cognito tags', { resourceArn, error: err.message });
2514
+ return null;
2515
+ }
2516
+ }
2517
+
2518
+ async _safeListBackupTags(client, resourceArn) {
2519
+ try {
2520
+ const output = await client.send(new ListBackupTagsCommand({
2521
+ ResourceArn: resourceArn
2522
+ }));
2523
+ return output?.Tags || null;
2524
+ } catch (err) {
2525
+ this.logger('warn', 'Failed to list Backup tags', { resourceArn, error: err.message });
2526
+ return null;
2527
+ }
2528
+ }
2529
+
2530
+ async _safeListKinesisTags(client, streamName) {
2531
+ try {
2532
+ const output = await client.send(new ListTagsForStreamCommand({
2533
+ StreamName: streamName
2534
+ }));
2535
+ return buildTagObject(output?.Tags);
2536
+ } catch (err) {
2537
+ this.logger('warn', 'Failed to list Kinesis tags', { streamName, error: err.message });
2538
+ return null;
2539
+ }
2540
+ }
2541
+ }
2542
+
2543
+ function extractInstanceName(instance) {
2544
+ if (!instance?.Tags) return null;
2545
+ const nameTag = instance.Tags.find(tag => tag.Key === 'Name');
2546
+ return nameTag?.Value || null;
2547
+ }
2548
+
2549
+ function sanitizeConfiguration(payload) {
2550
+ if (!payload || typeof payload !== 'object') return payload;
2551
+ return JSON.parse(JSON.stringify(payload));
2552
+ }
2553
+
2554
+ export default AwsInventoryDriver;