@friggframework/devtools 2.0.0--canary.428.d004aeb.0 ā 2.0.0--canary.428.1a6e465.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.
|
@@ -43,15 +43,7 @@ function loadSTS() {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
/**
|
|
47
|
-
* AWS Resource Discovery utilities for Frigg applications
|
|
48
|
-
* These functions use AWS credentials to discover default resources during build time
|
|
49
|
-
*/
|
|
50
46
|
class AWSDiscovery {
|
|
51
|
-
/**
|
|
52
|
-
* Creates an instance of AWSDiscovery
|
|
53
|
-
* @param {string} [region='us-east-1'] - AWS region to use for discovery
|
|
54
|
-
*/
|
|
55
47
|
constructor(region = 'us-east-1') {
|
|
56
48
|
this.region = region;
|
|
57
49
|
loadEC2();
|
|
@@ -62,11 +54,6 @@ class AWSDiscovery {
|
|
|
62
54
|
this.stsClient = new STSClient({ region });
|
|
63
55
|
}
|
|
64
56
|
|
|
65
|
-
/**
|
|
66
|
-
* Get AWS account ID
|
|
67
|
-
* @returns {Promise<string>} The AWS account ID
|
|
68
|
-
* @throws {Error} If unable to retrieve account ID
|
|
69
|
-
*/
|
|
70
57
|
async getAccountId() {
|
|
71
58
|
try {
|
|
72
59
|
const command = new GetCallerIdentityCommand({});
|
|
@@ -78,11 +65,6 @@ class AWSDiscovery {
|
|
|
78
65
|
}
|
|
79
66
|
}
|
|
80
67
|
|
|
81
|
-
/**
|
|
82
|
-
* Find the default VPC for the account
|
|
83
|
-
* @returns {Promise<Object>} VPC object containing VpcId and other properties
|
|
84
|
-
* @throws {Error} If no VPC is found in the account
|
|
85
|
-
*/
|
|
86
68
|
async findDefaultVpc() {
|
|
87
69
|
try {
|
|
88
70
|
const command = new DescribeVpcsCommand({
|
|
@@ -100,7 +82,6 @@ class AWSDiscovery {
|
|
|
100
82
|
return response.Vpcs[0];
|
|
101
83
|
}
|
|
102
84
|
|
|
103
|
-
// If no default VPC, get the first available VPC
|
|
104
85
|
const allVpcsCommand = new DescribeVpcsCommand({});
|
|
105
86
|
const allVpcsResponse = await this.ec2Client.send(allVpcsCommand);
|
|
106
87
|
|
|
@@ -116,144 +97,34 @@ class AWSDiscovery {
|
|
|
116
97
|
}
|
|
117
98
|
}
|
|
118
99
|
|
|
119
|
-
/**
|
|
120
|
-
* Find private subnets for the given VPC
|
|
121
|
-
* @param {string} vpcId - The VPC ID to search within
|
|
122
|
-
* @param {boolean} autoConvert - If true, convert public subnets to private if needed
|
|
123
|
-
* @returns {Promise<Array>} Array of subnet objects (at least 2 for high availability)
|
|
124
|
-
* @throws {Error} If no subnets are found in the VPC
|
|
125
|
-
*/
|
|
126
100
|
async findPrivateSubnets(vpcId, autoConvert = false) {
|
|
127
101
|
try {
|
|
128
|
-
const
|
|
129
|
-
Filters: [
|
|
130
|
-
{
|
|
131
|
-
Name: 'vpc-id',
|
|
132
|
-
Values: [vpcId],
|
|
133
|
-
},
|
|
134
|
-
],
|
|
135
|
-
});
|
|
102
|
+
const subnets = await this._fetchSubnets(vpcId);
|
|
136
103
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (!response.Subnets || response.Subnets.length === 0) {
|
|
104
|
+
if (subnets.length === 0) {
|
|
140
105
|
throw new Error(`No subnets found in VPC ${vpcId}`);
|
|
141
106
|
}
|
|
142
107
|
|
|
143
|
-
console.log(
|
|
144
|
-
`\nš Analyzing ${response.Subnets.length} subnets in VPC ${vpcId}...`
|
|
145
|
-
);
|
|
146
|
-
|
|
147
|
-
// Categorize subnets by their actual routing
|
|
148
|
-
const privateSubnets = [];
|
|
149
|
-
const publicSubnets = [];
|
|
150
|
-
|
|
151
|
-
for (const subnet of response.Subnets) {
|
|
152
|
-
// Check route tables to determine if subnet is private
|
|
153
|
-
const isPrivate = await this.isSubnetPrivate(subnet.SubnetId);
|
|
154
|
-
if (isPrivate) {
|
|
155
|
-
privateSubnets.push(subnet);
|
|
156
|
-
console.log(
|
|
157
|
-
` š Private subnet: ${subnet.SubnetId} (AZ: ${subnet.AvailabilityZone})`
|
|
158
|
-
);
|
|
159
|
-
} else {
|
|
160
|
-
publicSubnets.push(subnet);
|
|
161
|
-
console.log(
|
|
162
|
-
` š Public subnet: ${subnet.SubnetId} (AZ: ${subnet.AvailabilityZone})`
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
console.log(`\nš Subnet Analysis Results:`);
|
|
168
|
-
console.log(` - Private subnets: ${privateSubnets.length}`);
|
|
169
|
-
console.log(` - Public subnets: ${publicSubnets.length}`);
|
|
170
|
-
|
|
171
|
-
// If we have at least 2 private subnets, use them
|
|
172
|
-
if (privateSubnets.length >= 2) {
|
|
173
|
-
console.log(
|
|
174
|
-
`ā
Found ${privateSubnets.length} private subnets for Lambda deployment`
|
|
175
|
-
);
|
|
176
|
-
return privateSubnets.slice(0, 2);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// If we have 1 private subnet, we need at least one more
|
|
180
|
-
if (privateSubnets.length === 1) {
|
|
181
|
-
console.warn(
|
|
182
|
-
`ā ļø Only 1 private subnet found. Need at least 2 for high availability.`
|
|
183
|
-
);
|
|
184
|
-
if (publicSubnets.length > 0 && autoConvert) {
|
|
185
|
-
console.log(
|
|
186
|
-
`š Will convert 1 public subnet to private for high availability...`
|
|
187
|
-
);
|
|
188
|
-
// Note: The actual conversion happens in the serverless template
|
|
189
|
-
}
|
|
190
|
-
// Return what we have - mix of private and public if needed
|
|
191
|
-
return [...privateSubnets, ...publicSubnets].slice(0, 2);
|
|
192
|
-
}
|
|
108
|
+
console.log(`\nš Analyzing ${subnets.length} subnets in VPC ${vpcId}...`);
|
|
193
109
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
);
|
|
199
|
-
console.error(
|
|
200
|
-
`ā Lambda functions should NOT be deployed in public subnets!`
|
|
201
|
-
);
|
|
110
|
+
const { privateSubnets, publicSubnets } = await this._classifySubnets(
|
|
111
|
+
subnets,
|
|
112
|
+
{ logDetails: true }
|
|
113
|
+
);
|
|
202
114
|
|
|
203
|
-
|
|
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
|
-
);
|
|
213
|
-
if (publicSubnets[2]) {
|
|
214
|
-
console.log(
|
|
215
|
-
` - Converting ${publicSubnets[2].SubnetId} to private (for Lambda)`
|
|
216
|
-
);
|
|
217
|
-
}
|
|
115
|
+
this._logSubnetSummary(privateSubnets.length, publicSubnets.length);
|
|
218
116
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
);
|
|
226
|
-
console.log(
|
|
227
|
-
` - Will need to create new subnets or reconfigure existing ones`
|
|
228
|
-
);
|
|
229
|
-
// Return what we have but flag for conversion
|
|
230
|
-
return publicSubnets.slice(0, 2);
|
|
231
|
-
} else {
|
|
232
|
-
console.error(`\nā ļø CONFIGURATION ERROR:`);
|
|
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
|
-
);
|
|
239
|
-
console.error(`\n Options:`);
|
|
240
|
-
console.error(
|
|
241
|
-
` 1. Enable selfHeal: true in vpc configuration`
|
|
242
|
-
);
|
|
243
|
-
console.error(` 2. Create private subnets manually`);
|
|
244
|
-
console.error(
|
|
245
|
-
` 3. Set subnets.management: 'create' to create new private subnets`
|
|
246
|
-
);
|
|
117
|
+
const selection = this._selectSubnetsForLambda({
|
|
118
|
+
privateSubnets,
|
|
119
|
+
publicSubnets,
|
|
120
|
+
autoConvert,
|
|
121
|
+
vpcId,
|
|
122
|
+
});
|
|
247
123
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
`Found ${publicSubnets.length} public subnets. ` +
|
|
251
|
-
`Lambda requires private subnets. Enable selfHeal or create private subnets.`
|
|
252
|
-
);
|
|
253
|
-
}
|
|
124
|
+
if (selection) {
|
|
125
|
+
return selection;
|
|
254
126
|
}
|
|
255
127
|
|
|
256
|
-
// No subnets at all?
|
|
257
128
|
throw new Error(`No subnets found in VPC ${vpcId}`);
|
|
258
129
|
} catch (error) {
|
|
259
130
|
console.error('Error finding private subnets:', error);
|
|
@@ -261,168 +132,57 @@ class AWSDiscovery {
|
|
|
261
132
|
}
|
|
262
133
|
}
|
|
263
134
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
* @param {string} subnetId - The subnet ID to check
|
|
267
|
-
* @returns {Promise<boolean>} True if subnet is private, false if public
|
|
268
|
-
*/
|
|
269
|
-
/**
|
|
270
|
-
* Validate if a subnet is truly public (has IGW route)
|
|
271
|
-
* @param {string} subnetId - The subnet ID to validate
|
|
272
|
-
* @returns {Promise<boolean>} true if public (has IGW route), false if private
|
|
273
|
-
*/
|
|
274
|
-
async isSubnetPublic(subnetId) {
|
|
275
|
-
const isPrivate = await this.isSubnetPrivate(subnetId);
|
|
135
|
+
async isSubnetPublic(subnetId, vpcId) {
|
|
136
|
+
const isPrivate = await this.isSubnetPrivate(subnetId, vpcId);
|
|
276
137
|
return !isPrivate;
|
|
277
138
|
}
|
|
278
139
|
|
|
279
|
-
async isSubnetPrivate(subnetId) {
|
|
140
|
+
async isSubnetPrivate(subnetId, vpcId) {
|
|
280
141
|
try {
|
|
281
|
-
|
|
282
|
-
const subnetCommand = new DescribeSubnetsCommand({
|
|
283
|
-
SubnetIds: [subnetId],
|
|
284
|
-
});
|
|
285
|
-
const subnetResponse = await this.ec2Client.send(subnetCommand);
|
|
286
|
-
|
|
287
|
-
if (
|
|
288
|
-
!subnetResponse.Subnets ||
|
|
289
|
-
subnetResponse.Subnets.length === 0
|
|
290
|
-
) {
|
|
291
|
-
throw new Error(`Subnet ${subnetId} not found`);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
const subnet = subnetResponse.Subnets[0];
|
|
295
|
-
const vpcId = subnet.VpcId;
|
|
296
|
-
|
|
297
|
-
// Get all route tables for this VPC
|
|
298
|
-
const routeTablesCommand = new DescribeRouteTablesCommand({
|
|
299
|
-
Filters: [
|
|
300
|
-
{
|
|
301
|
-
Name: 'vpc-id',
|
|
302
|
-
Values: [vpcId],
|
|
303
|
-
},
|
|
304
|
-
],
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
const routeTablesResponse = await this.ec2Client.send(
|
|
308
|
-
routeTablesCommand
|
|
309
|
-
);
|
|
310
|
-
|
|
311
|
-
// Find the route table for this subnet
|
|
312
|
-
let routeTable = null;
|
|
313
|
-
|
|
314
|
-
// First check for explicit association
|
|
315
|
-
for (const rt of routeTablesResponse.RouteTables || []) {
|
|
316
|
-
for (const assoc of rt.Associations || []) {
|
|
317
|
-
if (assoc.SubnetId === subnetId) {
|
|
318
|
-
routeTable = rt;
|
|
319
|
-
break;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
if (routeTable) break;
|
|
323
|
-
}
|
|
142
|
+
const targetVpcId = vpcId || (await this._getSubnetVpcId(subnetId));
|
|
324
143
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
for (const rt of routeTablesResponse.RouteTables || []) {
|
|
328
|
-
for (const assoc of rt.Associations || []) {
|
|
329
|
-
if (assoc.Main === true) {
|
|
330
|
-
routeTable = rt;
|
|
331
|
-
break;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
if (routeTable) break;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
144
|
+
const routeTables = await this.findRouteTables(targetVpcId);
|
|
145
|
+
const routeTable = this._findRouteTableForSubnet(routeTables, subnetId);
|
|
337
146
|
|
|
338
147
|
if (!routeTable) {
|
|
339
148
|
console.warn(`No route table found for subnet ${subnetId}`);
|
|
340
|
-
return true;
|
|
149
|
+
return true;
|
|
341
150
|
}
|
|
342
151
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
let gatewayId = null;
|
|
346
|
-
|
|
347
|
-
for (const route of routeTable.Routes || []) {
|
|
348
|
-
if (route.GatewayId && route.GatewayId.startsWith('igw-')) {
|
|
349
|
-
hasIgwRoute = true;
|
|
350
|
-
gatewayId = route.GatewayId;
|
|
351
|
-
break;
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
// Enhanced logging for validation
|
|
356
|
-
if (hasIgwRoute) {
|
|
152
|
+
const gatewayId = this._findIgwRoute(routeTable);
|
|
153
|
+
if (gatewayId) {
|
|
357
154
|
console.log(
|
|
358
155
|
`ā
Subnet ${subnetId} is PUBLIC (has route to IGW ${gatewayId})`
|
|
359
156
|
);
|
|
360
|
-
return false;
|
|
361
|
-
} else {
|
|
362
|
-
console.log(
|
|
363
|
-
`š Subnet ${subnetId} is PRIVATE (no IGW route found)`
|
|
364
|
-
);
|
|
365
|
-
return true; // No IGW route found, it's private
|
|
157
|
+
return false;
|
|
366
158
|
}
|
|
159
|
+
|
|
160
|
+
console.log(
|
|
161
|
+
`š Subnet ${subnetId} is PRIVATE (no IGW route found)`
|
|
162
|
+
);
|
|
163
|
+
return true;
|
|
367
164
|
} catch (error) {
|
|
368
165
|
console.warn(
|
|
369
166
|
`Could not determine if subnet ${subnetId} is private:`,
|
|
370
167
|
error
|
|
371
168
|
);
|
|
372
|
-
return true;
|
|
169
|
+
return true;
|
|
373
170
|
}
|
|
374
171
|
}
|
|
375
172
|
|
|
376
|
-
/**
|
|
377
|
-
* Find or create a default security group for Lambda functions
|
|
378
|
-
* @param {string} vpcId - The VPC ID to search within
|
|
379
|
-
* @returns {Promise<Object>} Security group object containing GroupId and other properties
|
|
380
|
-
* @throws {Error} If no security group is found for the VPC
|
|
381
|
-
*/
|
|
382
173
|
async findDefaultSecurityGroup(vpcId) {
|
|
383
174
|
try {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
},
|
|
391
|
-
{
|
|
392
|
-
Name: 'group-name',
|
|
393
|
-
Values: ['frigg-lambda-sg'],
|
|
394
|
-
},
|
|
395
|
-
],
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
const friggResponse = await this.ec2Client.send(friggSgCommand);
|
|
399
|
-
if (
|
|
400
|
-
friggResponse.SecurityGroups &&
|
|
401
|
-
friggResponse.SecurityGroups.length > 0
|
|
402
|
-
) {
|
|
403
|
-
return friggResponse.SecurityGroups[0];
|
|
175
|
+
const friggGroup = await this._findSecurityGroupByName(
|
|
176
|
+
vpcId,
|
|
177
|
+
'frigg-lambda-sg'
|
|
178
|
+
);
|
|
179
|
+
if (friggGroup) {
|
|
180
|
+
return friggGroup;
|
|
404
181
|
}
|
|
405
182
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
{
|
|
410
|
-
Name: 'vpc-id',
|
|
411
|
-
Values: [vpcId],
|
|
412
|
-
},
|
|
413
|
-
{
|
|
414
|
-
Name: 'group-name',
|
|
415
|
-
Values: ['default'],
|
|
416
|
-
},
|
|
417
|
-
],
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
const defaultResponse = await this.ec2Client.send(defaultSgCommand);
|
|
421
|
-
if (
|
|
422
|
-
defaultResponse.SecurityGroups &&
|
|
423
|
-
defaultResponse.SecurityGroups.length > 0
|
|
424
|
-
) {
|
|
425
|
-
return defaultResponse.SecurityGroups[0];
|
|
183
|
+
const defaultGroup = await this._findSecurityGroupByName(vpcId, 'default');
|
|
184
|
+
if (defaultGroup) {
|
|
185
|
+
return defaultGroup;
|
|
426
186
|
}
|
|
427
187
|
|
|
428
188
|
throw new Error(`No security group found for VPC ${vpcId}`);
|
|
@@ -432,42 +192,17 @@ class AWSDiscovery {
|
|
|
432
192
|
}
|
|
433
193
|
}
|
|
434
194
|
|
|
435
|
-
/**
|
|
436
|
-
* Find public subnets for NAT Gateway placement
|
|
437
|
-
* @param {string} vpcId - The VPC ID to search within
|
|
438
|
-
* @returns {Promise<Object>} First public subnet object for NAT Gateway placement
|
|
439
|
-
* @throws {Error} If no public subnets are found in the VPC
|
|
440
|
-
*/
|
|
441
195
|
async findPublicSubnets(vpcId) {
|
|
442
196
|
try {
|
|
443
|
-
const
|
|
444
|
-
Filters: [
|
|
445
|
-
{
|
|
446
|
-
Name: 'vpc-id',
|
|
447
|
-
Values: [vpcId],
|
|
448
|
-
},
|
|
449
|
-
],
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
const response = await this.ec2Client.send(command);
|
|
197
|
+
const subnets = await this._fetchSubnets(vpcId);
|
|
453
198
|
|
|
454
|
-
if (
|
|
199
|
+
if (subnets.length === 0) {
|
|
455
200
|
throw new Error(`No subnets found in VPC ${vpcId}`);
|
|
456
201
|
}
|
|
457
202
|
|
|
458
|
-
|
|
459
|
-
const publicSubnets = [];
|
|
460
|
-
|
|
461
|
-
for (const subnet of response.Subnets) {
|
|
462
|
-
// Check route tables to determine if subnet is public
|
|
463
|
-
const isPrivate = await this.isSubnetPrivate(subnet.SubnetId);
|
|
464
|
-
if (!isPrivate) {
|
|
465
|
-
publicSubnets.push(subnet);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
203
|
+
const { publicSubnets } = await this._classifySubnets(subnets);
|
|
468
204
|
|
|
469
205
|
if (publicSubnets.length === 0) {
|
|
470
|
-
// If no public subnets found, we need to create one or inform the user
|
|
471
206
|
console.warn(
|
|
472
207
|
`WARNING: No public subnets found in VPC ${vpcId}`
|
|
473
208
|
);
|
|
@@ -477,10 +212,9 @@ class AWSDiscovery {
|
|
|
477
212
|
console.warn(
|
|
478
213
|
'Please create a public subnet or use VPC endpoints instead'
|
|
479
214
|
);
|
|
480
|
-
return null;
|
|
215
|
+
return null;
|
|
481
216
|
}
|
|
482
217
|
|
|
483
|
-
// Return first public subnet for NAT Gateway
|
|
484
218
|
console.log(
|
|
485
219
|
`Found ${publicSubnets.length} public subnets, using ${publicSubnets[0].SubnetId} for NAT Gateway`
|
|
486
220
|
);
|
|
@@ -491,56 +225,25 @@ class AWSDiscovery {
|
|
|
491
225
|
}
|
|
492
226
|
}
|
|
493
227
|
|
|
494
|
-
/**
|
|
495
|
-
* Find private route table for VPC endpoints
|
|
496
|
-
* @param {string} vpcId - The VPC ID to search within
|
|
497
|
-
* @returns {Promise<Object>} Route table object containing RouteTableId and other properties
|
|
498
|
-
* @throws {Error} If no route tables are found for the VPC
|
|
499
|
-
*/
|
|
500
228
|
async findPrivateRouteTable(vpcId) {
|
|
501
229
|
try {
|
|
502
|
-
const
|
|
503
|
-
Filters: [
|
|
504
|
-
{
|
|
505
|
-
Name: 'vpc-id',
|
|
506
|
-
Values: [vpcId],
|
|
507
|
-
},
|
|
508
|
-
],
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
const response = await this.ec2Client.send(command);
|
|
230
|
+
const routeTables = await this.findRouteTables(vpcId);
|
|
512
231
|
|
|
513
|
-
if (
|
|
232
|
+
if (routeTables.length === 0) {
|
|
514
233
|
throw new Error(`No route tables found for VPC ${vpcId}`);
|
|
515
234
|
}
|
|
516
235
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
for (const route of routeTable.Routes || []) {
|
|
521
|
-
if (route.GatewayId && route.GatewayId.startsWith('igw-')) {
|
|
522
|
-
hasIgwRoute = true;
|
|
523
|
-
break;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
if (!hasIgwRoute) {
|
|
527
|
-
return routeTable;
|
|
528
|
-
}
|
|
529
|
-
}
|
|
236
|
+
const privateTable = routeTables.find(
|
|
237
|
+
(rt) => !this._findIgwRoute(rt)
|
|
238
|
+
);
|
|
530
239
|
|
|
531
|
-
|
|
532
|
-
return response.RouteTables[0];
|
|
240
|
+
return privateTable || routeTables[0];
|
|
533
241
|
} catch (error) {
|
|
534
242
|
console.error('Error finding private route table:', error);
|
|
535
243
|
throw error;
|
|
536
244
|
}
|
|
537
245
|
}
|
|
538
246
|
|
|
539
|
-
/**
|
|
540
|
-
* Find existing NAT Gateways in the VPC
|
|
541
|
-
* @param {string} vpcId - The VPC ID to search within
|
|
542
|
-
* @returns {Promise<Object|null>} NAT Gateway object or null if none found
|
|
543
|
-
*/
|
|
544
247
|
async findExistingNatGateway(vpcId) {
|
|
545
248
|
try {
|
|
546
249
|
const command = new DescribeNatGatewaysCommand({
|
|
@@ -558,122 +261,85 @@ class AWSDiscovery {
|
|
|
558
261
|
|
|
559
262
|
const response = await this.ec2Client.send(command);
|
|
560
263
|
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
return false;
|
|
568
|
-
}
|
|
569
|
-
return true;
|
|
570
|
-
});
|
|
571
|
-
|
|
572
|
-
if (availableNatGateways.length === 0) {
|
|
573
|
-
console.warn('No truly available NAT Gateways found in VPC');
|
|
574
|
-
return null;
|
|
264
|
+
const natGateways = (response.NatGateways || []).filter((nat) => {
|
|
265
|
+
if (nat.State !== 'available') {
|
|
266
|
+
console.warn(
|
|
267
|
+
`Skipping NAT Gateway ${nat.NatGatewayId} with state: ${nat.State}`
|
|
268
|
+
);
|
|
269
|
+
return false;
|
|
575
270
|
}
|
|
271
|
+
return true;
|
|
272
|
+
});
|
|
576
273
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
a.Tags.some(
|
|
582
|
-
(tag) =>
|
|
583
|
-
(tag.Key === 'ManagedBy' &&
|
|
584
|
-
tag.Value === 'Frigg') ||
|
|
585
|
-
(tag.Key === 'Name' &&
|
|
586
|
-
tag.Value.includes('frigg'))
|
|
587
|
-
);
|
|
588
|
-
const bIsFrigg =
|
|
589
|
-
b.Tags &&
|
|
590
|
-
b.Tags.some(
|
|
591
|
-
(tag) =>
|
|
592
|
-
(tag.Key === 'ManagedBy' &&
|
|
593
|
-
tag.Value === 'Frigg') ||
|
|
594
|
-
(tag.Key === 'Name' &&
|
|
595
|
-
tag.Value.includes('frigg'))
|
|
596
|
-
);
|
|
274
|
+
if (natGateways.length === 0) {
|
|
275
|
+
console.warn('No truly available NAT Gateways found in VPC');
|
|
276
|
+
return null;
|
|
277
|
+
}
|
|
597
278
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
});
|
|
279
|
+
const sortedNatGateways = natGateways.sort((a, b) => {
|
|
280
|
+
const aIsFrigg = this._isFriggManaged(a.Tags);
|
|
281
|
+
const bIsFrigg = this._isFriggManaged(b.Tags);
|
|
602
282
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
// Check if it's a Frigg-managed NAT Gateway
|
|
609
|
-
const isFriggNat =
|
|
610
|
-
natGateway.Tags &&
|
|
611
|
-
natGateway.Tags.some(
|
|
612
|
-
(tag) =>
|
|
613
|
-
(tag.Key === 'ManagedBy' &&
|
|
614
|
-
tag.Value === 'Frigg') ||
|
|
615
|
-
(tag.Key === 'Name' &&
|
|
616
|
-
tag.Value.includes('frigg'))
|
|
617
|
-
);
|
|
283
|
+
if (aIsFrigg && !bIsFrigg) return -1;
|
|
284
|
+
if (!aIsFrigg && bIsFrigg) return 1;
|
|
285
|
+
return 0;
|
|
286
|
+
});
|
|
618
287
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
288
|
+
for (const natGateway of sortedNatGateways) {
|
|
289
|
+
const subnetId = natGateway.SubnetId;
|
|
290
|
+
const isPrivate = await this.isSubnetPrivate(
|
|
291
|
+
subnetId,
|
|
292
|
+
natGateway.VpcId
|
|
293
|
+
);
|
|
294
|
+
const isFriggNat = this._isFriggManaged(natGateway.Tags);
|
|
625
295
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
console.warn(
|
|
631
|
-
'Consider enabling selfHeal: true to fix this automatically'
|
|
632
|
-
);
|
|
633
|
-
// Return it anyway if it's Frigg-managed - we can fix the routes
|
|
634
|
-
// Mark that it's in a private subnet
|
|
635
|
-
natGateway._isInPrivateSubnet = true;
|
|
636
|
-
return natGateway;
|
|
637
|
-
} else {
|
|
638
|
-
console.warn(
|
|
639
|
-
'NAT Gateways MUST be placed in public subnets with Internet Gateway routes'
|
|
640
|
-
);
|
|
641
|
-
console.warn(
|
|
642
|
-
'Skipping this misconfigured NAT Gateway...'
|
|
643
|
-
);
|
|
644
|
-
continue; // Skip non-Frigg NAT Gateways in private subnets
|
|
645
|
-
}
|
|
646
|
-
}
|
|
296
|
+
if (isPrivate) {
|
|
297
|
+
console.warn(
|
|
298
|
+
`WARNING: NAT Gateway ${natGateway.NatGatewayId} is in subnet ${subnetId} which appears to be private`
|
|
299
|
+
);
|
|
647
300
|
|
|
648
301
|
if (isFriggNat) {
|
|
649
|
-
console.
|
|
650
|
-
|
|
302
|
+
console.warn(
|
|
303
|
+
'This is a Frigg-managed NAT Gateway that may have been misconfigured by route table changes'
|
|
304
|
+
);
|
|
305
|
+
console.warn(
|
|
306
|
+
'Consider enabling selfHeal: true to fix this automatically'
|
|
651
307
|
);
|
|
652
|
-
natGateway._isInPrivateSubnet =
|
|
308
|
+
natGateway._isInPrivateSubnet = true;
|
|
653
309
|
return natGateway;
|
|
654
310
|
}
|
|
655
311
|
|
|
656
|
-
|
|
312
|
+
console.warn(
|
|
313
|
+
'NAT Gateways MUST be placed in public subnets with Internet Gateway routes'
|
|
314
|
+
);
|
|
315
|
+
console.warn('Skipping this misconfigured NAT Gateway...');
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (isFriggNat) {
|
|
657
320
|
console.log(
|
|
658
|
-
`Found existing NAT Gateway
|
|
321
|
+
`Found existing Frigg-managed NAT Gateway: ${natGateway.NatGatewayId} (State: ${natGateway.State})`
|
|
659
322
|
);
|
|
660
323
|
natGateway._isInPrivateSubnet = false;
|
|
661
324
|
return natGateway;
|
|
662
325
|
}
|
|
663
326
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
`ERROR: Found ${response.NatGateways.length} NAT Gateway(s) but all non-Frigg ones are in private subnets!`
|
|
667
|
-
);
|
|
668
|
-
console.error(
|
|
669
|
-
'These NAT Gateways will not provide internet connectivity without route table fixes'
|
|
670
|
-
);
|
|
671
|
-
console.error(
|
|
672
|
-
'Enable selfHeal: true to fix automatically or create a new NAT Gateway'
|
|
327
|
+
console.log(
|
|
328
|
+
`Found existing NAT Gateway in public subnet: ${natGateway.NatGatewayId} (State: ${natGateway.State})`
|
|
673
329
|
);
|
|
674
|
-
|
|
330
|
+
natGateway._isInPrivateSubnet = false;
|
|
331
|
+
return natGateway;
|
|
675
332
|
}
|
|
676
333
|
|
|
334
|
+
console.error(
|
|
335
|
+
`ERROR: Found ${(response.NatGateways || []).length} NAT Gateway(s) but all non-Frigg ones are in private subnets!`
|
|
336
|
+
);
|
|
337
|
+
console.error(
|
|
338
|
+
'These NAT Gateways will not provide internet connectivity without route table fixes'
|
|
339
|
+
);
|
|
340
|
+
console.error(
|
|
341
|
+
'Enable selfHeal: true to fix automatically or create a new NAT Gateway'
|
|
342
|
+
);
|
|
677
343
|
return null;
|
|
678
344
|
} catch (error) {
|
|
679
345
|
console.warn('Error finding existing NAT Gateway:', error.message);
|
|
@@ -681,17 +347,12 @@ class AWSDiscovery {
|
|
|
681
347
|
}
|
|
682
348
|
}
|
|
683
349
|
|
|
684
|
-
/**
|
|
685
|
-
* Find available Elastic IPs
|
|
686
|
-
* @returns {Promise<Object|null>} Available EIP object or null if none found
|
|
687
|
-
*/
|
|
688
350
|
async findAvailableElasticIP() {
|
|
689
351
|
try {
|
|
690
352
|
const command = new DescribeAddressesCommand({});
|
|
691
353
|
const response = await this.ec2Client.send(command);
|
|
692
354
|
|
|
693
355
|
if (response.Addresses && response.Addresses.length > 0) {
|
|
694
|
-
// Find an unassociated EIP first
|
|
695
356
|
const availableEIP = response.Addresses.find(
|
|
696
357
|
(eip) =>
|
|
697
358
|
!eip.AssociationId &&
|
|
@@ -706,15 +367,8 @@ class AWSDiscovery {
|
|
|
706
367
|
return availableEIP;
|
|
707
368
|
}
|
|
708
369
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
(eip) =>
|
|
712
|
-
eip.Tags &&
|
|
713
|
-
eip.Tags.some(
|
|
714
|
-
(tag) =>
|
|
715
|
-
tag.Key === 'Name' &&
|
|
716
|
-
tag.Value.includes('frigg')
|
|
717
|
-
)
|
|
370
|
+
const friggEIP = response.Addresses.find((eip) =>
|
|
371
|
+
this._isFriggManaged(eip.Tags)
|
|
718
372
|
);
|
|
719
373
|
|
|
720
374
|
if (friggEIP) {
|
|
@@ -732,14 +386,9 @@ class AWSDiscovery {
|
|
|
732
386
|
}
|
|
733
387
|
}
|
|
734
388
|
|
|
735
|
-
/**
|
|
736
|
-
* Find the default KMS key for the account
|
|
737
|
-
* @returns {Promise<string|null>} KMS key ARN or null if no key found
|
|
738
|
-
*/
|
|
739
389
|
async findDefaultKmsKey() {
|
|
740
390
|
console.log('KMS Discovery Starting...');
|
|
741
391
|
try {
|
|
742
|
-
// Log AWS account and region info for verification
|
|
743
392
|
console.log(`[KMS Discovery] Running in region: ${this.region}`);
|
|
744
393
|
try {
|
|
745
394
|
const accountId = await this.getAccountId();
|
|
@@ -767,7 +416,6 @@ class AWSDiscovery {
|
|
|
767
416
|
let enabledKeys = 0;
|
|
768
417
|
let pendingDeletionKeys = 0;
|
|
769
418
|
|
|
770
|
-
// Look for customer managed keys first
|
|
771
419
|
for (const key of response.Keys) {
|
|
772
420
|
try {
|
|
773
421
|
const describeCommand = new DescribeKeyCommand({
|
|
@@ -778,58 +426,50 @@ class AWSDiscovery {
|
|
|
778
426
|
);
|
|
779
427
|
keysExamined++;
|
|
780
428
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
metadata.DeletionDate
|
|
823
|
-
) {
|
|
824
|
-
// This shouldn't happen according to AWS docs, but log it if it does
|
|
825
|
-
console.error(
|
|
826
|
-
`[KMS Discovery] WARNING: Key ${key.KeyId} has KeyState='Enabled' but DeletionDate is set: ${metadata.DeletionDate}`
|
|
827
|
-
);
|
|
828
|
-
}
|
|
429
|
+
const metadata = keyDetails.KeyMetadata;
|
|
430
|
+
if (!metadata) {
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
console.log(`[KMS Discovery] Key ${key.KeyId}:`, {
|
|
435
|
+
KeyManager: metadata.KeyManager,
|
|
436
|
+
KeyState: metadata.KeyState,
|
|
437
|
+
Enabled: metadata.Enabled,
|
|
438
|
+
DeletionDate:
|
|
439
|
+
metadata.DeletionDate || 'Not scheduled for deletion',
|
|
440
|
+
Arn: metadata.Arn,
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
if (metadata.KeyManager === 'CUSTOMER') {
|
|
444
|
+
customerManagedKeys++;
|
|
445
|
+
|
|
446
|
+
if (metadata.KeyState === 'Enabled') {
|
|
447
|
+
enabledKeys++;
|
|
448
|
+
} else if (metadata.KeyState === 'PendingDeletion') {
|
|
449
|
+
pendingDeletionKeys++;
|
|
450
|
+
console.warn(
|
|
451
|
+
`[KMS Discovery] Skipping key ${key.KeyId} - State: PendingDeletion, DeletionDate: ${metadata.DeletionDate}`
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (
|
|
456
|
+
metadata.KeyState === 'Enabled' &&
|
|
457
|
+
!metadata.DeletionDate
|
|
458
|
+
) {
|
|
459
|
+
console.log(
|
|
460
|
+
`[KMS Discovery] Found eligible customer managed KMS key: ${metadata.Arn}`
|
|
461
|
+
);
|
|
462
|
+
return metadata.Arn;
|
|
463
|
+
} else if (
|
|
464
|
+
metadata.KeyState === 'Enabled' &&
|
|
465
|
+
metadata.DeletionDate
|
|
466
|
+
) {
|
|
467
|
+
console.error(
|
|
468
|
+
`[KMS Discovery] WARNING: Key ${key.KeyId} has KeyState='Enabled' but DeletionDate is set: ${metadata.DeletionDate}`
|
|
469
|
+
);
|
|
829
470
|
}
|
|
830
471
|
}
|
|
831
472
|
} catch (error) {
|
|
832
|
-
// Continue to next key if we can't describe this one
|
|
833
473
|
console.warn(
|
|
834
474
|
`[KMS Discovery] Could not describe key ${key.KeyId}:`,
|
|
835
475
|
error.message
|
|
@@ -838,13 +478,12 @@ class AWSDiscovery {
|
|
|
838
478
|
}
|
|
839
479
|
}
|
|
840
480
|
|
|
841
|
-
// Summary logging
|
|
842
481
|
console.log('[KMS Discovery] Summary:', {
|
|
843
482
|
totalKeys: response.Keys.length,
|
|
844
|
-
keysExamined
|
|
845
|
-
customerManagedKeys
|
|
846
|
-
enabledKeys
|
|
847
|
-
pendingDeletionKeys
|
|
483
|
+
keysExamined,
|
|
484
|
+
customerManagedKeys,
|
|
485
|
+
enabledKeys,
|
|
486
|
+
pendingDeletionKeys,
|
|
848
487
|
});
|
|
849
488
|
|
|
850
489
|
if (customerManagedKeys === 0) {
|
|
@@ -871,89 +510,6 @@ class AWSDiscovery {
|
|
|
871
510
|
}
|
|
872
511
|
}
|
|
873
512
|
|
|
874
|
-
/**
|
|
875
|
-
* Find Frigg-managed resources by tags
|
|
876
|
-
* @param {string} vpcId - The VPC ID to search within
|
|
877
|
-
* @returns {Promise<Object>} Object containing Frigg-managed resources
|
|
878
|
-
*/
|
|
879
|
-
async findFriggManagedResources(vpcId) {
|
|
880
|
-
try {
|
|
881
|
-
const resources = {
|
|
882
|
-
natGateways: [],
|
|
883
|
-
elasticIps: [],
|
|
884
|
-
subnets: [],
|
|
885
|
-
routeTables: [],
|
|
886
|
-
};
|
|
887
|
-
|
|
888
|
-
// Find NAT Gateways with Frigg tags
|
|
889
|
-
const natCommand = new DescribeNatGatewaysCommand({
|
|
890
|
-
Filter: [
|
|
891
|
-
{ Name: 'vpc-id', Values: [vpcId] },
|
|
892
|
-
{ Name: 'state', Values: ['available'] },
|
|
893
|
-
],
|
|
894
|
-
});
|
|
895
|
-
const natResponse = await this.ec2Client.send(natCommand);
|
|
896
|
-
|
|
897
|
-
if (natResponse.NatGateways) {
|
|
898
|
-
resources.natGateways = natResponse.NatGateways.filter(
|
|
899
|
-
(nat) =>
|
|
900
|
-
nat.Tags &&
|
|
901
|
-
nat.Tags.some(
|
|
902
|
-
(tag) =>
|
|
903
|
-
tag.Key === 'ManagedBy' && tag.Value === 'Frigg'
|
|
904
|
-
)
|
|
905
|
-
);
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
// Find Elastic IPs with Frigg tags
|
|
909
|
-
const eipCommand = new DescribeAddressesCommand({});
|
|
910
|
-
const eipResponse = await this.ec2Client.send(eipCommand);
|
|
911
|
-
|
|
912
|
-
if (eipResponse.Addresses) {
|
|
913
|
-
resources.elasticIps = eipResponse.Addresses.filter(
|
|
914
|
-
(eip) =>
|
|
915
|
-
eip.Tags &&
|
|
916
|
-
eip.Tags.some(
|
|
917
|
-
(tag) =>
|
|
918
|
-
tag.Key === 'ManagedBy' && tag.Value === 'Frigg'
|
|
919
|
-
)
|
|
920
|
-
);
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
// Find Route Tables with Frigg tags
|
|
924
|
-
const rtCommand = new DescribeRouteTablesCommand({
|
|
925
|
-
Filters: [{ Name: 'vpc-id', Values: [vpcId] }],
|
|
926
|
-
});
|
|
927
|
-
const rtResponse = await this.ec2Client.send(rtCommand);
|
|
928
|
-
|
|
929
|
-
if (rtResponse.RouteTables) {
|
|
930
|
-
resources.routeTables = rtResponse.RouteTables.filter(
|
|
931
|
-
(rt) =>
|
|
932
|
-
rt.Tags &&
|
|
933
|
-
rt.Tags.some(
|
|
934
|
-
(tag) =>
|
|
935
|
-
tag.Key === 'ManagedBy' && tag.Value === 'Frigg'
|
|
936
|
-
)
|
|
937
|
-
);
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
return resources;
|
|
941
|
-
} catch (error) {
|
|
942
|
-
console.error('Error finding Frigg-managed resources:', error);
|
|
943
|
-
return {
|
|
944
|
-
natGateways: [],
|
|
945
|
-
elasticIps: [],
|
|
946
|
-
subnets: [],
|
|
947
|
-
routeTables: [],
|
|
948
|
-
};
|
|
949
|
-
}
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
/**
|
|
953
|
-
* Detect misconfigured resources that need healing
|
|
954
|
-
* @param {string} vpcId - The VPC ID to check
|
|
955
|
-
* @returns {Promise<Object>} Object containing misconfiguration details
|
|
956
|
-
*/
|
|
957
513
|
async detectMisconfiguredResources(vpcId) {
|
|
958
514
|
try {
|
|
959
515
|
const misconfigurations = {
|
|
@@ -963,7 +519,6 @@ class AWSDiscovery {
|
|
|
963
519
|
privateSubnetsWithoutNatRoute: [],
|
|
964
520
|
};
|
|
965
521
|
|
|
966
|
-
// Find NAT Gateways in private subnets
|
|
967
522
|
const natCommand = new DescribeNatGatewaysCommand({
|
|
968
523
|
Filter: [
|
|
969
524
|
{ Name: 'vpc-id', Values: [vpcId] },
|
|
@@ -972,75 +527,52 @@ class AWSDiscovery {
|
|
|
972
527
|
});
|
|
973
528
|
const natResponse = await this.ec2Client.send(natCommand);
|
|
974
529
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
});
|
|
984
|
-
}
|
|
530
|
+
for (const nat of natResponse.NatGateways || []) {
|
|
531
|
+
const isPrivate = await this.isSubnetPrivate(nat.SubnetId, vpcId);
|
|
532
|
+
if (isPrivate) {
|
|
533
|
+
misconfigurations.natGatewaysInPrivateSubnets.push({
|
|
534
|
+
natGatewayId: nat.NatGatewayId,
|
|
535
|
+
subnetId: nat.SubnetId,
|
|
536
|
+
tags: nat.Tags,
|
|
537
|
+
});
|
|
985
538
|
}
|
|
986
539
|
}
|
|
987
540
|
|
|
988
|
-
// Find orphaned Elastic IPs
|
|
989
541
|
const eipCommand = new DescribeAddressesCommand({});
|
|
990
542
|
const eipResponse = await this.ec2Client.send(eipCommand);
|
|
991
543
|
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
tag.Key === 'ManagedBy' &&
|
|
1005
|
-
tag.Value === 'Frigg'
|
|
1006
|
-
);
|
|
1007
|
-
if (isFriggManaged) {
|
|
1008
|
-
misconfigurations.orphanedElasticIps.push({
|
|
1009
|
-
allocationId: eip.AllocationId,
|
|
1010
|
-
publicIp: eip.PublicIp,
|
|
1011
|
-
tags: eip.Tags,
|
|
1012
|
-
});
|
|
1013
|
-
}
|
|
1014
|
-
}
|
|
544
|
+
for (const eip of eipResponse.Addresses || []) {
|
|
545
|
+
if (
|
|
546
|
+
!eip.InstanceId &&
|
|
547
|
+
!eip.NetworkInterfaceId &&
|
|
548
|
+
!eip.AssociationId &&
|
|
549
|
+
this._isFriggManaged(eip.Tags)
|
|
550
|
+
) {
|
|
551
|
+
misconfigurations.orphanedElasticIps.push({
|
|
552
|
+
allocationId: eip.AllocationId,
|
|
553
|
+
publicIp: eip.PublicIp,
|
|
554
|
+
tags: eip.Tags,
|
|
555
|
+
});
|
|
1015
556
|
}
|
|
1016
557
|
}
|
|
1017
558
|
|
|
1018
|
-
// Find private subnets without NAT route
|
|
1019
559
|
const subnets = await this.findPrivateSubnets(vpcId);
|
|
1020
560
|
const routeTables = await this.findRouteTables(vpcId);
|
|
1021
561
|
|
|
1022
562
|
for (const subnet of subnets) {
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
rt.Associations.some(
|
|
1030
|
-
(assoc) => assoc.SubnetId === subnet.SubnetId
|
|
1031
|
-
);
|
|
1032
|
-
|
|
1033
|
-
if (isAssociated) {
|
|
1034
|
-
hasNatRoute =
|
|
1035
|
-
rt.Routes &&
|
|
1036
|
-
rt.Routes.some(
|
|
1037
|
-
(route) =>
|
|
1038
|
-
route.NatGatewayId &&
|
|
1039
|
-
route.DestinationCidrBlock === '0.0.0.0/0'
|
|
1040
|
-
);
|
|
1041
|
-
break;
|
|
563
|
+
const hasNatRoute = routeTables.some((rt) => {
|
|
564
|
+
const isAssociated = (rt.Associations || []).some(
|
|
565
|
+
(assoc) => assoc.SubnetId === subnet.SubnetId
|
|
566
|
+
);
|
|
567
|
+
if (!isAssociated) {
|
|
568
|
+
return false;
|
|
1042
569
|
}
|
|
1043
|
-
|
|
570
|
+
return (rt.Routes || []).some(
|
|
571
|
+
(route) =>
|
|
572
|
+
route.NatGatewayId &&
|
|
573
|
+
route.DestinationCidrBlock === '0.0.0.0/0'
|
|
574
|
+
);
|
|
575
|
+
});
|
|
1044
576
|
|
|
1045
577
|
if (!hasNatRoute) {
|
|
1046
578
|
misconfigurations.privateSubnetsWithoutNatRoute.push({
|
|
@@ -1062,11 +594,6 @@ class AWSDiscovery {
|
|
|
1062
594
|
}
|
|
1063
595
|
}
|
|
1064
596
|
|
|
1065
|
-
/**
|
|
1066
|
-
* Get healing recommendations based on detected issues
|
|
1067
|
-
* @param {Object} misconfigurations - Object from detectMisconfiguredResources
|
|
1068
|
-
* @returns {Array} Array of healing recommendations
|
|
1069
|
-
*/
|
|
1070
597
|
getHealingRecommendations(misconfigurations) {
|
|
1071
598
|
const recommendations = [];
|
|
1072
599
|
|
|
@@ -1107,7 +634,6 @@ class AWSDiscovery {
|
|
|
1107
634
|
});
|
|
1108
635
|
}
|
|
1109
636
|
|
|
1110
|
-
// Sort by severity
|
|
1111
637
|
recommendations.sort((a, b) => {
|
|
1112
638
|
const severityOrder = { critical: 0, warning: 1, info: 2 };
|
|
1113
639
|
return severityOrder[a.severity] - severityOrder[b.severity];
|
|
@@ -1116,18 +642,6 @@ class AWSDiscovery {
|
|
|
1116
642
|
return recommendations;
|
|
1117
643
|
}
|
|
1118
644
|
|
|
1119
|
-
/**
|
|
1120
|
-
* Discover all AWS resources needed for Frigg deployment
|
|
1121
|
-
* @returns {Promise<Object>} Object containing discovered resource IDs:
|
|
1122
|
-
* @returns {string} return.defaultVpcId - The default VPC ID
|
|
1123
|
-
* @returns {string} return.defaultSecurityGroupId - The default security group ID
|
|
1124
|
-
* @returns {string} return.privateSubnetId1 - First private subnet ID
|
|
1125
|
-
* @returns {string} return.privateSubnetId2 - Second private subnet ID
|
|
1126
|
-
* @returns {string} return.publicSubnetId - Public subnet ID for NAT Gateway
|
|
1127
|
-
* @returns {string} return.privateRouteTableId - Private route table ID
|
|
1128
|
-
* @returns {string|null} return.defaultKmsKeyId - Default KMS key ARN or null if not found
|
|
1129
|
-
* @throws {Error} If resource discovery fails
|
|
1130
|
-
*/
|
|
1131
645
|
async discoverResources(options = {}) {
|
|
1132
646
|
try {
|
|
1133
647
|
console.log(
|
|
@@ -1138,7 +652,6 @@ class AWSDiscovery {
|
|
|
1138
652
|
const vpc = await this.findDefaultVpc();
|
|
1139
653
|
console.log(`\nā
Found VPC: ${vpc.VpcId}`);
|
|
1140
654
|
|
|
1141
|
-
// Enable auto-convert if selfHeal is enabled
|
|
1142
655
|
const autoConvert = options.selfHeal || false;
|
|
1143
656
|
|
|
1144
657
|
const privateSubnets = await this.findPrivateSubnets(
|
|
@@ -1177,7 +690,6 @@ class AWSDiscovery {
|
|
|
1177
690
|
console.log('ā¹ļø No KMS key found');
|
|
1178
691
|
}
|
|
1179
692
|
|
|
1180
|
-
// Try to find existing NAT Gateway
|
|
1181
693
|
const existingNatGateway = await this.findExistingNatGateway(
|
|
1182
694
|
vpc.VpcId
|
|
1183
695
|
);
|
|
@@ -1187,11 +699,9 @@ class AWSDiscovery {
|
|
|
1187
699
|
|
|
1188
700
|
if (existingNatGateway) {
|
|
1189
701
|
natGatewayId = existingNatGateway.NatGatewayId;
|
|
1190
|
-
// Check if NAT Gateway is in a private subnet (from our detection)
|
|
1191
702
|
natGatewayInPrivateSubnet =
|
|
1192
703
|
existingNatGateway._isInPrivateSubnet || false;
|
|
1193
704
|
|
|
1194
|
-
// Get the EIP allocation ID from the NAT Gateway
|
|
1195
705
|
if (
|
|
1196
706
|
existingNatGateway.NatGatewayAddresses &&
|
|
1197
707
|
existingNatGateway.NatGatewayAddresses.length > 0
|
|
@@ -1200,19 +710,23 @@ class AWSDiscovery {
|
|
|
1200
710
|
existingNatGateway.NatGatewayAddresses[0].AllocationId;
|
|
1201
711
|
}
|
|
1202
712
|
} else {
|
|
1203
|
-
// If no NAT Gateway exists, check for available EIP
|
|
1204
713
|
const availableEIP = await this.findAvailableElasticIP();
|
|
1205
714
|
if (availableEIP) {
|
|
1206
715
|
elasticIpAllocationId = availableEIP.AllocationId;
|
|
1207
716
|
}
|
|
1208
717
|
}
|
|
1209
718
|
|
|
1210
|
-
// Check if the "private" subnets are actually public
|
|
1211
719
|
const subnet1IsActuallyPrivate = privateSubnets[0]
|
|
1212
|
-
? await this.isSubnetPrivate(
|
|
720
|
+
? await this.isSubnetPrivate(
|
|
721
|
+
privateSubnets[0].SubnetId,
|
|
722
|
+
privateSubnets[0].VpcId || vpc.VpcId
|
|
723
|
+
)
|
|
1213
724
|
: false;
|
|
1214
725
|
const subnet2IsActuallyPrivate = privateSubnets[1]
|
|
1215
|
-
? await this.isSubnetPrivate(
|
|
726
|
+
? await this.isSubnetPrivate(
|
|
727
|
+
privateSubnets[1].SubnetId,
|
|
728
|
+
privateSubnets[1].VpcId || vpc.VpcId
|
|
729
|
+
)
|
|
1216
730
|
: subnet1IsActuallyPrivate;
|
|
1217
731
|
|
|
1218
732
|
const subnetStatus = {
|
|
@@ -1267,12 +781,12 @@ class AWSDiscovery {
|
|
|
1267
781
|
|
|
1268
782
|
return {
|
|
1269
783
|
defaultVpcId: vpc.VpcId,
|
|
1270
|
-
vpcCidr: vpc.CidrBlock,
|
|
784
|
+
vpcCidr: vpc.CidrBlock,
|
|
1271
785
|
defaultSecurityGroupId: securityGroup.GroupId,
|
|
1272
786
|
privateSubnetId1: privateSubnets[0]?.SubnetId,
|
|
1273
787
|
privateSubnetId2:
|
|
1274
788
|
privateSubnets[1]?.SubnetId || privateSubnets[0]?.SubnetId,
|
|
1275
|
-
publicSubnetId: publicSubnet?.SubnetId || null,
|
|
789
|
+
publicSubnetId: publicSubnet?.SubnetId || null,
|
|
1276
790
|
privateRouteTableId: routeTable.RouteTableId,
|
|
1277
791
|
defaultKmsKeyId: kmsKeyArn,
|
|
1278
792
|
existingNatGatewayId: natGatewayId,
|
|
@@ -1302,11 +816,6 @@ class AWSDiscovery {
|
|
|
1302
816
|
}
|
|
1303
817
|
}
|
|
1304
818
|
|
|
1305
|
-
/**
|
|
1306
|
-
* Find an existing Internet Gateway attached to the VPC
|
|
1307
|
-
* @param {string} vpcId - VPC ID to search in
|
|
1308
|
-
* @returns {Promise<Object|null>} Internet Gateway object or null if none found
|
|
1309
|
-
*/
|
|
1310
819
|
async findInternetGateway(vpcId) {
|
|
1311
820
|
try {
|
|
1312
821
|
const command = new DescribeInternetGatewaysCommand({
|
|
@@ -1341,24 +850,17 @@ class AWSDiscovery {
|
|
|
1341
850
|
}
|
|
1342
851
|
}
|
|
1343
852
|
|
|
1344
|
-
/**
|
|
1345
|
-
* Find Frigg-managed resources by tags
|
|
1346
|
-
* @param {string} serviceName - The service name to search for
|
|
1347
|
-
* @param {string} stage - The deployment stage
|
|
1348
|
-
* @returns {Promise<Object>} Object containing found Frigg-managed resources
|
|
1349
|
-
*/
|
|
1350
853
|
async findFriggManagedResources(serviceName, stage) {
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
};
|
|
854
|
+
const results = {
|
|
855
|
+
natGateways: [],
|
|
856
|
+
elasticIps: [],
|
|
857
|
+
routeTables: [],
|
|
858
|
+
subnets: [],
|
|
859
|
+
securityGroups: [],
|
|
860
|
+
};
|
|
1359
861
|
|
|
1360
|
-
|
|
1361
|
-
const
|
|
862
|
+
try {
|
|
863
|
+
const filters = [
|
|
1362
864
|
{
|
|
1363
865
|
Name: 'tag:ManagedBy',
|
|
1364
866
|
Values: ['Frigg'],
|
|
@@ -1366,82 +868,76 @@ class AWSDiscovery {
|
|
|
1366
868
|
];
|
|
1367
869
|
|
|
1368
870
|
if (serviceName) {
|
|
1369
|
-
|
|
871
|
+
filters.push({
|
|
1370
872
|
Name: 'tag:Service',
|
|
1371
873
|
Values: [serviceName],
|
|
1372
874
|
});
|
|
1373
875
|
}
|
|
1374
876
|
|
|
1375
877
|
if (stage) {
|
|
1376
|
-
|
|
878
|
+
filters.push({
|
|
1377
879
|
Name: 'tag:Stage',
|
|
1378
880
|
Values: [stage],
|
|
1379
881
|
});
|
|
1380
882
|
}
|
|
1381
883
|
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
884
|
+
const fetchWithFallback = async (Command, input, field, label) => {
|
|
885
|
+
try {
|
|
886
|
+
const response = await this.ec2Client.send(
|
|
887
|
+
new Command(input)
|
|
888
|
+
);
|
|
889
|
+
return response[field] || [];
|
|
890
|
+
} catch (err) {
|
|
891
|
+
console.warn(
|
|
892
|
+
`Error finding Frigg ${label}:`,
|
|
893
|
+
err.message
|
|
894
|
+
);
|
|
895
|
+
return [];
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
results.natGateways = await fetchWithFallback(
|
|
900
|
+
DescribeNatGatewaysCommand,
|
|
901
|
+
{
|
|
1385
902
|
Filter: [
|
|
1386
|
-
...
|
|
903
|
+
...filters,
|
|
1387
904
|
{
|
|
1388
905
|
Name: 'state',
|
|
1389
906
|
Values: ['available'],
|
|
1390
907
|
},
|
|
1391
908
|
],
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
console.warn('Error finding Frigg NAT Gateways:', err.message);
|
|
1397
|
-
}
|
|
909
|
+
},
|
|
910
|
+
'NatGateways',
|
|
911
|
+
'NAT Gateways'
|
|
912
|
+
);
|
|
1398
913
|
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
results.elasticIps = eipResponse.Addresses || [];
|
|
1406
|
-
} catch (err) {
|
|
1407
|
-
console.warn('Error finding Frigg Elastic IPs:', err.message);
|
|
1408
|
-
}
|
|
914
|
+
results.elasticIps = await fetchWithFallback(
|
|
915
|
+
DescribeAddressesCommand,
|
|
916
|
+
{ Filters: filters },
|
|
917
|
+
'Addresses',
|
|
918
|
+
'Elastic IPs'
|
|
919
|
+
);
|
|
1409
920
|
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
results.routeTables = rtResponse.RouteTables || [];
|
|
1417
|
-
} catch (err) {
|
|
1418
|
-
console.warn('Error finding Frigg Route Tables:', err.message);
|
|
1419
|
-
}
|
|
921
|
+
results.routeTables = await fetchWithFallback(
|
|
922
|
+
DescribeRouteTablesCommand,
|
|
923
|
+
{ Filters: filters },
|
|
924
|
+
'RouteTables',
|
|
925
|
+
'Route Tables'
|
|
926
|
+
);
|
|
1420
927
|
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
results.subnets = subnetResponse.Subnets || [];
|
|
1428
|
-
} catch (err) {
|
|
1429
|
-
console.warn('Error finding Frigg Subnets:', err.message);
|
|
1430
|
-
}
|
|
928
|
+
results.subnets = await fetchWithFallback(
|
|
929
|
+
DescribeSubnetsCommand,
|
|
930
|
+
{ Filters: filters },
|
|
931
|
+
'Subnets',
|
|
932
|
+
'Subnets'
|
|
933
|
+
);
|
|
1431
934
|
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
results.securityGroups = sgResponse.SecurityGroups || [];
|
|
1439
|
-
} catch (err) {
|
|
1440
|
-
console.warn(
|
|
1441
|
-
'Error finding Frigg Security Groups:',
|
|
1442
|
-
err.message
|
|
1443
|
-
);
|
|
1444
|
-
}
|
|
935
|
+
results.securityGroups = await fetchWithFallback(
|
|
936
|
+
DescribeSecurityGroupsCommand,
|
|
937
|
+
{ Filters: filters },
|
|
938
|
+
'SecurityGroups',
|
|
939
|
+
'Security Groups'
|
|
940
|
+
);
|
|
1445
941
|
|
|
1446
942
|
console.log('Found Frigg-managed resources:', {
|
|
1447
943
|
natGateways: results.natGateways.length,
|
|
@@ -1454,14 +950,226 @@ class AWSDiscovery {
|
|
|
1454
950
|
return results;
|
|
1455
951
|
} catch (error) {
|
|
1456
952
|
console.error('Error finding Frigg-managed resources:', error);
|
|
1457
|
-
return
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
953
|
+
return results;
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
async findRouteTables(vpcId) {
|
|
958
|
+
const command = new DescribeRouteTablesCommand({
|
|
959
|
+
Filters: [
|
|
960
|
+
{
|
|
961
|
+
Name: 'vpc-id',
|
|
962
|
+
Values: [vpcId],
|
|
963
|
+
},
|
|
964
|
+
],
|
|
965
|
+
});
|
|
966
|
+
const response = await this.ec2Client.send(command);
|
|
967
|
+
return response.RouteTables || [];
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
async _fetchSubnets(vpcId) {
|
|
971
|
+
const command = new DescribeSubnetsCommand({
|
|
972
|
+
Filters: [
|
|
973
|
+
{
|
|
974
|
+
Name: 'vpc-id',
|
|
975
|
+
Values: [vpcId],
|
|
976
|
+
},
|
|
977
|
+
],
|
|
978
|
+
});
|
|
979
|
+
const response = await this.ec2Client.send(command);
|
|
980
|
+
return response.Subnets || [];
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
async _getSubnetVpcId(subnetId) {
|
|
984
|
+
const command = new DescribeSubnetsCommand({
|
|
985
|
+
SubnetIds: [subnetId],
|
|
986
|
+
});
|
|
987
|
+
const response = await this.ec2Client.send(command);
|
|
988
|
+
|
|
989
|
+
if (!response.Subnets || response.Subnets.length === 0) {
|
|
990
|
+
throw new Error(`Subnet ${subnetId} not found`);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
return response.Subnets[0].VpcId;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
async _classifySubnets(subnets, { logDetails = false } = {}) {
|
|
997
|
+
const privateSubnets = [];
|
|
998
|
+
const publicSubnets = [];
|
|
999
|
+
|
|
1000
|
+
for (const subnet of subnets) {
|
|
1001
|
+
const isPrivate = await this.isSubnetPrivate(
|
|
1002
|
+
subnet.SubnetId,
|
|
1003
|
+
subnet.VpcId
|
|
1004
|
+
);
|
|
1005
|
+
if (isPrivate) {
|
|
1006
|
+
privateSubnets.push(subnet);
|
|
1007
|
+
if (logDetails) {
|
|
1008
|
+
console.log(
|
|
1009
|
+
` š Private subnet: ${subnet.SubnetId} (AZ: ${subnet.AvailabilityZone})`
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
1012
|
+
} else {
|
|
1013
|
+
publicSubnets.push(subnet);
|
|
1014
|
+
if (logDetails) {
|
|
1015
|
+
console.log(
|
|
1016
|
+
` š Public subnet: ${subnet.SubnetId} (AZ: ${subnet.AvailabilityZone})`
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
return { privateSubnets, publicSubnets };
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
_logSubnetSummary(privateCount, publicCount) {
|
|
1026
|
+
console.log(`\nš Subnet Analysis Results:`);
|
|
1027
|
+
console.log(` - Private subnets: ${privateCount}`);
|
|
1028
|
+
console.log(` - Public subnets: ${publicCount}`);
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
_selectSubnetsForLambda({ privateSubnets, publicSubnets, autoConvert, vpcId }) {
|
|
1032
|
+
if (privateSubnets.length >= 2) {
|
|
1033
|
+
console.log(
|
|
1034
|
+
`ā
Found ${privateSubnets.length} private subnets for Lambda deployment`
|
|
1035
|
+
);
|
|
1036
|
+
return privateSubnets.slice(0, 2);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
if (privateSubnets.length === 1) {
|
|
1040
|
+
console.warn(
|
|
1041
|
+
`ā ļø Only 1 private subnet found. Need at least 2 for high availability.`
|
|
1042
|
+
);
|
|
1043
|
+
if (publicSubnets.length > 0 && autoConvert) {
|
|
1044
|
+
console.log(
|
|
1045
|
+
`š Will convert 1 public subnet to private for high availability...`
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
return [...privateSubnets, ...publicSubnets].slice(0, 2);
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
if (privateSubnets.length === 0 && publicSubnets.length > 0) {
|
|
1052
|
+
console.error(
|
|
1053
|
+
`ā CRITICAL: No private subnets found, but ${publicSubnets.length} public subnets exist`
|
|
1054
|
+
);
|
|
1055
|
+
console.error(
|
|
1056
|
+
`ā Lambda functions should NOT be deployed in public subnets!`
|
|
1057
|
+
);
|
|
1058
|
+
|
|
1059
|
+
if (autoConvert && publicSubnets.length >= 3) {
|
|
1060
|
+
console.log(
|
|
1061
|
+
`\nš§ AUTO-CONVERSION: Will configure subnets for proper isolation...`
|
|
1062
|
+
);
|
|
1063
|
+
console.log(
|
|
1064
|
+
` - Keeping ${publicSubnets[0].SubnetId} as public (for NAT Gateway)`
|
|
1065
|
+
);
|
|
1066
|
+
console.log(
|
|
1067
|
+
` - Converting ${publicSubnets[1].SubnetId} to private (for Lambda)`
|
|
1068
|
+
);
|
|
1069
|
+
if (publicSubnets[2]) {
|
|
1070
|
+
console.log(
|
|
1071
|
+
` - Converting ${publicSubnets[2].SubnetId} to private (for Lambda)`
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
return publicSubnets.slice(1, 3);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
if (autoConvert && publicSubnets.length >= 2) {
|
|
1078
|
+
console.log(
|
|
1079
|
+
`\nš§ AUTO-CONVERSION: Only ${publicSubnets.length} subnets available`
|
|
1080
|
+
);
|
|
1081
|
+
console.log(
|
|
1082
|
+
` - Will need to create new subnets or reconfigure existing ones`
|
|
1083
|
+
);
|
|
1084
|
+
return publicSubnets.slice(0, 2);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
console.error(`\nā ļø CONFIGURATION ERROR:`);
|
|
1088
|
+
console.error(
|
|
1089
|
+
` Found ${publicSubnets.length} public subnets but no private subnets.`
|
|
1090
|
+
);
|
|
1091
|
+
console.error(
|
|
1092
|
+
` Lambda functions require private subnets for security.`
|
|
1093
|
+
);
|
|
1094
|
+
console.error(`\n Options:`);
|
|
1095
|
+
console.error(
|
|
1096
|
+
` 1. Enable selfHeal: true in vpc configuration`
|
|
1097
|
+
);
|
|
1098
|
+
console.error(` 2. Create private subnets manually`);
|
|
1099
|
+
console.error(
|
|
1100
|
+
` 3. Set subnets.management: 'create' to create new private subnets`
|
|
1101
|
+
);
|
|
1102
|
+
|
|
1103
|
+
throw new Error(
|
|
1104
|
+
`No private subnets found in VPC ${vpcId}. ` +
|
|
1105
|
+
`Found ${publicSubnets.length} public subnets. ` +
|
|
1106
|
+
`Lambda requires private subnets. Enable selfHeal or create private subnets.`
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
return null;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
_findRouteTableForSubnet(routeTables, subnetId) {
|
|
1114
|
+
for (const rt of routeTables) {
|
|
1115
|
+
for (const assoc of rt.Associations || []) {
|
|
1116
|
+
if (assoc.SubnetId === subnetId) {
|
|
1117
|
+
return rt;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
for (const rt of routeTables) {
|
|
1123
|
+
for (const assoc of rt.Associations || []) {
|
|
1124
|
+
if (assoc.Main === true) {
|
|
1125
|
+
return rt;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
return null;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
_findIgwRoute(routeTable) {
|
|
1134
|
+
for (const route of routeTable.Routes || []) {
|
|
1135
|
+
if (route.GatewayId && route.GatewayId.startsWith('igw-')) {
|
|
1136
|
+
return route.GatewayId;
|
|
1137
|
+
}
|
|
1464
1138
|
}
|
|
1139
|
+
return null;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
_isFriggManaged(tags) {
|
|
1143
|
+
if (!tags) {
|
|
1144
|
+
return false;
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
return tags.some(
|
|
1148
|
+
(tag) =>
|
|
1149
|
+
(tag.Key === 'ManagedBy' && tag.Value === 'Frigg') ||
|
|
1150
|
+
(tag.Key === 'Name' &&
|
|
1151
|
+
typeof tag.Value === 'string' &&
|
|
1152
|
+
tag.Value.includes('frigg'))
|
|
1153
|
+
);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
async _findSecurityGroupByName(vpcId, groupName) {
|
|
1157
|
+
const command = new DescribeSecurityGroupsCommand({
|
|
1158
|
+
Filters: [
|
|
1159
|
+
{
|
|
1160
|
+
Name: 'vpc-id',
|
|
1161
|
+
Values: [vpcId],
|
|
1162
|
+
},
|
|
1163
|
+
{
|
|
1164
|
+
Name: 'group-name',
|
|
1165
|
+
Values: [groupName],
|
|
1166
|
+
},
|
|
1167
|
+
],
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
const response = await this.ec2Client.send(command);
|
|
1171
|
+
const groups = response.SecurityGroups || [];
|
|
1172
|
+
return groups[0] || null;
|
|
1465
1173
|
}
|
|
1466
1174
|
}
|
|
1467
1175
|
|