@friggframework/devtools 2.0.0--canary.428.5364e8f.0 ā 2.0.0--canary.428.1c210bc.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.
|
@@ -1,22 +1,45 @@
|
|
|
1
|
-
let EC2Client,
|
|
1
|
+
let EC2Client,
|
|
2
|
+
DescribeVpcsCommand,
|
|
3
|
+
DescribeSubnetsCommand,
|
|
4
|
+
DescribeSecurityGroupsCommand,
|
|
5
|
+
DescribeRouteTablesCommand,
|
|
6
|
+
DescribeNatGatewaysCommand,
|
|
7
|
+
DescribeAddressesCommand,
|
|
8
|
+
DescribeInternetGatewaysCommand;
|
|
2
9
|
let KMSClient, ListKeysCommand, DescribeKeyCommand;
|
|
3
10
|
let STSClient, GetCallerIdentityCommand;
|
|
4
11
|
|
|
5
12
|
function loadEC2() {
|
|
6
13
|
if (!EC2Client) {
|
|
7
|
-
({
|
|
14
|
+
({
|
|
15
|
+
EC2Client,
|
|
16
|
+
DescribeVpcsCommand,
|
|
17
|
+
DescribeSubnetsCommand,
|
|
18
|
+
DescribeSecurityGroupsCommand,
|
|
19
|
+
DescribeRouteTablesCommand,
|
|
20
|
+
DescribeNatGatewaysCommand,
|
|
21
|
+
DescribeAddressesCommand,
|
|
22
|
+
DescribeInternetGatewaysCommand,
|
|
23
|
+
} = require('@aws-sdk/client-ec2'));
|
|
8
24
|
}
|
|
9
25
|
}
|
|
10
26
|
|
|
11
27
|
function loadKMS() {
|
|
12
28
|
if (!KMSClient) {
|
|
13
|
-
({
|
|
29
|
+
({
|
|
30
|
+
KMSClient,
|
|
31
|
+
ListKeysCommand,
|
|
32
|
+
DescribeKeyCommand,
|
|
33
|
+
} = require('@aws-sdk/client-kms'));
|
|
14
34
|
}
|
|
15
35
|
}
|
|
16
36
|
|
|
17
37
|
function loadSTS() {
|
|
18
38
|
if (!STSClient) {
|
|
19
|
-
({
|
|
39
|
+
({
|
|
40
|
+
STSClient,
|
|
41
|
+
GetCallerIdentityCommand,
|
|
42
|
+
} = require('@aws-sdk/client-sts'));
|
|
20
43
|
}
|
|
21
44
|
}
|
|
22
45
|
|
|
@@ -66,26 +89,26 @@ class AWSDiscovery {
|
|
|
66
89
|
Filters: [
|
|
67
90
|
{
|
|
68
91
|
Name: 'is-default',
|
|
69
|
-
Values: ['true']
|
|
70
|
-
}
|
|
71
|
-
]
|
|
92
|
+
Values: ['true'],
|
|
93
|
+
},
|
|
94
|
+
],
|
|
72
95
|
});
|
|
73
|
-
|
|
96
|
+
|
|
74
97
|
const response = await this.ec2Client.send(command);
|
|
75
|
-
|
|
98
|
+
|
|
76
99
|
if (response.Vpcs && response.Vpcs.length > 0) {
|
|
77
100
|
return response.Vpcs[0];
|
|
78
101
|
}
|
|
79
|
-
|
|
102
|
+
|
|
80
103
|
// If no default VPC, get the first available VPC
|
|
81
104
|
const allVpcsCommand = new DescribeVpcsCommand({});
|
|
82
105
|
const allVpcsResponse = await this.ec2Client.send(allVpcsCommand);
|
|
83
|
-
|
|
106
|
+
|
|
84
107
|
if (allVpcsResponse.Vpcs && allVpcsResponse.Vpcs.length > 0) {
|
|
85
108
|
console.log('No default VPC found, using first available VPC');
|
|
86
109
|
return allVpcsResponse.Vpcs[0];
|
|
87
110
|
}
|
|
88
|
-
|
|
111
|
+
|
|
89
112
|
throw new Error('No VPC found in the account');
|
|
90
113
|
} catch (error) {
|
|
91
114
|
console.error('Error finding default VPC:', error.message);
|
|
@@ -106,9 +129,9 @@ class AWSDiscovery {
|
|
|
106
129
|
Filters: [
|
|
107
130
|
{
|
|
108
131
|
Name: 'vpc-id',
|
|
109
|
-
Values: [vpcId]
|
|
110
|
-
}
|
|
111
|
-
]
|
|
132
|
+
Values: [vpcId],
|
|
133
|
+
},
|
|
134
|
+
],
|
|
112
135
|
});
|
|
113
136
|
|
|
114
137
|
const response = await this.ec2Client.send(command);
|
|
@@ -117,7 +140,9 @@ class AWSDiscovery {
|
|
|
117
140
|
throw new Error(`No subnets found in VPC ${vpcId}`);
|
|
118
141
|
}
|
|
119
142
|
|
|
120
|
-
console.log(
|
|
143
|
+
console.log(
|
|
144
|
+
`\nš Analyzing ${response.Subnets.length} subnets in VPC ${vpcId}...`
|
|
145
|
+
);
|
|
121
146
|
|
|
122
147
|
// Categorize subnets by their actual routing
|
|
123
148
|
const privateSubnets = [];
|
|
@@ -128,10 +153,14 @@ class AWSDiscovery {
|
|
|
128
153
|
const isPrivate = await this.isSubnetPrivate(subnet.SubnetId);
|
|
129
154
|
if (isPrivate) {
|
|
130
155
|
privateSubnets.push(subnet);
|
|
131
|
-
console.log(
|
|
156
|
+
console.log(
|
|
157
|
+
` š Private subnet: ${subnet.SubnetId} (AZ: ${subnet.AvailabilityZone})`
|
|
158
|
+
);
|
|
132
159
|
} else {
|
|
133
160
|
publicSubnets.push(subnet);
|
|
134
|
-
console.log(
|
|
161
|
+
console.log(
|
|
162
|
+
` š Public subnet: ${subnet.SubnetId} (AZ: ${subnet.AvailabilityZone})`
|
|
163
|
+
);
|
|
135
164
|
}
|
|
136
165
|
}
|
|
137
166
|
|
|
@@ -141,15 +170,21 @@ class AWSDiscovery {
|
|
|
141
170
|
|
|
142
171
|
// If we have at least 2 private subnets, use them
|
|
143
172
|
if (privateSubnets.length >= 2) {
|
|
144
|
-
console.log(
|
|
173
|
+
console.log(
|
|
174
|
+
`ā
Found ${privateSubnets.length} private subnets for Lambda deployment`
|
|
175
|
+
);
|
|
145
176
|
return privateSubnets.slice(0, 2);
|
|
146
177
|
}
|
|
147
178
|
|
|
148
179
|
// If we have 1 private subnet, we need at least one more
|
|
149
180
|
if (privateSubnets.length === 1) {
|
|
150
|
-
console.warn(
|
|
181
|
+
console.warn(
|
|
182
|
+
`ā ļø Only 1 private subnet found. Need at least 2 for high availability.`
|
|
183
|
+
);
|
|
151
184
|
if (publicSubnets.length > 0 && autoConvert) {
|
|
152
|
-
console.log(
|
|
185
|
+
console.log(
|
|
186
|
+
`š Will convert 1 public subnet to private for high availability...`
|
|
187
|
+
);
|
|
153
188
|
// Note: The actual conversion happens in the serverless template
|
|
154
189
|
}
|
|
155
190
|
// Return what we have - mix of private and public if needed
|
|
@@ -158,38 +193,62 @@ class AWSDiscovery {
|
|
|
158
193
|
|
|
159
194
|
// No private subnets found at all - this is a problem!
|
|
160
195
|
if (privateSubnets.length === 0 && publicSubnets.length > 0) {
|
|
161
|
-
console.error(
|
|
162
|
-
|
|
196
|
+
console.error(
|
|
197
|
+
`ā CRITICAL: No private subnets found, but ${publicSubnets.length} public subnets exist`
|
|
198
|
+
);
|
|
199
|
+
console.error(
|
|
200
|
+
`ā Lambda functions should NOT be deployed in public subnets!`
|
|
201
|
+
);
|
|
163
202
|
|
|
164
203
|
if (autoConvert && publicSubnets.length >= 3) {
|
|
165
|
-
console.log(
|
|
166
|
-
|
|
167
|
-
|
|
204
|
+
console.log(
|
|
205
|
+
`\nš§ AUTO-CONVERSION: Will configure subnets for proper isolation...`
|
|
206
|
+
);
|
|
207
|
+
console.log(
|
|
208
|
+
` - Keeping ${publicSubnets[0].SubnetId} as public (for NAT Gateway)`
|
|
209
|
+
);
|
|
210
|
+
console.log(
|
|
211
|
+
` - Converting ${publicSubnets[1].SubnetId} to private (for Lambda)`
|
|
212
|
+
);
|
|
168
213
|
if (publicSubnets[2]) {
|
|
169
|
-
console.log(
|
|
214
|
+
console.log(
|
|
215
|
+
` - Converting ${publicSubnets[2].SubnetId} to private (for Lambda)`
|
|
216
|
+
);
|
|
170
217
|
}
|
|
171
218
|
|
|
172
219
|
// Return subnets that SHOULD be private (indexes 1 and 2)
|
|
173
220
|
// The actual conversion happens in the serverless template
|
|
174
221
|
return publicSubnets.slice(1, 3);
|
|
175
222
|
} else if (autoConvert && publicSubnets.length >= 2) {
|
|
176
|
-
console.log(
|
|
177
|
-
|
|
223
|
+
console.log(
|
|
224
|
+
`\nš§ AUTO-CONVERSION: Only ${publicSubnets.length} subnets available`
|
|
225
|
+
);
|
|
226
|
+
console.log(
|
|
227
|
+
` - Will need to create new subnets or reconfigure existing ones`
|
|
228
|
+
);
|
|
178
229
|
// Return what we have but flag for conversion
|
|
179
230
|
return publicSubnets.slice(0, 2);
|
|
180
231
|
} else {
|
|
181
232
|
console.error(`\nā ļø CONFIGURATION ERROR:`);
|
|
182
|
-
console.error(
|
|
183
|
-
|
|
233
|
+
console.error(
|
|
234
|
+
` Found ${publicSubnets.length} public subnets but no private subnets.`
|
|
235
|
+
);
|
|
236
|
+
console.error(
|
|
237
|
+
` Lambda functions require private subnets for security.`
|
|
238
|
+
);
|
|
184
239
|
console.error(`\n Options:`);
|
|
185
|
-
console.error(
|
|
240
|
+
console.error(
|
|
241
|
+
` 1. Enable selfHeal: true in vpc configuration`
|
|
242
|
+
);
|
|
186
243
|
console.error(` 2. Create private subnets manually`);
|
|
187
|
-
console.error(
|
|
244
|
+
console.error(
|
|
245
|
+
` 3. Set subnets.management: 'create' to create new private subnets`
|
|
246
|
+
);
|
|
188
247
|
|
|
189
248
|
throw new Error(
|
|
190
249
|
`No private subnets found in VPC ${vpcId}. ` +
|
|
191
|
-
|
|
192
|
-
|
|
250
|
+
`Found ${publicSubnets.length} public subnets. ` +
|
|
251
|
+
`Lambda requires private subnets. Enable selfHeal or create private subnets.`
|
|
193
252
|
);
|
|
194
253
|
}
|
|
195
254
|
}
|
|
@@ -221,32 +280,37 @@ class AWSDiscovery {
|
|
|
221
280
|
try {
|
|
222
281
|
// First, get the subnet details to find its VPC
|
|
223
282
|
const subnetCommand = new DescribeSubnetsCommand({
|
|
224
|
-
SubnetIds: [subnetId]
|
|
283
|
+
SubnetIds: [subnetId],
|
|
225
284
|
});
|
|
226
285
|
const subnetResponse = await this.ec2Client.send(subnetCommand);
|
|
227
|
-
|
|
228
|
-
if (
|
|
286
|
+
|
|
287
|
+
if (
|
|
288
|
+
!subnetResponse.Subnets ||
|
|
289
|
+
subnetResponse.Subnets.length === 0
|
|
290
|
+
) {
|
|
229
291
|
throw new Error(`Subnet ${subnetId} not found`);
|
|
230
292
|
}
|
|
231
|
-
|
|
293
|
+
|
|
232
294
|
const subnet = subnetResponse.Subnets[0];
|
|
233
295
|
const vpcId = subnet.VpcId;
|
|
234
|
-
|
|
296
|
+
|
|
235
297
|
// Get all route tables for this VPC
|
|
236
298
|
const routeTablesCommand = new DescribeRouteTablesCommand({
|
|
237
299
|
Filters: [
|
|
238
300
|
{
|
|
239
301
|
Name: 'vpc-id',
|
|
240
|
-
Values: [vpcId]
|
|
241
|
-
}
|
|
242
|
-
]
|
|
302
|
+
Values: [vpcId],
|
|
303
|
+
},
|
|
304
|
+
],
|
|
243
305
|
});
|
|
244
|
-
|
|
245
|
-
const routeTablesResponse = await this.ec2Client.send(
|
|
246
|
-
|
|
306
|
+
|
|
307
|
+
const routeTablesResponse = await this.ec2Client.send(
|
|
308
|
+
routeTablesCommand
|
|
309
|
+
);
|
|
310
|
+
|
|
247
311
|
// Find the route table for this subnet
|
|
248
312
|
let routeTable = null;
|
|
249
|
-
|
|
313
|
+
|
|
250
314
|
// First check for explicit association
|
|
251
315
|
for (const rt of routeTablesResponse.RouteTables || []) {
|
|
252
316
|
for (const assoc of rt.Associations || []) {
|
|
@@ -257,7 +321,7 @@ class AWSDiscovery {
|
|
|
257
321
|
}
|
|
258
322
|
if (routeTable) break;
|
|
259
323
|
}
|
|
260
|
-
|
|
324
|
+
|
|
261
325
|
// If no explicit association, use the main route table
|
|
262
326
|
if (!routeTable) {
|
|
263
327
|
for (const rt of routeTablesResponse.RouteTables || []) {
|
|
@@ -270,12 +334,12 @@ class AWSDiscovery {
|
|
|
270
334
|
if (routeTable) break;
|
|
271
335
|
}
|
|
272
336
|
}
|
|
273
|
-
|
|
337
|
+
|
|
274
338
|
if (!routeTable) {
|
|
275
339
|
console.warn(`No route table found for subnet ${subnetId}`);
|
|
276
340
|
return true; // Default to private for safety
|
|
277
341
|
}
|
|
278
|
-
|
|
342
|
+
|
|
279
343
|
// Check if route table has a route to an Internet Gateway
|
|
280
344
|
let hasIgwRoute = false;
|
|
281
345
|
let gatewayId = null;
|
|
@@ -290,14 +354,21 @@ class AWSDiscovery {
|
|
|
290
354
|
|
|
291
355
|
// Enhanced logging for validation
|
|
292
356
|
if (hasIgwRoute) {
|
|
293
|
-
console.log(
|
|
357
|
+
console.log(
|
|
358
|
+
`ā
Subnet ${subnetId} is PUBLIC (has route to IGW ${gatewayId})`
|
|
359
|
+
);
|
|
294
360
|
return false; // It's a public subnet
|
|
295
361
|
} else {
|
|
296
|
-
console.log(
|
|
362
|
+
console.log(
|
|
363
|
+
`š Subnet ${subnetId} is PRIVATE (no IGW route found)`
|
|
364
|
+
);
|
|
297
365
|
return true; // No IGW route found, it's private
|
|
298
366
|
}
|
|
299
367
|
} catch (error) {
|
|
300
|
-
console.warn(
|
|
368
|
+
console.warn(
|
|
369
|
+
`Could not determine if subnet ${subnetId} is private:`,
|
|
370
|
+
error
|
|
371
|
+
);
|
|
301
372
|
return true; // Default to private for safety
|
|
302
373
|
}
|
|
303
374
|
}
|
|
@@ -315,17 +386,20 @@ class AWSDiscovery {
|
|
|
315
386
|
Filters: [
|
|
316
387
|
{
|
|
317
388
|
Name: 'vpc-id',
|
|
318
|
-
Values: [vpcId]
|
|
389
|
+
Values: [vpcId],
|
|
319
390
|
},
|
|
320
391
|
{
|
|
321
392
|
Name: 'group-name',
|
|
322
|
-
Values: ['frigg-lambda-sg']
|
|
323
|
-
}
|
|
324
|
-
]
|
|
393
|
+
Values: ['frigg-lambda-sg'],
|
|
394
|
+
},
|
|
395
|
+
],
|
|
325
396
|
});
|
|
326
|
-
|
|
397
|
+
|
|
327
398
|
const friggResponse = await this.ec2Client.send(friggSgCommand);
|
|
328
|
-
if (
|
|
399
|
+
if (
|
|
400
|
+
friggResponse.SecurityGroups &&
|
|
401
|
+
friggResponse.SecurityGroups.length > 0
|
|
402
|
+
) {
|
|
329
403
|
return friggResponse.SecurityGroups[0];
|
|
330
404
|
}
|
|
331
405
|
|
|
@@ -334,20 +408,23 @@ class AWSDiscovery {
|
|
|
334
408
|
Filters: [
|
|
335
409
|
{
|
|
336
410
|
Name: 'vpc-id',
|
|
337
|
-
Values: [vpcId]
|
|
411
|
+
Values: [vpcId],
|
|
338
412
|
},
|
|
339
413
|
{
|
|
340
414
|
Name: 'group-name',
|
|
341
|
-
Values: ['default']
|
|
342
|
-
}
|
|
343
|
-
]
|
|
415
|
+
Values: ['default'],
|
|
416
|
+
},
|
|
417
|
+
],
|
|
344
418
|
});
|
|
345
|
-
|
|
419
|
+
|
|
346
420
|
const defaultResponse = await this.ec2Client.send(defaultSgCommand);
|
|
347
|
-
if (
|
|
421
|
+
if (
|
|
422
|
+
defaultResponse.SecurityGroups &&
|
|
423
|
+
defaultResponse.SecurityGroups.length > 0
|
|
424
|
+
) {
|
|
348
425
|
return defaultResponse.SecurityGroups[0];
|
|
349
426
|
}
|
|
350
|
-
|
|
427
|
+
|
|
351
428
|
throw new Error(`No security group found for VPC ${vpcId}`);
|
|
352
429
|
} catch (error) {
|
|
353
430
|
console.error('Error finding default security group:', error);
|
|
@@ -367,9 +444,9 @@ class AWSDiscovery {
|
|
|
367
444
|
Filters: [
|
|
368
445
|
{
|
|
369
446
|
Name: 'vpc-id',
|
|
370
|
-
Values: [vpcId]
|
|
371
|
-
}
|
|
372
|
-
]
|
|
447
|
+
Values: [vpcId],
|
|
448
|
+
},
|
|
449
|
+
],
|
|
373
450
|
});
|
|
374
451
|
|
|
375
452
|
const response = await this.ec2Client.send(command);
|
|
@@ -391,14 +468,22 @@ class AWSDiscovery {
|
|
|
391
468
|
|
|
392
469
|
if (publicSubnets.length === 0) {
|
|
393
470
|
// If no public subnets found, we need to create one or inform the user
|
|
394
|
-
console.warn(
|
|
395
|
-
|
|
396
|
-
|
|
471
|
+
console.warn(
|
|
472
|
+
`WARNING: No public subnets found in VPC ${vpcId}`
|
|
473
|
+
);
|
|
474
|
+
console.warn(
|
|
475
|
+
'A public subnet with Internet Gateway route is required for NAT Gateway placement'
|
|
476
|
+
);
|
|
477
|
+
console.warn(
|
|
478
|
+
'Please create a public subnet or use VPC endpoints instead'
|
|
479
|
+
);
|
|
397
480
|
return null; // Return null instead of throwing to allow graceful handling
|
|
398
481
|
}
|
|
399
482
|
|
|
400
483
|
// Return first public subnet for NAT Gateway
|
|
401
|
-
console.log(
|
|
484
|
+
console.log(
|
|
485
|
+
`Found ${publicSubnets.length} public subnets, using ${publicSubnets[0].SubnetId} for NAT Gateway`
|
|
486
|
+
);
|
|
402
487
|
return publicSubnets[0];
|
|
403
488
|
} catch (error) {
|
|
404
489
|
console.error('Error finding public subnets:', error);
|
|
@@ -418,13 +503,13 @@ class AWSDiscovery {
|
|
|
418
503
|
Filters: [
|
|
419
504
|
{
|
|
420
505
|
Name: 'vpc-id',
|
|
421
|
-
Values: [vpcId]
|
|
422
|
-
}
|
|
423
|
-
]
|
|
506
|
+
Values: [vpcId],
|
|
507
|
+
},
|
|
508
|
+
],
|
|
424
509
|
});
|
|
425
|
-
|
|
510
|
+
|
|
426
511
|
const response = await this.ec2Client.send(command);
|
|
427
|
-
|
|
512
|
+
|
|
428
513
|
if (!response.RouteTables || response.RouteTables.length === 0) {
|
|
429
514
|
throw new Error(`No route tables found for VPC ${vpcId}`);
|
|
430
515
|
}
|
|
@@ -462,13 +547,13 @@ class AWSDiscovery {
|
|
|
462
547
|
Filter: [
|
|
463
548
|
{
|
|
464
549
|
Name: 'vpc-id',
|
|
465
|
-
Values: [vpcId]
|
|
550
|
+
Values: [vpcId],
|
|
466
551
|
},
|
|
467
552
|
{
|
|
468
553
|
Name: 'state',
|
|
469
|
-
Values: ['available']
|
|
470
|
-
}
|
|
471
|
-
]
|
|
554
|
+
Values: ['available'],
|
|
555
|
+
},
|
|
556
|
+
],
|
|
472
557
|
});
|
|
473
558
|
|
|
474
559
|
const response = await this.ec2Client.send(command);
|
|
@@ -476,14 +561,24 @@ class AWSDiscovery {
|
|
|
476
561
|
if (response.NatGateways && response.NatGateways.length > 0) {
|
|
477
562
|
// Sort NAT Gateways to prioritize Frigg-managed ones
|
|
478
563
|
const sortedNatGateways = response.NatGateways.sort((a, b) => {
|
|
479
|
-
const aIsFrigg =
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
564
|
+
const aIsFrigg =
|
|
565
|
+
a.Tags &&
|
|
566
|
+
a.Tags.some(
|
|
567
|
+
(tag) =>
|
|
568
|
+
(tag.Key === 'ManagedBy' &&
|
|
569
|
+
tag.Value === 'Frigg') ||
|
|
570
|
+
(tag.Key === 'Name' &&
|
|
571
|
+
tag.Value.includes('frigg'))
|
|
572
|
+
);
|
|
573
|
+
const bIsFrigg =
|
|
574
|
+
b.Tags &&
|
|
575
|
+
b.Tags.some(
|
|
576
|
+
(tag) =>
|
|
577
|
+
(tag.Key === 'ManagedBy' &&
|
|
578
|
+
tag.Value === 'Frigg') ||
|
|
579
|
+
(tag.Key === 'Name' &&
|
|
580
|
+
tag.Value.includes('frigg'))
|
|
581
|
+
);
|
|
487
582
|
|
|
488
583
|
if (aIsFrigg && !bIsFrigg) return -1;
|
|
489
584
|
if (!aIsFrigg && bIsFrigg) return 1;
|
|
@@ -496,46 +591,71 @@ class AWSDiscovery {
|
|
|
496
591
|
const isPrivate = await this.isSubnetPrivate(subnetId);
|
|
497
592
|
|
|
498
593
|
// Check if it's a Frigg-managed NAT Gateway
|
|
499
|
-
const isFriggNat =
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
594
|
+
const isFriggNat =
|
|
595
|
+
natGateway.Tags &&
|
|
596
|
+
natGateway.Tags.some(
|
|
597
|
+
(tag) =>
|
|
598
|
+
(tag.Key === 'ManagedBy' &&
|
|
599
|
+
tag.Value === 'Frigg') ||
|
|
600
|
+
(tag.Key === 'Name' &&
|
|
601
|
+
tag.Value.includes('frigg'))
|
|
602
|
+
);
|
|
503
603
|
|
|
504
604
|
if (isPrivate) {
|
|
505
605
|
// NAT Gateway appears to be in a private subnet
|
|
506
606
|
// This could be due to route table misconfiguration
|
|
507
|
-
console.warn(
|
|
607
|
+
console.warn(
|
|
608
|
+
`WARNING: NAT Gateway ${natGateway.NatGatewayId} is in subnet ${subnetId} which appears to be private`
|
|
609
|
+
);
|
|
508
610
|
|
|
509
611
|
if (isFriggNat) {
|
|
510
|
-
console.warn(
|
|
511
|
-
|
|
612
|
+
console.warn(
|
|
613
|
+
'This is a Frigg-managed NAT Gateway that may have been misconfigured by route table changes'
|
|
614
|
+
);
|
|
615
|
+
console.warn(
|
|
616
|
+
'Consider enabling selfHeal: true to fix this automatically'
|
|
617
|
+
);
|
|
512
618
|
// Return it anyway if it's Frigg-managed - we can fix the routes
|
|
513
619
|
// Mark that it's in a private subnet
|
|
514
620
|
natGateway._isInPrivateSubnet = true;
|
|
515
621
|
return natGateway;
|
|
516
622
|
} else {
|
|
517
|
-
console.warn(
|
|
518
|
-
|
|
623
|
+
console.warn(
|
|
624
|
+
'NAT Gateways MUST be placed in public subnets with Internet Gateway routes'
|
|
625
|
+
);
|
|
626
|
+
console.warn(
|
|
627
|
+
'Skipping this misconfigured NAT Gateway...'
|
|
628
|
+
);
|
|
519
629
|
continue; // Skip non-Frigg NAT Gateways in private subnets
|
|
520
630
|
}
|
|
521
631
|
}
|
|
522
632
|
|
|
523
633
|
if (isFriggNat) {
|
|
524
|
-
console.log(
|
|
634
|
+
console.log(
|
|
635
|
+
`Found existing Frigg-managed NAT Gateway: ${natGateway.NatGatewayId}`
|
|
636
|
+
);
|
|
525
637
|
natGateway._isInPrivateSubnet = false;
|
|
526
638
|
return natGateway;
|
|
527
639
|
}
|
|
528
640
|
|
|
529
641
|
// Return first valid NAT Gateway that's in a public subnet
|
|
530
|
-
console.log(
|
|
642
|
+
console.log(
|
|
643
|
+
`Found existing NAT Gateway in public subnet: ${natGateway.NatGatewayId}`
|
|
644
|
+
);
|
|
531
645
|
natGateway._isInPrivateSubnet = false;
|
|
532
646
|
return natGateway;
|
|
533
647
|
}
|
|
534
648
|
|
|
535
649
|
// All non-Frigg NAT Gateways are in private subnets
|
|
536
|
-
console.error(
|
|
537
|
-
|
|
538
|
-
|
|
650
|
+
console.error(
|
|
651
|
+
`ERROR: Found ${response.NatGateways.length} NAT Gateway(s) but all non-Frigg ones are in private subnets!`
|
|
652
|
+
);
|
|
653
|
+
console.error(
|
|
654
|
+
'These NAT Gateways will not provide internet connectivity without route table fixes'
|
|
655
|
+
);
|
|
656
|
+
console.error(
|
|
657
|
+
'Enable selfHeal: true to fix automatically or create a new NAT Gateway'
|
|
658
|
+
);
|
|
539
659
|
return null; // Return null to trigger creation of new NAT Gateway
|
|
540
660
|
}
|
|
541
661
|
|
|
@@ -554,31 +674,42 @@ class AWSDiscovery {
|
|
|
554
674
|
try {
|
|
555
675
|
const command = new DescribeAddressesCommand({});
|
|
556
676
|
const response = await this.ec2Client.send(command);
|
|
557
|
-
|
|
677
|
+
|
|
558
678
|
if (response.Addresses && response.Addresses.length > 0) {
|
|
559
679
|
// Find an unassociated EIP first
|
|
560
|
-
const availableEIP = response.Addresses.find(
|
|
561
|
-
|
|
680
|
+
const availableEIP = response.Addresses.find(
|
|
681
|
+
(eip) =>
|
|
682
|
+
!eip.AssociationId &&
|
|
683
|
+
!eip.InstanceId &&
|
|
684
|
+
!eip.NetworkInterfaceId
|
|
562
685
|
);
|
|
563
|
-
|
|
686
|
+
|
|
564
687
|
if (availableEIP) {
|
|
565
|
-
console.log(
|
|
688
|
+
console.log(
|
|
689
|
+
`Found available Elastic IP: ${availableEIP.AllocationId}`
|
|
690
|
+
);
|
|
566
691
|
return availableEIP;
|
|
567
692
|
}
|
|
568
|
-
|
|
693
|
+
|
|
569
694
|
// Check for EIPs tagged for Frigg
|
|
570
|
-
const friggEIP = response.Addresses.find(
|
|
571
|
-
eip
|
|
572
|
-
|
|
573
|
-
|
|
695
|
+
const friggEIP = response.Addresses.find(
|
|
696
|
+
(eip) =>
|
|
697
|
+
eip.Tags &&
|
|
698
|
+
eip.Tags.some(
|
|
699
|
+
(tag) =>
|
|
700
|
+
tag.Key === 'Name' &&
|
|
701
|
+
tag.Value.includes('frigg')
|
|
702
|
+
)
|
|
574
703
|
);
|
|
575
|
-
|
|
704
|
+
|
|
576
705
|
if (friggEIP) {
|
|
577
|
-
console.log(
|
|
706
|
+
console.log(
|
|
707
|
+
`Found Frigg-tagged Elastic IP: ${friggEIP.AllocationId}`
|
|
708
|
+
);
|
|
578
709
|
return friggEIP;
|
|
579
710
|
}
|
|
580
711
|
}
|
|
581
|
-
|
|
712
|
+
|
|
582
713
|
return null;
|
|
583
714
|
} catch (error) {
|
|
584
715
|
console.warn('Error finding available Elastic IP:', error.message);
|
|
@@ -591,6 +722,7 @@ class AWSDiscovery {
|
|
|
591
722
|
* @returns {Promise<string|null>} KMS key ARN or null if no key found
|
|
592
723
|
*/
|
|
593
724
|
async findDefaultKmsKey() {
|
|
725
|
+
console.log('KMS Discovery Starting...');
|
|
594
726
|
try {
|
|
595
727
|
// Log AWS account and region info for verification
|
|
596
728
|
console.log(`[KMS Discovery] Running in region: ${this.region}`);
|
|
@@ -598,7 +730,10 @@ class AWSDiscovery {
|
|
|
598
730
|
const accountId = await this.getAccountId();
|
|
599
731
|
console.log(`[KMS Discovery] AWS Account ID: ${accountId}`);
|
|
600
732
|
} catch (error) {
|
|
601
|
-
console.warn(
|
|
733
|
+
console.warn(
|
|
734
|
+
'[KMS Discovery] Could not retrieve account ID:',
|
|
735
|
+
error.message
|
|
736
|
+
);
|
|
602
737
|
}
|
|
603
738
|
|
|
604
739
|
const command = new ListKeysCommand({});
|
|
@@ -609,7 +744,9 @@ class AWSDiscovery {
|
|
|
609
744
|
return null;
|
|
610
745
|
}
|
|
611
746
|
|
|
612
|
-
console.log(
|
|
747
|
+
console.log(
|
|
748
|
+
`[KMS Discovery] Found ${response.Keys.length} total keys in account`
|
|
749
|
+
);
|
|
613
750
|
let keysExamined = 0;
|
|
614
751
|
let customerManagedKeys = 0;
|
|
615
752
|
let enabledKeys = 0;
|
|
@@ -618,8 +755,12 @@ class AWSDiscovery {
|
|
|
618
755
|
// Look for customer managed keys first
|
|
619
756
|
for (const key of response.Keys) {
|
|
620
757
|
try {
|
|
621
|
-
const describeCommand = new DescribeKeyCommand({
|
|
622
|
-
|
|
758
|
+
const describeCommand = new DescribeKeyCommand({
|
|
759
|
+
KeyId: key.KeyId,
|
|
760
|
+
});
|
|
761
|
+
const keyDetails = await this.kmsClient.send(
|
|
762
|
+
describeCommand
|
|
763
|
+
);
|
|
623
764
|
keysExamined++;
|
|
624
765
|
|
|
625
766
|
if (keyDetails.KeyMetadata) {
|
|
@@ -630,8 +771,10 @@ class AWSDiscovery {
|
|
|
630
771
|
KeyManager: metadata.KeyManager,
|
|
631
772
|
KeyState: metadata.KeyState,
|
|
632
773
|
Enabled: metadata.Enabled,
|
|
633
|
-
DeletionDate:
|
|
634
|
-
|
|
774
|
+
DeletionDate:
|
|
775
|
+
metadata.DeletionDate ||
|
|
776
|
+
'Not scheduled for deletion',
|
|
777
|
+
Arn: metadata.Arn,
|
|
635
778
|
});
|
|
636
779
|
|
|
637
780
|
if (metadata.KeyManager === 'CUSTOMER') {
|
|
@@ -639,28 +782,43 @@ class AWSDiscovery {
|
|
|
639
782
|
|
|
640
783
|
if (metadata.KeyState === 'Enabled') {
|
|
641
784
|
enabledKeys++;
|
|
642
|
-
} else if (
|
|
785
|
+
} else if (
|
|
786
|
+
metadata.KeyState === 'PendingDeletion'
|
|
787
|
+
) {
|
|
643
788
|
pendingDeletionKeys++;
|
|
644
|
-
console.warn(
|
|
789
|
+
console.warn(
|
|
790
|
+
`[KMS Discovery] Skipping key ${key.KeyId} - State: PendingDeletion, DeletionDate: ${metadata.DeletionDate}`
|
|
791
|
+
);
|
|
645
792
|
}
|
|
646
793
|
|
|
647
794
|
// Explicitly check for enabled state AND absence of deletion
|
|
648
|
-
if (
|
|
795
|
+
if (
|
|
796
|
+
metadata.KeyManager === 'CUSTOMER' &&
|
|
649
797
|
metadata.KeyState === 'Enabled' &&
|
|
650
|
-
!metadata.DeletionDate
|
|
651
|
-
|
|
798
|
+
!metadata.DeletionDate
|
|
799
|
+
) {
|
|
800
|
+
console.log(
|
|
801
|
+
`[KMS Discovery] Found eligible customer managed KMS key: ${metadata.Arn}`
|
|
802
|
+
);
|
|
652
803
|
return metadata.Arn;
|
|
653
|
-
} else if (
|
|
654
|
-
|
|
655
|
-
|
|
804
|
+
} else if (
|
|
805
|
+
metadata.KeyManager === 'CUSTOMER' &&
|
|
806
|
+
metadata.KeyState === 'Enabled' &&
|
|
807
|
+
metadata.DeletionDate
|
|
808
|
+
) {
|
|
656
809
|
// This shouldn't happen according to AWS docs, but log it if it does
|
|
657
|
-
console.error(
|
|
810
|
+
console.error(
|
|
811
|
+
`[KMS Discovery] WARNING: Key ${key.KeyId} has KeyState='Enabled' but DeletionDate is set: ${metadata.DeletionDate}`
|
|
812
|
+
);
|
|
658
813
|
}
|
|
659
814
|
}
|
|
660
815
|
}
|
|
661
816
|
} catch (error) {
|
|
662
817
|
// Continue to next key if we can't describe this one
|
|
663
|
-
console.warn(
|
|
818
|
+
console.warn(
|
|
819
|
+
`[KMS Discovery] Could not describe key ${key.KeyId}:`,
|
|
820
|
+
error.message
|
|
821
|
+
);
|
|
664
822
|
continue;
|
|
665
823
|
}
|
|
666
824
|
}
|
|
@@ -671,20 +829,29 @@ class AWSDiscovery {
|
|
|
671
829
|
keysExamined: keysExamined,
|
|
672
830
|
customerManagedKeys: customerManagedKeys,
|
|
673
831
|
enabledKeys: enabledKeys,
|
|
674
|
-
pendingDeletionKeys: pendingDeletionKeys
|
|
832
|
+
pendingDeletionKeys: pendingDeletionKeys,
|
|
675
833
|
});
|
|
676
834
|
|
|
677
835
|
if (customerManagedKeys === 0) {
|
|
678
|
-
console.log(
|
|
836
|
+
console.log(
|
|
837
|
+
'[KMS Discovery] No customer managed KMS keys found in account'
|
|
838
|
+
);
|
|
679
839
|
} else if (enabledKeys === 0) {
|
|
680
|
-
console.warn(
|
|
840
|
+
console.warn(
|
|
841
|
+
'[KMS Discovery] Found customer managed keys but none are in Enabled state'
|
|
842
|
+
);
|
|
681
843
|
} else {
|
|
682
|
-
console.warn(
|
|
844
|
+
console.warn(
|
|
845
|
+
'[KMS Discovery] Found enabled customer managed keys but none met all criteria'
|
|
846
|
+
);
|
|
683
847
|
}
|
|
684
848
|
|
|
685
849
|
return null;
|
|
686
850
|
} catch (error) {
|
|
687
|
-
console.error(
|
|
851
|
+
console.error(
|
|
852
|
+
'[KMS Discovery] Error finding default KMS key:',
|
|
853
|
+
error
|
|
854
|
+
);
|
|
688
855
|
return null;
|
|
689
856
|
}
|
|
690
857
|
}
|
|
@@ -700,23 +867,26 @@ class AWSDiscovery {
|
|
|
700
867
|
natGateways: [],
|
|
701
868
|
elasticIps: [],
|
|
702
869
|
subnets: [],
|
|
703
|
-
routeTables: []
|
|
870
|
+
routeTables: [],
|
|
704
871
|
};
|
|
705
872
|
|
|
706
873
|
// Find NAT Gateways with Frigg tags
|
|
707
874
|
const natCommand = new DescribeNatGatewaysCommand({
|
|
708
875
|
Filter: [
|
|
709
876
|
{ Name: 'vpc-id', Values: [vpcId] },
|
|
710
|
-
{ Name: 'state', Values: ['available'] }
|
|
711
|
-
]
|
|
877
|
+
{ Name: 'state', Values: ['available'] },
|
|
878
|
+
],
|
|
712
879
|
});
|
|
713
880
|
const natResponse = await this.ec2Client.send(natCommand);
|
|
714
881
|
|
|
715
882
|
if (natResponse.NatGateways) {
|
|
716
|
-
resources.natGateways = natResponse.NatGateways.filter(
|
|
717
|
-
nat
|
|
718
|
-
|
|
719
|
-
|
|
883
|
+
resources.natGateways = natResponse.NatGateways.filter(
|
|
884
|
+
(nat) =>
|
|
885
|
+
nat.Tags &&
|
|
886
|
+
nat.Tags.some(
|
|
887
|
+
(tag) =>
|
|
888
|
+
tag.Key === 'ManagedBy' && tag.Value === 'Frigg'
|
|
889
|
+
)
|
|
720
890
|
);
|
|
721
891
|
}
|
|
722
892
|
|
|
@@ -725,26 +895,30 @@ class AWSDiscovery {
|
|
|
725
895
|
const eipResponse = await this.ec2Client.send(eipCommand);
|
|
726
896
|
|
|
727
897
|
if (eipResponse.Addresses) {
|
|
728
|
-
resources.elasticIps = eipResponse.Addresses.filter(
|
|
729
|
-
eip
|
|
730
|
-
|
|
731
|
-
|
|
898
|
+
resources.elasticIps = eipResponse.Addresses.filter(
|
|
899
|
+
(eip) =>
|
|
900
|
+
eip.Tags &&
|
|
901
|
+
eip.Tags.some(
|
|
902
|
+
(tag) =>
|
|
903
|
+
tag.Key === 'ManagedBy' && tag.Value === 'Frigg'
|
|
904
|
+
)
|
|
732
905
|
);
|
|
733
906
|
}
|
|
734
907
|
|
|
735
908
|
// Find Route Tables with Frigg tags
|
|
736
909
|
const rtCommand = new DescribeRouteTablesCommand({
|
|
737
|
-
Filters: [
|
|
738
|
-
{ Name: 'vpc-id', Values: [vpcId] }
|
|
739
|
-
]
|
|
910
|
+
Filters: [{ Name: 'vpc-id', Values: [vpcId] }],
|
|
740
911
|
});
|
|
741
912
|
const rtResponse = await this.ec2Client.send(rtCommand);
|
|
742
913
|
|
|
743
914
|
if (rtResponse.RouteTables) {
|
|
744
|
-
resources.routeTables = rtResponse.RouteTables.filter(
|
|
745
|
-
rt
|
|
746
|
-
|
|
747
|
-
|
|
915
|
+
resources.routeTables = rtResponse.RouteTables.filter(
|
|
916
|
+
(rt) =>
|
|
917
|
+
rt.Tags &&
|
|
918
|
+
rt.Tags.some(
|
|
919
|
+
(tag) =>
|
|
920
|
+
tag.Key === 'ManagedBy' && tag.Value === 'Frigg'
|
|
921
|
+
)
|
|
748
922
|
);
|
|
749
923
|
}
|
|
750
924
|
|
|
@@ -755,7 +929,7 @@ class AWSDiscovery {
|
|
|
755
929
|
natGateways: [],
|
|
756
930
|
elasticIps: [],
|
|
757
931
|
subnets: [],
|
|
758
|
-
routeTables: []
|
|
932
|
+
routeTables: [],
|
|
759
933
|
};
|
|
760
934
|
}
|
|
761
935
|
}
|
|
@@ -771,15 +945,15 @@ class AWSDiscovery {
|
|
|
771
945
|
natGatewaysInPrivateSubnets: [],
|
|
772
946
|
orphanedElasticIps: [],
|
|
773
947
|
misconfiguredRouteTables: [],
|
|
774
|
-
privateSubnetsWithoutNatRoute: []
|
|
948
|
+
privateSubnetsWithoutNatRoute: [],
|
|
775
949
|
};
|
|
776
950
|
|
|
777
951
|
// Find NAT Gateways in private subnets
|
|
778
952
|
const natCommand = new DescribeNatGatewaysCommand({
|
|
779
953
|
Filter: [
|
|
780
954
|
{ Name: 'vpc-id', Values: [vpcId] },
|
|
781
|
-
{ Name: 'state', Values: ['available'] }
|
|
782
|
-
]
|
|
955
|
+
{ Name: 'state', Values: ['available'] },
|
|
956
|
+
],
|
|
783
957
|
});
|
|
784
958
|
const natResponse = await this.ec2Client.send(natCommand);
|
|
785
959
|
|
|
@@ -790,7 +964,7 @@ class AWSDiscovery {
|
|
|
790
964
|
misconfigurations.natGatewaysInPrivateSubnets.push({
|
|
791
965
|
natGatewayId: nat.NatGatewayId,
|
|
792
966
|
subnetId: nat.SubnetId,
|
|
793
|
-
tags: nat.Tags
|
|
967
|
+
tags: nat.Tags,
|
|
794
968
|
});
|
|
795
969
|
}
|
|
796
970
|
}
|
|
@@ -802,16 +976,24 @@ class AWSDiscovery {
|
|
|
802
976
|
|
|
803
977
|
if (eipResponse.Addresses) {
|
|
804
978
|
for (const eip of eipResponse.Addresses) {
|
|
805
|
-
if (
|
|
979
|
+
if (
|
|
980
|
+
!eip.InstanceId &&
|
|
981
|
+
!eip.NetworkInterfaceId &&
|
|
982
|
+
!eip.AssociationId
|
|
983
|
+
) {
|
|
806
984
|
// Check if it's Frigg-managed
|
|
807
|
-
const isFriggManaged =
|
|
808
|
-
|
|
809
|
-
|
|
985
|
+
const isFriggManaged =
|
|
986
|
+
eip.Tags &&
|
|
987
|
+
eip.Tags.some(
|
|
988
|
+
(tag) =>
|
|
989
|
+
tag.Key === 'ManagedBy' &&
|
|
990
|
+
tag.Value === 'Frigg'
|
|
991
|
+
);
|
|
810
992
|
if (isFriggManaged) {
|
|
811
993
|
misconfigurations.orphanedElasticIps.push({
|
|
812
994
|
allocationId: eip.AllocationId,
|
|
813
995
|
publicIp: eip.PublicIp,
|
|
814
|
-
tags: eip.Tags
|
|
996
|
+
tags: eip.Tags,
|
|
815
997
|
});
|
|
816
998
|
}
|
|
817
999
|
}
|
|
@@ -827,15 +1009,20 @@ class AWSDiscovery {
|
|
|
827
1009
|
|
|
828
1010
|
// Find route table for this subnet
|
|
829
1011
|
for (const rt of routeTables) {
|
|
830
|
-
const isAssociated =
|
|
831
|
-
|
|
832
|
-
|
|
1012
|
+
const isAssociated =
|
|
1013
|
+
rt.Associations &&
|
|
1014
|
+
rt.Associations.some(
|
|
1015
|
+
(assoc) => assoc.SubnetId === subnet.SubnetId
|
|
1016
|
+
);
|
|
833
1017
|
|
|
834
1018
|
if (isAssociated) {
|
|
835
|
-
hasNatRoute =
|
|
836
|
-
|
|
1019
|
+
hasNatRoute =
|
|
1020
|
+
rt.Routes &&
|
|
1021
|
+
rt.Routes.some(
|
|
1022
|
+
(route) =>
|
|
1023
|
+
route.NatGatewayId &&
|
|
837
1024
|
route.DestinationCidrBlock === '0.0.0.0/0'
|
|
838
|
-
|
|
1025
|
+
);
|
|
839
1026
|
break;
|
|
840
1027
|
}
|
|
841
1028
|
}
|
|
@@ -843,7 +1030,7 @@ class AWSDiscovery {
|
|
|
843
1030
|
if (!hasNatRoute) {
|
|
844
1031
|
misconfigurations.privateSubnetsWithoutNatRoute.push({
|
|
845
1032
|
subnetId: subnet.SubnetId,
|
|
846
|
-
availabilityZone: subnet.AvailabilityZone
|
|
1033
|
+
availabilityZone: subnet.AvailabilityZone,
|
|
847
1034
|
});
|
|
848
1035
|
}
|
|
849
1036
|
}
|
|
@@ -855,7 +1042,7 @@ class AWSDiscovery {
|
|
|
855
1042
|
natGatewaysInPrivateSubnets: [],
|
|
856
1043
|
orphanedElasticIps: [],
|
|
857
1044
|
misconfiguredRouteTables: [],
|
|
858
|
-
privateSubnetsWithoutNatRoute: []
|
|
1045
|
+
privateSubnetsWithoutNatRoute: [],
|
|
859
1046
|
};
|
|
860
1047
|
}
|
|
861
1048
|
}
|
|
@@ -872,8 +1059,12 @@ class AWSDiscovery {
|
|
|
872
1059
|
recommendations.push({
|
|
873
1060
|
severity: 'critical',
|
|
874
1061
|
issue: 'NAT Gateway in private subnet',
|
|
875
|
-
recommendation:
|
|
876
|
-
|
|
1062
|
+
recommendation:
|
|
1063
|
+
'Recreate NAT Gateway in public subnet or fix route tables',
|
|
1064
|
+
affectedResources:
|
|
1065
|
+
misconfigurations.natGatewaysInPrivateSubnets.map(
|
|
1066
|
+
(n) => n.natGatewayId
|
|
1067
|
+
),
|
|
877
1068
|
});
|
|
878
1069
|
}
|
|
879
1070
|
|
|
@@ -882,7 +1073,9 @@ class AWSDiscovery {
|
|
|
882
1073
|
severity: 'warning',
|
|
883
1074
|
issue: 'Orphaned Elastic IPs',
|
|
884
1075
|
recommendation: 'Release unused Elastic IPs to avoid charges',
|
|
885
|
-
affectedResources: misconfigurations.orphanedElasticIps.map(
|
|
1076
|
+
affectedResources: misconfigurations.orphanedElasticIps.map(
|
|
1077
|
+
(e) => e.allocationId
|
|
1078
|
+
),
|
|
886
1079
|
});
|
|
887
1080
|
}
|
|
888
1081
|
|
|
@@ -890,8 +1083,12 @@ class AWSDiscovery {
|
|
|
890
1083
|
recommendations.push({
|
|
891
1084
|
severity: 'critical',
|
|
892
1085
|
issue: 'Private subnets without NAT route',
|
|
893
|
-
recommendation:
|
|
894
|
-
|
|
1086
|
+
recommendation:
|
|
1087
|
+
'Add NAT Gateway route to private subnet route tables',
|
|
1088
|
+
affectedResources:
|
|
1089
|
+
misconfigurations.privateSubnetsWithoutNatRoute.map(
|
|
1090
|
+
(s) => s.subnetId
|
|
1091
|
+
),
|
|
895
1092
|
});
|
|
896
1093
|
}
|
|
897
1094
|
|
|
@@ -918,7 +1115,9 @@ class AWSDiscovery {
|
|
|
918
1115
|
*/
|
|
919
1116
|
async discoverResources(options = {}) {
|
|
920
1117
|
try {
|
|
921
|
-
console.log(
|
|
1118
|
+
console.log(
|
|
1119
|
+
'\nš Discovering AWS resources for Frigg deployment...'
|
|
1120
|
+
);
|
|
922
1121
|
console.log('ā'.repeat(60));
|
|
923
1122
|
|
|
924
1123
|
const vpc = await this.findDefaultVpc();
|
|
@@ -927,17 +1126,30 @@ class AWSDiscovery {
|
|
|
927
1126
|
// Enable auto-convert if selfHeal is enabled
|
|
928
1127
|
const autoConvert = options.selfHeal || false;
|
|
929
1128
|
|
|
930
|
-
const privateSubnets = await this.findPrivateSubnets(
|
|
931
|
-
|
|
1129
|
+
const privateSubnets = await this.findPrivateSubnets(
|
|
1130
|
+
vpc.VpcId,
|
|
1131
|
+
autoConvert
|
|
1132
|
+
);
|
|
1133
|
+
console.log(
|
|
1134
|
+
`\nā
Selected subnets for Lambda: ${privateSubnets
|
|
1135
|
+
.map((s) => s.SubnetId)
|
|
1136
|
+
.join(', ')}`
|
|
1137
|
+
);
|
|
932
1138
|
|
|
933
1139
|
const publicSubnet = await this.findPublicSubnets(vpc.VpcId);
|
|
934
1140
|
if (publicSubnet) {
|
|
935
|
-
console.log(
|
|
1141
|
+
console.log(
|
|
1142
|
+
`\nā
Found public subnet for NAT Gateway: ${publicSubnet.SubnetId}`
|
|
1143
|
+
);
|
|
936
1144
|
} else {
|
|
937
|
-
console.log(
|
|
1145
|
+
console.log(
|
|
1146
|
+
`\nā ļø No public subnet found - NAT Gateway creation may fail`
|
|
1147
|
+
);
|
|
938
1148
|
}
|
|
939
1149
|
|
|
940
|
-
const securityGroup = await this.findDefaultSecurityGroup(
|
|
1150
|
+
const securityGroup = await this.findDefaultSecurityGroup(
|
|
1151
|
+
vpc.VpcId
|
|
1152
|
+
);
|
|
941
1153
|
console.log(`\nā
Found security group: ${securityGroup.GroupId}`);
|
|
942
1154
|
|
|
943
1155
|
const routeTable = await this.findPrivateRouteTable(vpc.VpcId);
|
|
@@ -949,9 +1161,11 @@ class AWSDiscovery {
|
|
|
949
1161
|
} else {
|
|
950
1162
|
console.log('ā¹ļø No KMS key found');
|
|
951
1163
|
}
|
|
952
|
-
|
|
1164
|
+
|
|
953
1165
|
// Try to find existing NAT Gateway
|
|
954
|
-
const existingNatGateway = await this.findExistingNatGateway(
|
|
1166
|
+
const existingNatGateway = await this.findExistingNatGateway(
|
|
1167
|
+
vpc.VpcId
|
|
1168
|
+
);
|
|
955
1169
|
let natGatewayId = null;
|
|
956
1170
|
let elasticIpAllocationId = null;
|
|
957
1171
|
let natGatewayInPrivateSubnet = false;
|
|
@@ -959,11 +1173,16 @@ class AWSDiscovery {
|
|
|
959
1173
|
if (existingNatGateway) {
|
|
960
1174
|
natGatewayId = existingNatGateway.NatGatewayId;
|
|
961
1175
|
// Check if NAT Gateway is in a private subnet (from our detection)
|
|
962
|
-
natGatewayInPrivateSubnet =
|
|
1176
|
+
natGatewayInPrivateSubnet =
|
|
1177
|
+
existingNatGateway._isInPrivateSubnet || false;
|
|
963
1178
|
|
|
964
1179
|
// Get the EIP allocation ID from the NAT Gateway
|
|
965
|
-
if (
|
|
966
|
-
|
|
1180
|
+
if (
|
|
1181
|
+
existingNatGateway.NatGatewayAddresses &&
|
|
1182
|
+
existingNatGateway.NatGatewayAddresses.length > 0
|
|
1183
|
+
) {
|
|
1184
|
+
elasticIpAllocationId =
|
|
1185
|
+
existingNatGateway.NatGatewayAddresses[0].AllocationId;
|
|
967
1186
|
}
|
|
968
1187
|
} else {
|
|
969
1188
|
// If no NAT Gateway exists, check for available EIP
|
|
@@ -974,36 +1193,58 @@ class AWSDiscovery {
|
|
|
974
1193
|
}
|
|
975
1194
|
|
|
976
1195
|
// Check if the "private" subnets are actually public
|
|
977
|
-
const subnet1IsActuallyPrivate = privateSubnets[0]
|
|
978
|
-
await this.isSubnetPrivate(privateSubnets[0].SubnetId)
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1196
|
+
const subnet1IsActuallyPrivate = privateSubnets[0]
|
|
1197
|
+
? await this.isSubnetPrivate(privateSubnets[0].SubnetId)
|
|
1198
|
+
: false;
|
|
1199
|
+
const subnet2IsActuallyPrivate = privateSubnets[1]
|
|
1200
|
+
? await this.isSubnetPrivate(privateSubnets[1].SubnetId)
|
|
1201
|
+
: subnet1IsActuallyPrivate;
|
|
982
1202
|
|
|
983
1203
|
const subnetStatus = {
|
|
984
|
-
requiresConversion:
|
|
1204
|
+
requiresConversion:
|
|
1205
|
+
!subnet1IsActuallyPrivate || !subnet2IsActuallyPrivate,
|
|
985
1206
|
subnet1NeedsConversion: !subnet1IsActuallyPrivate,
|
|
986
|
-
subnet2NeedsConversion: !subnet2IsActuallyPrivate
|
|
1207
|
+
subnet2NeedsConversion: !subnet2IsActuallyPrivate,
|
|
987
1208
|
};
|
|
988
1209
|
|
|
989
1210
|
if (subnetStatus.requiresConversion) {
|
|
990
1211
|
console.log(`\nā ļø SUBNET CONFIGURATION WARNING:`);
|
|
991
1212
|
if (subnetStatus.subnet1NeedsConversion && privateSubnets[0]) {
|
|
992
|
-
console.log(
|
|
1213
|
+
console.log(
|
|
1214
|
+
` - Subnet ${privateSubnets[0].SubnetId} is currently PUBLIC but will be used for Lambda`
|
|
1215
|
+
);
|
|
993
1216
|
}
|
|
994
1217
|
if (subnetStatus.subnet2NeedsConversion && privateSubnets[1]) {
|
|
995
|
-
console.log(
|
|
1218
|
+
console.log(
|
|
1219
|
+
` - Subnet ${privateSubnets[1].SubnetId} is currently PUBLIC but will be used for Lambda`
|
|
1220
|
+
);
|
|
996
1221
|
}
|
|
997
|
-
console.log(
|
|
1222
|
+
console.log(
|
|
1223
|
+
` š” Enable selfHeal: true to automatically fix this`
|
|
1224
|
+
);
|
|
998
1225
|
}
|
|
999
1226
|
|
|
1000
1227
|
console.log(`\n${'ā'.repeat(60)}`);
|
|
1001
1228
|
console.log('š Discovery Summary:');
|
|
1002
1229
|
console.log(` VPC: ${vpc.VpcId}`);
|
|
1003
|
-
console.log(
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1230
|
+
console.log(
|
|
1231
|
+
` Lambda Subnets: ${privateSubnets
|
|
1232
|
+
.map((s) => s.SubnetId)
|
|
1233
|
+
.join(', ')}`
|
|
1234
|
+
);
|
|
1235
|
+
console.log(
|
|
1236
|
+
` NAT Subnet: ${
|
|
1237
|
+
publicSubnet?.SubnetId || 'None (needs creation)'
|
|
1238
|
+
}`
|
|
1239
|
+
);
|
|
1240
|
+
console.log(
|
|
1241
|
+
` NAT Gateway: ${natGatewayId || 'None (will be created)'}`
|
|
1242
|
+
);
|
|
1243
|
+
console.log(
|
|
1244
|
+
` Elastic IP: ${
|
|
1245
|
+
elasticIpAllocationId || 'None (will be allocated)'
|
|
1246
|
+
}`
|
|
1247
|
+
);
|
|
1007
1248
|
if (subnetStatus.requiresConversion) {
|
|
1008
1249
|
console.log(` ā ļø Subnet Conversion Required: Yes`);
|
|
1009
1250
|
}
|
|
@@ -1013,7 +1254,8 @@ class AWSDiscovery {
|
|
|
1013
1254
|
defaultVpcId: vpc.VpcId,
|
|
1014
1255
|
defaultSecurityGroupId: securityGroup.GroupId,
|
|
1015
1256
|
privateSubnetId1: privateSubnets[0]?.SubnetId,
|
|
1016
|
-
privateSubnetId2:
|
|
1257
|
+
privateSubnetId2:
|
|
1258
|
+
privateSubnets[1]?.SubnetId || privateSubnets[0]?.SubnetId,
|
|
1017
1259
|
publicSubnetId: publicSubnet?.SubnetId || null, // May be null if no public subnet exists
|
|
1018
1260
|
privateRouteTableId: routeTable.RouteTableId,
|
|
1019
1261
|
defaultKmsKeyId: kmsKeyArn,
|
|
@@ -1023,14 +1265,20 @@ class AWSDiscovery {
|
|
|
1023
1265
|
subnetConversionRequired: subnetStatus.requiresConversion,
|
|
1024
1266
|
privateSubnetsWithWrongRoutes: (() => {
|
|
1025
1267
|
const wrongRoutes = [];
|
|
1026
|
-
if (
|
|
1268
|
+
if (
|
|
1269
|
+
subnetStatus.subnet1NeedsConversion &&
|
|
1270
|
+
privateSubnets[0]
|
|
1271
|
+
) {
|
|
1027
1272
|
wrongRoutes.push(privateSubnets[0].SubnetId);
|
|
1028
1273
|
}
|
|
1029
|
-
if (
|
|
1274
|
+
if (
|
|
1275
|
+
subnetStatus.subnet2NeedsConversion &&
|
|
1276
|
+
privateSubnets[1]
|
|
1277
|
+
) {
|
|
1030
1278
|
wrongRoutes.push(privateSubnets[1].SubnetId);
|
|
1031
1279
|
}
|
|
1032
1280
|
return wrongRoutes;
|
|
1033
|
-
})()
|
|
1281
|
+
})(),
|
|
1034
1282
|
};
|
|
1035
1283
|
} catch (error) {
|
|
1036
1284
|
console.error('Error discovering AWS resources:', error);
|
|
@@ -1049,19 +1297,24 @@ class AWSDiscovery {
|
|
|
1049
1297
|
Filters: [
|
|
1050
1298
|
{
|
|
1051
1299
|
Name: 'attachment.vpc-id',
|
|
1052
|
-
Values: [vpcId]
|
|
1300
|
+
Values: [vpcId],
|
|
1053
1301
|
},
|
|
1054
1302
|
{
|
|
1055
1303
|
Name: 'attachment.state',
|
|
1056
|
-
Values: ['available']
|
|
1057
|
-
}
|
|
1058
|
-
]
|
|
1304
|
+
Values: ['available'],
|
|
1305
|
+
},
|
|
1306
|
+
],
|
|
1059
1307
|
});
|
|
1060
1308
|
|
|
1061
1309
|
const response = await this.ec2Client.send(command);
|
|
1062
1310
|
|
|
1063
|
-
if (
|
|
1064
|
-
|
|
1311
|
+
if (
|
|
1312
|
+
response.InternetGateways &&
|
|
1313
|
+
response.InternetGateways.length > 0
|
|
1314
|
+
) {
|
|
1315
|
+
console.log(
|
|
1316
|
+
`Found existing Internet Gateway: ${response.InternetGateways[0].InternetGatewayId}`
|
|
1317
|
+
);
|
|
1065
1318
|
return response.InternetGateways[0];
|
|
1066
1319
|
}
|
|
1067
1320
|
|
|
@@ -1085,28 +1338,28 @@ class AWSDiscovery {
|
|
|
1085
1338
|
elasticIps: [],
|
|
1086
1339
|
routeTables: [],
|
|
1087
1340
|
subnets: [],
|
|
1088
|
-
securityGroups: []
|
|
1341
|
+
securityGroups: [],
|
|
1089
1342
|
};
|
|
1090
1343
|
|
|
1091
1344
|
// Common filter for Frigg-managed resources
|
|
1092
1345
|
const friggFilters = [
|
|
1093
1346
|
{
|
|
1094
1347
|
Name: 'tag:ManagedBy',
|
|
1095
|
-
Values: ['Frigg']
|
|
1096
|
-
}
|
|
1348
|
+
Values: ['Frigg'],
|
|
1349
|
+
},
|
|
1097
1350
|
];
|
|
1098
1351
|
|
|
1099
1352
|
if (serviceName) {
|
|
1100
1353
|
friggFilters.push({
|
|
1101
1354
|
Name: 'tag:Service',
|
|
1102
|
-
Values: [serviceName]
|
|
1355
|
+
Values: [serviceName],
|
|
1103
1356
|
});
|
|
1104
1357
|
}
|
|
1105
1358
|
|
|
1106
1359
|
if (stage) {
|
|
1107
1360
|
friggFilters.push({
|
|
1108
1361
|
Name: 'tag:Stage',
|
|
1109
|
-
Values: [stage]
|
|
1362
|
+
Values: [stage],
|
|
1110
1363
|
});
|
|
1111
1364
|
}
|
|
1112
1365
|
|
|
@@ -1117,9 +1370,9 @@ class AWSDiscovery {
|
|
|
1117
1370
|
...friggFilters,
|
|
1118
1371
|
{
|
|
1119
1372
|
Name: 'state',
|
|
1120
|
-
Values: ['available']
|
|
1121
|
-
}
|
|
1122
|
-
]
|
|
1373
|
+
Values: ['available'],
|
|
1374
|
+
},
|
|
1375
|
+
],
|
|
1123
1376
|
});
|
|
1124
1377
|
const natResponse = await this.ec2Client.send(natCommand);
|
|
1125
1378
|
results.natGateways = natResponse.NatGateways || [];
|
|
@@ -1130,7 +1383,7 @@ class AWSDiscovery {
|
|
|
1130
1383
|
// Find Elastic IPs
|
|
1131
1384
|
try {
|
|
1132
1385
|
const eipCommand = new DescribeAddressesCommand({
|
|
1133
|
-
Filters: friggFilters
|
|
1386
|
+
Filters: friggFilters,
|
|
1134
1387
|
});
|
|
1135
1388
|
const eipResponse = await this.ec2Client.send(eipCommand);
|
|
1136
1389
|
results.elasticIps = eipResponse.Addresses || [];
|
|
@@ -1141,7 +1394,7 @@ class AWSDiscovery {
|
|
|
1141
1394
|
// Find Route Tables
|
|
1142
1395
|
try {
|
|
1143
1396
|
const rtCommand = new DescribeRouteTablesCommand({
|
|
1144
|
-
Filters: friggFilters
|
|
1397
|
+
Filters: friggFilters,
|
|
1145
1398
|
});
|
|
1146
1399
|
const rtResponse = await this.ec2Client.send(rtCommand);
|
|
1147
1400
|
results.routeTables = rtResponse.RouteTables || [];
|
|
@@ -1152,7 +1405,7 @@ class AWSDiscovery {
|
|
|
1152
1405
|
// Find Subnets
|
|
1153
1406
|
try {
|
|
1154
1407
|
const subnetCommand = new DescribeSubnetsCommand({
|
|
1155
|
-
Filters: friggFilters
|
|
1408
|
+
Filters: friggFilters,
|
|
1156
1409
|
});
|
|
1157
1410
|
const subnetResponse = await this.ec2Client.send(subnetCommand);
|
|
1158
1411
|
results.subnets = subnetResponse.Subnets || [];
|
|
@@ -1163,12 +1416,15 @@ class AWSDiscovery {
|
|
|
1163
1416
|
// Find Security Groups
|
|
1164
1417
|
try {
|
|
1165
1418
|
const sgCommand = new DescribeSecurityGroupsCommand({
|
|
1166
|
-
Filters: friggFilters
|
|
1419
|
+
Filters: friggFilters,
|
|
1167
1420
|
});
|
|
1168
1421
|
const sgResponse = await this.ec2Client.send(sgCommand);
|
|
1169
1422
|
results.securityGroups = sgResponse.SecurityGroups || [];
|
|
1170
1423
|
} catch (err) {
|
|
1171
|
-
console.warn(
|
|
1424
|
+
console.warn(
|
|
1425
|
+
'Error finding Frigg Security Groups:',
|
|
1426
|
+
err.message
|
|
1427
|
+
);
|
|
1172
1428
|
}
|
|
1173
1429
|
|
|
1174
1430
|
console.log('Found Frigg-managed resources:', {
|
|
@@ -1176,7 +1432,7 @@ class AWSDiscovery {
|
|
|
1176
1432
|
elasticIps: results.elasticIps.length,
|
|
1177
1433
|
routeTables: results.routeTables.length,
|
|
1178
1434
|
subnets: results.subnets.length,
|
|
1179
|
-
securityGroups: results.securityGroups.length
|
|
1435
|
+
securityGroups: results.securityGroups.length,
|
|
1180
1436
|
});
|
|
1181
1437
|
|
|
1182
1438
|
return results;
|
|
@@ -1187,10 +1443,10 @@ class AWSDiscovery {
|
|
|
1187
1443
|
elasticIps: [],
|
|
1188
1444
|
routeTables: [],
|
|
1189
1445
|
subnets: [],
|
|
1190
|
-
securityGroups: []
|
|
1446
|
+
securityGroups: [],
|
|
1191
1447
|
};
|
|
1192
1448
|
}
|
|
1193
1449
|
}
|
|
1194
1450
|
}
|
|
1195
1451
|
|
|
1196
|
-
module.exports = { AWSDiscovery };
|
|
1452
|
+
module.exports = { AWSDiscovery };
|