infrawise 0.8.2 → 0.8.3
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 +1 -47
- package/dist/adapters/aws/logs.js +3 -1
- package/dist/adapters/aws/s3.js +14 -5
- package/dist/adapters/aws/services.js +12 -7
- package/dist/adapters/db/mongodb.js +1 -3
- package/dist/adapters/iac/terraform.js +52 -25
- package/dist/analyzers/aws-services.js +5 -1
- package/dist/analyzers/rds.js +10 -2
- package/dist/analyzers/terraform.js +18 -3
- package/dist/cli/commands/analyze.js +149 -102
- package/dist/cli/commands/auth.js +4 -1
- package/dist/cli/commands/dev.js +4 -2
- package/dist/cli/commands/doctor.js +64 -24
- package/dist/cli/commands/init.js +25 -7
- package/dist/cli/utils.js +23 -9
- package/dist/context/index.js +36 -9
- package/dist/core/config.js +38 -16
- package/dist/core/index.js +1 -1
- package/dist/graph/index.js +63 -9
- package/dist/server/index.js +170 -51
- package/package.json +18 -5
package/README.md
CHANGED
|
@@ -404,53 +404,7 @@ Infrawise does not use an LLM to analyze your infrastructure. All extraction and
|
|
|
404
404
|
|
|
405
405
|
## Architecture overview
|
|
406
406
|
|
|
407
|
-
|
|
408
|
-
flowchart LR
|
|
409
|
-
subgraph IN["Your Infrastructure & Code"]
|
|
410
|
-
direction TB
|
|
411
|
-
D["DynamoDB"]
|
|
412
|
-
L["Lambda · SQS · SNS\nEventBridge · RDS"]
|
|
413
|
-
S["Secrets Manager · SSM\nCloudWatch"]
|
|
414
|
-
P["PostgreSQL · MySQL"]
|
|
415
|
-
M["MongoDB"]
|
|
416
|
-
T["Terraform · CDK\nCloudFormation"]
|
|
417
|
-
C["TypeScript / JS"]
|
|
418
|
-
end
|
|
419
|
-
|
|
420
|
-
A["Adapters"]
|
|
421
|
-
G["Graph Engine"]
|
|
422
|
-
AN["23 Analyzers"]
|
|
423
|
-
CA["Cache"]
|
|
424
|
-
|
|
425
|
-
subgraph SV["infrawise dev"]
|
|
426
|
-
MCP["MCP Server\nlocalhost:3000/mcp"]
|
|
427
|
-
end
|
|
428
|
-
|
|
429
|
-
subgraph AI["AI Coding Assistants"]
|
|
430
|
-
direction TB
|
|
431
|
-
CC["Claude Code"]
|
|
432
|
-
CU["Cursor"]
|
|
433
|
-
WS["Windsurf"]
|
|
434
|
-
end
|
|
435
|
-
|
|
436
|
-
D & L & S & P & M & T & C --> A
|
|
437
|
-
A --> G --> AN --> CA --> MCP
|
|
438
|
-
MCP --> CC & CU & WS
|
|
439
|
-
|
|
440
|
-
classDef aws fill:#FF9900,stroke:#232F3E,color:#000
|
|
441
|
-
classDef db fill:#336791,stroke:#1a3a5c,color:#fff
|
|
442
|
-
classDef iac fill:#7B42BC,stroke:#4a2080,color:#fff
|
|
443
|
-
classDef code fill:#3178C6,stroke:#1a4a80,color:#fff
|
|
444
|
-
classDef iw fill:#1a1a2e,stroke:#e94560,color:#fff
|
|
445
|
-
classDef ai fill:#10a37f,stroke:#0a6b54,color:#fff
|
|
446
|
-
|
|
447
|
-
class D,L,S aws
|
|
448
|
-
class P,M db
|
|
449
|
-
class T iac
|
|
450
|
-
class C code
|
|
451
|
-
class A,G,AN,CA,MCP iw
|
|
452
|
-
class CC,CU,WS ai
|
|
453
|
-
```
|
|
407
|
+

|
|
454
408
|
|
|
455
409
|
### Source layout
|
|
456
410
|
|
|
@@ -43,7 +43,9 @@ export async function extractLogsMetadata(cfg = {}) {
|
|
|
43
43
|
// Discover log groups
|
|
44
44
|
const logGroups = [];
|
|
45
45
|
try {
|
|
46
|
-
const prefixes = cfg.logGroupPrefixes?.length
|
|
46
|
+
const prefixes = cfg.logGroupPrefixes?.length
|
|
47
|
+
? cfg.logGroupPrefixes
|
|
48
|
+
: [undefined];
|
|
47
49
|
for (const prefix of prefixes) {
|
|
48
50
|
let nextToken;
|
|
49
51
|
do {
|
package/dist/adapters/aws/s3.js
CHANGED
|
@@ -48,18 +48,27 @@ export async function extractS3Metadata(cfg = {}) {
|
|
|
48
48
|
notifications.push(notification);
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
-
const versioned = versionResult.status === 'fulfilled'
|
|
52
|
-
? versionResult.value.Status === 'Enabled'
|
|
53
|
-
: false;
|
|
51
|
+
const versioned = versionResult.status === 'fulfilled' ? versionResult.value.Status === 'Enabled' : false;
|
|
54
52
|
const encrypted = encryptResult.status === 'fulfilled'
|
|
55
53
|
? (encryptResult.value.ServerSideEncryptionConfiguration?.Rules?.length ?? 0) > 0
|
|
56
54
|
: false;
|
|
57
55
|
let publicAccessBlocked = false;
|
|
58
56
|
if (pabResult.status === 'fulfilled') {
|
|
59
57
|
const pab = pabResult.value.PublicAccessBlockConfiguration ?? {};
|
|
60
|
-
publicAccessBlocked = !!(pab.BlockPublicAcls &&
|
|
58
|
+
publicAccessBlocked = !!(pab.BlockPublicAcls &&
|
|
59
|
+
pab.IgnorePublicAcls &&
|
|
60
|
+
pab.BlockPublicPolicy &&
|
|
61
|
+
pab.RestrictPublicBuckets);
|
|
61
62
|
}
|
|
62
|
-
buckets.push({
|
|
63
|
+
buckets.push({
|
|
64
|
+
name,
|
|
65
|
+
arn,
|
|
66
|
+
createdAt,
|
|
67
|
+
versioned,
|
|
68
|
+
encrypted,
|
|
69
|
+
publicAccessBlocked,
|
|
70
|
+
notifications,
|
|
71
|
+
});
|
|
63
72
|
}
|
|
64
73
|
}
|
|
65
74
|
catch (err) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { SQSClient, ListQueuesCommand, GetQueueAttributesCommand
|
|
1
|
+
import { SQSClient, ListQueuesCommand, GetQueueAttributesCommand } from '@aws-sdk/client-sqs';
|
|
2
2
|
import { SNSClient, ListTopicsCommand, GetTopicAttributesCommand, ListSubscriptionsByTopicCommand, } from '@aws-sdk/client-sns';
|
|
3
|
-
import { SSMClient, DescribeParametersCommand
|
|
4
|
-
import { SecretsManagerClient, ListSecretsCommand
|
|
3
|
+
import { SSMClient, DescribeParametersCommand } from '@aws-sdk/client-ssm';
|
|
4
|
+
import { SecretsManagerClient, ListSecretsCommand } from '@aws-sdk/client-secrets-manager';
|
|
5
5
|
import { LambdaClient, ListFunctionsCommand, ListEventSourceMappingsCommand, } from '@aws-sdk/client-lambda';
|
|
6
6
|
import { EventBridgeClient, ListRulesCommand, ListTargetsByRuleCommand, } from '@aws-sdk/client-eventbridge';
|
|
7
|
-
import { RDSClient, DescribeDBInstancesCommand
|
|
7
|
+
import { RDSClient, DescribeDBInstancesCommand } from '@aws-sdk/client-rds';
|
|
8
8
|
import { fromIni } from '@aws-sdk/credential-providers';
|
|
9
9
|
import { logger } from '../../core/index.js';
|
|
10
10
|
function clientConfig(cfg) {
|
|
@@ -33,9 +33,14 @@ export async function extractSQSMetadata(cfg = {}) {
|
|
|
33
33
|
const attrs = await client.send(new GetQueueAttributesCommand({
|
|
34
34
|
QueueUrl: url,
|
|
35
35
|
AttributeNames: [
|
|
36
|
-
'QueueArn',
|
|
37
|
-
'
|
|
38
|
-
'
|
|
36
|
+
'QueueArn',
|
|
37
|
+
'VisibilityTimeout',
|
|
38
|
+
'MessageRetentionPeriod',
|
|
39
|
+
'RedrivePolicy',
|
|
40
|
+
'KmsMasterKeyId',
|
|
41
|
+
'SqsManagedSseEnabled',
|
|
42
|
+
'ApproximateNumberOfMessages',
|
|
43
|
+
'ApproximateNumberOfMessagesNotVisible',
|
|
39
44
|
],
|
|
40
45
|
}));
|
|
41
46
|
const a = attrs.Attributes ?? {};
|
|
@@ -25,9 +25,7 @@ export async function extractMongoMetadata(connectionString, databases) {
|
|
|
25
25
|
else {
|
|
26
26
|
const adminDb = client.db('admin');
|
|
27
27
|
const dbList = await adminDb.admin().listDatabases();
|
|
28
|
-
dbNames = dbList.databases
|
|
29
|
-
.map((d) => d.name)
|
|
30
|
-
.filter((name) => !SYSTEM_DATABASES.has(name));
|
|
28
|
+
dbNames = dbList.databases.map((d) => d.name).filter((name) => !SYSTEM_DATABASES.has(name));
|
|
31
29
|
}
|
|
32
30
|
logger.debug(`Introspecting ${dbNames.length} MongoDB database(s)`);
|
|
33
31
|
const results = [];
|
|
@@ -96,7 +96,8 @@ export async function extractTerraformSchema(repoPath) {
|
|
|
96
96
|
partitionKey: tfStr(body, 'hash_key'),
|
|
97
97
|
sortKey: tfStr(body, 'range_key'),
|
|
98
98
|
gsiNames: tfGSINames(body),
|
|
99
|
-
source: 'terraform',
|
|
99
|
+
source: 'terraform',
|
|
100
|
+
filePath,
|
|
100
101
|
});
|
|
101
102
|
break;
|
|
102
103
|
case 'aws_db_instance':
|
|
@@ -104,13 +105,15 @@ export async function extractTerraformSchema(repoPath) {
|
|
|
104
105
|
schema.rdsInstances.push({
|
|
105
106
|
identifier: tfStr(body, 'identifier') ?? tfStr(body, 'cluster_identifier') ?? resourceName,
|
|
106
107
|
engine: tfStr(body, 'engine') ?? 'unknown',
|
|
107
|
-
source: 'terraform',
|
|
108
|
+
source: 'terraform',
|
|
109
|
+
filePath,
|
|
108
110
|
});
|
|
109
111
|
break;
|
|
110
112
|
case 'aws_docdb_cluster':
|
|
111
113
|
schema.mongoClusters.push({
|
|
112
114
|
identifier: tfStr(body, 'cluster_identifier') ?? resourceName,
|
|
113
|
-
source: 'terraform',
|
|
115
|
+
source: 'terraform',
|
|
116
|
+
filePath,
|
|
114
117
|
});
|
|
115
118
|
break;
|
|
116
119
|
case 'aws_sqs_queue': {
|
|
@@ -124,41 +127,47 @@ export async function extractTerraformSchema(repoPath) {
|
|
|
124
127
|
schema.topics.push({
|
|
125
128
|
name: tfStr(body, 'name') ?? resourceName,
|
|
126
129
|
encrypted: body.includes('kms_master_key_id'),
|
|
127
|
-
source: 'terraform',
|
|
130
|
+
source: 'terraform',
|
|
131
|
+
filePath,
|
|
128
132
|
});
|
|
129
133
|
break;
|
|
130
134
|
case 'aws_lambda_function':
|
|
131
135
|
schema.lambdas.push({
|
|
132
136
|
name: tfStr(body, 'function_name') ?? resourceName,
|
|
133
137
|
runtime: tfStr(body, 'runtime'),
|
|
134
|
-
source: 'terraform',
|
|
138
|
+
source: 'terraform',
|
|
139
|
+
filePath,
|
|
135
140
|
});
|
|
136
141
|
break;
|
|
137
142
|
case 'aws_s3_bucket':
|
|
138
143
|
schema.buckets.push({
|
|
139
144
|
name: tfStr(body, 'bucket') ?? tfStr(body, 'bucket_prefix') ?? resourceName,
|
|
140
145
|
versioned: body.includes('versioning') && body.includes('enabled = true'),
|
|
141
|
-
source: 'terraform',
|
|
146
|
+
source: 'terraform',
|
|
147
|
+
filePath,
|
|
142
148
|
});
|
|
143
149
|
break;
|
|
144
150
|
case 'aws_ssm_parameter':
|
|
145
151
|
schema.parameters.push({
|
|
146
152
|
name: tfStr(body, 'name') ?? resourceName,
|
|
147
153
|
type: tfStr(body, 'type') ?? 'String',
|
|
148
|
-
source: 'terraform',
|
|
154
|
+
source: 'terraform',
|
|
155
|
+
filePath,
|
|
149
156
|
});
|
|
150
157
|
break;
|
|
151
158
|
case 'aws_secretsmanager_secret':
|
|
152
159
|
schema.secrets.push({
|
|
153
160
|
name: tfStr(body, 'name') ?? resourceName,
|
|
154
|
-
source: 'terraform',
|
|
161
|
+
source: 'terraform',
|
|
162
|
+
filePath,
|
|
155
163
|
});
|
|
156
164
|
break;
|
|
157
165
|
case 'aws_api_gateway_rest_api':
|
|
158
166
|
case 'aws_apigatewayv2_api':
|
|
159
167
|
schema.apiGateways.push({
|
|
160
168
|
name: tfStr(body, 'name') ?? resourceName,
|
|
161
|
-
source: 'terraform',
|
|
169
|
+
source: 'terraform',
|
|
170
|
+
filePath,
|
|
162
171
|
});
|
|
163
172
|
break;
|
|
164
173
|
}
|
|
@@ -171,7 +180,8 @@ function isCloudFormationTemplate(parsed) {
|
|
|
171
180
|
if (typeof parsed !== 'object' || parsed === null)
|
|
172
181
|
return false;
|
|
173
182
|
const obj = parsed;
|
|
174
|
-
return 'AWSTemplateFormatVersion' in obj ||
|
|
183
|
+
return ('AWSTemplateFormatVersion' in obj ||
|
|
184
|
+
('Resources' in obj && typeof obj['Resources'] === 'object'));
|
|
175
185
|
}
|
|
176
186
|
function parseCFNFile(filePath) {
|
|
177
187
|
let content;
|
|
@@ -242,7 +252,8 @@ function processCFNResources(resources, schema, filePath, source) {
|
|
|
242
252
|
partitionKey: pk,
|
|
243
253
|
sortKey: sk,
|
|
244
254
|
gsiNames,
|
|
245
|
-
source,
|
|
255
|
+
source,
|
|
256
|
+
filePath,
|
|
246
257
|
});
|
|
247
258
|
break;
|
|
248
259
|
}
|
|
@@ -250,20 +261,23 @@ function processCFNResources(resources, schema, filePath, source) {
|
|
|
250
261
|
schema.rdsInstances.push({
|
|
251
262
|
identifier: cfnStr(props, 'DBInstanceIdentifier') ?? logicalId,
|
|
252
263
|
engine: cfnStr(props, 'Engine') ?? 'unknown',
|
|
253
|
-
source,
|
|
264
|
+
source,
|
|
265
|
+
filePath,
|
|
254
266
|
});
|
|
255
267
|
break;
|
|
256
268
|
case 'AWS::RDS::DBCluster':
|
|
257
269
|
schema.rdsInstances.push({
|
|
258
270
|
identifier: cfnStr(props, 'DBClusterIdentifier') ?? logicalId,
|
|
259
271
|
engine: cfnStr(props, 'Engine') ?? 'aurora',
|
|
260
|
-
source,
|
|
272
|
+
source,
|
|
273
|
+
filePath,
|
|
261
274
|
});
|
|
262
275
|
break;
|
|
263
276
|
case 'AWS::DocDB::DBCluster':
|
|
264
277
|
schema.mongoClusters.push({
|
|
265
278
|
identifier: cfnStr(props, 'DBClusterIdentifier') ?? logicalId,
|
|
266
|
-
source,
|
|
279
|
+
source,
|
|
280
|
+
filePath,
|
|
267
281
|
});
|
|
268
282
|
break;
|
|
269
283
|
case 'AWS::SQS::Queue': {
|
|
@@ -277,14 +291,16 @@ function processCFNResources(resources, schema, filePath, source) {
|
|
|
277
291
|
schema.topics.push({
|
|
278
292
|
name: cfnStr(props, 'TopicName') ?? logicalId,
|
|
279
293
|
encrypted: !!props['KmsMasterKeyId'],
|
|
280
|
-
source,
|
|
294
|
+
source,
|
|
295
|
+
filePath,
|
|
281
296
|
});
|
|
282
297
|
break;
|
|
283
298
|
case 'AWS::Lambda::Function':
|
|
284
299
|
schema.lambdas.push({
|
|
285
300
|
name: cfnStr(props, 'FunctionName') ?? logicalId,
|
|
286
301
|
runtime: cfnStr(props, 'Runtime'),
|
|
287
|
-
source,
|
|
302
|
+
source,
|
|
303
|
+
filePath,
|
|
288
304
|
});
|
|
289
305
|
break;
|
|
290
306
|
case 'AWS::S3::Bucket': {
|
|
@@ -292,7 +308,8 @@ function processCFNResources(resources, schema, filePath, source) {
|
|
|
292
308
|
schema.buckets.push({
|
|
293
309
|
name: cfnStr(props, 'BucketName') ?? logicalId,
|
|
294
310
|
versioned: versioningConfig?.['Status'] === 'Enabled',
|
|
295
|
-
source,
|
|
311
|
+
source,
|
|
312
|
+
filePath,
|
|
296
313
|
});
|
|
297
314
|
break;
|
|
298
315
|
}
|
|
@@ -300,20 +317,23 @@ function processCFNResources(resources, schema, filePath, source) {
|
|
|
300
317
|
schema.parameters.push({
|
|
301
318
|
name: cfnStr(props, 'Name') ?? logicalId,
|
|
302
319
|
type: cfnStr(props, 'Type') ?? 'String',
|
|
303
|
-
source,
|
|
320
|
+
source,
|
|
321
|
+
filePath,
|
|
304
322
|
});
|
|
305
323
|
break;
|
|
306
324
|
case 'AWS::SecretsManager::Secret':
|
|
307
325
|
schema.secrets.push({
|
|
308
326
|
name: cfnStr(props, 'Name') ?? logicalId,
|
|
309
|
-
source,
|
|
327
|
+
source,
|
|
328
|
+
filePath,
|
|
310
329
|
});
|
|
311
330
|
break;
|
|
312
331
|
case 'AWS::ApiGateway::RestApi':
|
|
313
332
|
case 'AWS::ApiGatewayV2::Api':
|
|
314
333
|
schema.apiGateways.push({
|
|
315
334
|
name: cfnStr(props, 'Name') ?? logicalId,
|
|
316
|
-
source,
|
|
335
|
+
source,
|
|
336
|
+
filePath,
|
|
317
337
|
});
|
|
318
338
|
break;
|
|
319
339
|
}
|
|
@@ -340,7 +360,8 @@ export async function extractCDKSchema(repoPath) {
|
|
|
340
360
|
// Strategy 1: Parse cdk.out/*.template.json (synthesized CloudFormation)
|
|
341
361
|
const cdkOutDir = path.join(repoPath, 'cdk.out');
|
|
342
362
|
if (fs.existsSync(cdkOutDir)) {
|
|
343
|
-
const templateFiles = fs
|
|
363
|
+
const templateFiles = fs
|
|
364
|
+
.readdirSync(cdkOutDir)
|
|
344
365
|
.filter((f) => f.endsWith('.template.json'))
|
|
345
366
|
.map((f) => path.join(cdkOutDir, f));
|
|
346
367
|
logger.debug(`Found ${templateFiles.length} CDK synthesized template(s) in cdk.out/`);
|
|
@@ -459,10 +480,16 @@ export async function extractIaCSchema(repoPath) {
|
|
|
459
480
|
extractCDKSchema(repoPath),
|
|
460
481
|
]);
|
|
461
482
|
const merged = mergeSchemas(tfSchema, cfnSchema, cdkSchema);
|
|
462
|
-
const total = merged.dynamoTables.length +
|
|
463
|
-
merged.
|
|
464
|
-
merged.
|
|
465
|
-
merged.
|
|
483
|
+
const total = merged.dynamoTables.length +
|
|
484
|
+
merged.rdsInstances.length +
|
|
485
|
+
merged.mongoClusters.length +
|
|
486
|
+
merged.queues.length +
|
|
487
|
+
merged.topics.length +
|
|
488
|
+
merged.lambdas.length +
|
|
489
|
+
merged.buckets.length +
|
|
490
|
+
merged.parameters.length +
|
|
491
|
+
merged.secrets.length +
|
|
492
|
+
merged.apiGateways.length;
|
|
466
493
|
logger.debug(`IaC schema total: ${total} resource(s) across TF/CFN/CDK`);
|
|
467
494
|
return merged;
|
|
468
495
|
}
|
|
@@ -155,7 +155,11 @@ export class LambdaMissingTriggerDLQAnalyzer {
|
|
|
155
155
|
issue: `Lambda "${node.name}" is triggered by "${trigger.sourceName}" which has no DLQ`,
|
|
156
156
|
description: `"${node.name}" receives events from "${trigger.sourceName}" (${trigger.type.toUpperCase()}). If the Lambda handler fails, messages will be retried and eventually discarded with no failure record.`,
|
|
157
157
|
recommendation: `Add a DLQ to "${trigger.sourceName}" and set a destination config on the event source mapping so failed batches are captured and inspectable.`,
|
|
158
|
-
metadata: {
|
|
158
|
+
metadata: {
|
|
159
|
+
functionName: node.name,
|
|
160
|
+
triggerSource: trigger.sourceName,
|
|
161
|
+
triggerType: trigger.type,
|
|
162
|
+
},
|
|
159
163
|
});
|
|
160
164
|
}
|
|
161
165
|
}
|
package/dist/analyzers/rds.js
CHANGED
|
@@ -12,7 +12,11 @@ export class RDSPubliclyAccessibleAnalyzer {
|
|
|
12
12
|
issue: `RDS instance "${node.name}" is publicly accessible`,
|
|
13
13
|
description: `"${node.name}" (${node.engine}) has PubliclyAccessible=true, meaning it is reachable from the internet. This exposes the database to brute-force and credential-stuffing attacks.`,
|
|
14
14
|
recommendation: `Set PubliclyAccessible=false on "${node.name}" and use a bastion host, VPN, or VPC peering for private access. If public access is required, enforce strong passwords, IP allowlisting, and TLS.`,
|
|
15
|
-
metadata: {
|
|
15
|
+
metadata: {
|
|
16
|
+
dbInstanceIdentifier: node.name,
|
|
17
|
+
engine: node.engine,
|
|
18
|
+
instanceClass: node.instanceClass,
|
|
19
|
+
},
|
|
16
20
|
});
|
|
17
21
|
}
|
|
18
22
|
}
|
|
@@ -32,7 +36,11 @@ export class RDSNoBackupAnalyzer {
|
|
|
32
36
|
issue: `RDS instance "${node.name}" has automated backups disabled`,
|
|
33
37
|
description: `"${node.name}" has a backup retention period of 0, meaning automated backups are off. Any accidental deletion or corruption is unrecoverable without a manual snapshot.`,
|
|
34
38
|
recommendation: `Enable automated backups on "${node.name}" with at least 7 days retention (35 days for production workloads). Enable point-in-time recovery.`,
|
|
35
|
-
metadata: {
|
|
39
|
+
metadata: {
|
|
40
|
+
dbInstanceIdentifier: node.name,
|
|
41
|
+
engine: node.engine,
|
|
42
|
+
backupRetentionDays: node.backupRetentionDays,
|
|
43
|
+
},
|
|
36
44
|
});
|
|
37
45
|
}
|
|
38
46
|
}
|
|
@@ -21,7 +21,12 @@ export class IaCDriftAnalyzer {
|
|
|
21
21
|
issue: `IaC drift: DynamoDB table "${name}" defined in IaC but not deployed`,
|
|
22
22
|
description: `"${name}" is in ${fp} but not found in AWS. It may be undeployed or deleted manually.`,
|
|
23
23
|
recommendation: 'Run `terraform apply` / deploy your stack, or remove the definition from IaC.',
|
|
24
|
-
metadata: {
|
|
24
|
+
metadata: {
|
|
25
|
+
resourceType: 'dynamodb_table',
|
|
26
|
+
name,
|
|
27
|
+
filePath: fp,
|
|
28
|
+
driftType: 'defined_not_deployed',
|
|
29
|
+
},
|
|
25
30
|
});
|
|
26
31
|
}
|
|
27
32
|
}
|
|
@@ -46,7 +51,12 @@ export class IaCDriftAnalyzer {
|
|
|
46
51
|
issue: `IaC drift: SQS queue "${name}" defined in IaC but not deployed`,
|
|
47
52
|
description: `SQS queue "${name}" is defined in ${fp} but not found in the live account.`,
|
|
48
53
|
recommendation: 'Deploy the queue via `terraform apply` or your CFN/CDK stack.',
|
|
49
|
-
metadata: {
|
|
54
|
+
metadata: {
|
|
55
|
+
resourceType: 'sqs_queue',
|
|
56
|
+
name,
|
|
57
|
+
filePath: fp,
|
|
58
|
+
driftType: 'defined_not_deployed',
|
|
59
|
+
},
|
|
50
60
|
});
|
|
51
61
|
}
|
|
52
62
|
}
|
|
@@ -71,7 +81,12 @@ export class IaCDriftAnalyzer {
|
|
|
71
81
|
issue: `IaC drift: Lambda "${name}" defined in IaC but not deployed`,
|
|
72
82
|
description: `Lambda function "${name}" is defined in ${fp} but not found in the live account.`,
|
|
73
83
|
recommendation: 'Deploy the function via `terraform apply` or your CFN/CDK stack.',
|
|
74
|
-
metadata: {
|
|
84
|
+
metadata: {
|
|
85
|
+
resourceType: 'lambda_function',
|
|
86
|
+
name,
|
|
87
|
+
filePath: fp,
|
|
88
|
+
driftType: 'defined_not_deployed',
|
|
89
|
+
},
|
|
75
90
|
});
|
|
76
91
|
}
|
|
77
92
|
}
|