@friggframework/devtools 2.0.0--canary.428.1a6e465.0 → 2.0.0--canary.428.380d241.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/infrastructure/README.md +19 -8
- package/infrastructure/aws-discovery.test.js +38 -0
- package/infrastructure/serverless-template.js +1012 -1736
- package/package.json +6 -6
- package/infrastructure/AWS-DISCOVERY-TROUBLESHOOTING.md +0 -245
- package/infrastructure/AWS-IAM-CREDENTIAL-NEEDS.md +0 -627
- package/infrastructure/README-TESTING.md +0 -332
- package/infrastructure/test-subnet-logic.js +0 -49
|
@@ -2,28 +2,13 @@ const path = require('path');
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const { AWSDiscovery } = require('./aws-discovery');
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
*/
|
|
10
|
-
const shouldRunDiscovery = (AppDefinition) => {
|
|
11
|
-
return (
|
|
12
|
-
AppDefinition.vpc?.enable === true ||
|
|
13
|
-
AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms' ||
|
|
14
|
-
AppDefinition.ssm?.enable === true
|
|
15
|
-
);
|
|
16
|
-
};
|
|
5
|
+
const shouldRunDiscovery = (AppDefinition) =>
|
|
6
|
+
AppDefinition.vpc?.enable === true ||
|
|
7
|
+
AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms' ||
|
|
8
|
+
AppDefinition.ssm?.enable === true;
|
|
17
9
|
|
|
18
|
-
/**
|
|
19
|
-
* Extract environment variables from AppDefinition
|
|
20
|
-
* @param {Object} AppDefinition - Application definition
|
|
21
|
-
* @returns {Object} Environment variables to set in serverless
|
|
22
|
-
*/
|
|
23
10
|
const getAppEnvironmentVars = (AppDefinition) => {
|
|
24
11
|
const envVars = {};
|
|
25
|
-
|
|
26
|
-
// AWS Lambda reserved environment variables that cannot be set (from official AWS docs)
|
|
27
12
|
const reservedVars = new Set([
|
|
28
13
|
'_HANDLER',
|
|
29
14
|
'_X_AMZN_TRACE_ID',
|
|
@@ -42,115 +27,80 @@ const getAppEnvironmentVars = (AppDefinition) => {
|
|
|
42
27
|
'AWS_SESSION_TOKEN',
|
|
43
28
|
]);
|
|
44
29
|
|
|
45
|
-
if (AppDefinition.environment) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const skippedKeys = [];
|
|
30
|
+
if (!AppDefinition.environment) {
|
|
31
|
+
return envVars;
|
|
32
|
+
}
|
|
49
33
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
skippedKeys.push(key);
|
|
54
|
-
} else {
|
|
55
|
-
envVars[key] = `\${env:${key}, ''}`;
|
|
56
|
-
envKeys.push(key);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
34
|
+
console.log('📋 Loading environment variables from appDefinition...');
|
|
35
|
+
const envKeys = [];
|
|
36
|
+
const skippedKeys = [];
|
|
60
37
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
if (skippedKeys.length > 0) {
|
|
68
|
-
console.log(
|
|
69
|
-
` ⚠️ Skipped ${skippedKeys.length
|
|
70
|
-
} reserved AWS Lambda variables: ${skippedKeys.join(', ')}`
|
|
71
|
-
);
|
|
38
|
+
for (const [key, value] of Object.entries(AppDefinition.environment)) {
|
|
39
|
+
if (value !== true) continue;
|
|
40
|
+
if (reservedVars.has(key)) {
|
|
41
|
+
skippedKeys.push(key);
|
|
42
|
+
continue;
|
|
72
43
|
}
|
|
44
|
+
envVars[key] = `\${env:${key}, ''}`;
|
|
45
|
+
envKeys.push(key);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (envKeys.length > 0) {
|
|
49
|
+
console.log(` Found ${envKeys.length} environment variables: ${envKeys.join(', ')}`);
|
|
50
|
+
}
|
|
51
|
+
if (skippedKeys.length > 0) {
|
|
52
|
+
console.log(
|
|
53
|
+
` ⚠️ Skipped ${skippedKeys.length} reserved AWS Lambda variables: ${skippedKeys.join(', ')}`
|
|
54
|
+
);
|
|
73
55
|
}
|
|
74
56
|
|
|
75
57
|
return envVars;
|
|
76
58
|
};
|
|
77
59
|
|
|
78
|
-
/**
|
|
79
|
-
* Find the actual path to node_modules directory
|
|
80
|
-
* Tries multiple methods to locate node_modules:
|
|
81
|
-
* 1. Traversing up from current directory
|
|
82
|
-
* 2. Using npm root command
|
|
83
|
-
* 3. Looking for package.json and adjacent node_modules
|
|
84
|
-
* @returns {string} Path to node_modules directory
|
|
85
|
-
*/
|
|
86
60
|
const findNodeModulesPath = () => {
|
|
87
61
|
try {
|
|
88
|
-
// Method 1: Try to find node_modules by traversing up from current directory
|
|
89
62
|
let currentDir = process.cwd();
|
|
90
63
|
let nodeModulesPath = null;
|
|
91
64
|
|
|
92
|
-
// Traverse up to 5 levels to find node_modules
|
|
93
65
|
for (let i = 0; i < 5; i++) {
|
|
94
66
|
const potentialPath = path.join(currentDir, 'node_modules');
|
|
95
67
|
if (fs.existsSync(potentialPath)) {
|
|
96
68
|
nodeModulesPath = potentialPath;
|
|
97
|
-
console.log(
|
|
98
|
-
`Found node_modules at: ${nodeModulesPath} (method 1)`
|
|
99
|
-
);
|
|
69
|
+
console.log(`Found node_modules at: ${nodeModulesPath} (method 1)`);
|
|
100
70
|
break;
|
|
101
71
|
}
|
|
102
|
-
// Move up one directory
|
|
103
72
|
const parentDir = path.dirname(currentDir);
|
|
104
|
-
if (parentDir === currentDir)
|
|
105
|
-
// We've reached the root
|
|
106
|
-
break;
|
|
107
|
-
}
|
|
73
|
+
if (parentDir === currentDir) break;
|
|
108
74
|
currentDir = parentDir;
|
|
109
75
|
}
|
|
110
76
|
|
|
111
|
-
// Method 2: If method 1 fails, try using npm root command
|
|
112
77
|
if (!nodeModulesPath) {
|
|
113
78
|
try {
|
|
114
|
-
// This requires child_process, so let's require it here
|
|
115
79
|
const { execSync } = require('node:child_process');
|
|
116
|
-
const npmRoot = execSync('npm root', {
|
|
117
|
-
encoding: 'utf8',
|
|
118
|
-
}).trim();
|
|
80
|
+
const npmRoot = execSync('npm root', { encoding: 'utf8' }).trim();
|
|
119
81
|
if (fs.existsSync(npmRoot)) {
|
|
120
82
|
nodeModulesPath = npmRoot;
|
|
121
|
-
console.log(
|
|
122
|
-
`Found node_modules at: ${nodeModulesPath} (method 2)`
|
|
123
|
-
);
|
|
83
|
+
console.log(`Found node_modules at: ${nodeModulesPath} (method 2)`);
|
|
124
84
|
}
|
|
125
85
|
} catch (npmError) {
|
|
126
86
|
console.error('Error executing npm root:', npmError);
|
|
127
87
|
}
|
|
128
88
|
}
|
|
129
89
|
|
|
130
|
-
// Method 3: If all else fails, check for a package.json and assume node_modules is adjacent
|
|
131
90
|
if (!nodeModulesPath) {
|
|
132
91
|
currentDir = process.cwd();
|
|
133
92
|
for (let i = 0; i < 5; i++) {
|
|
134
93
|
const packageJsonPath = path.join(currentDir, 'package.json');
|
|
135
94
|
if (fs.existsSync(packageJsonPath)) {
|
|
136
|
-
const potentialNodeModules = path.join(
|
|
137
|
-
currentDir,
|
|
138
|
-
'node_modules'
|
|
139
|
-
);
|
|
95
|
+
const potentialNodeModules = path.join(currentDir, 'node_modules');
|
|
140
96
|
if (fs.existsSync(potentialNodeModules)) {
|
|
141
97
|
nodeModulesPath = potentialNodeModules;
|
|
142
|
-
console.log(
|
|
143
|
-
`Found node_modules at: ${nodeModulesPath} (method 3)`
|
|
144
|
-
);
|
|
98
|
+
console.log(`Found node_modules at: ${nodeModulesPath} (method 3)`);
|
|
145
99
|
break;
|
|
146
100
|
}
|
|
147
101
|
}
|
|
148
|
-
// Move up one directory
|
|
149
102
|
const parentDir = path.dirname(currentDir);
|
|
150
|
-
if (parentDir === currentDir)
|
|
151
|
-
// We've reached the root
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
103
|
+
if (parentDir === currentDir) break;
|
|
154
104
|
currentDir = parentDir;
|
|
155
105
|
}
|
|
156
106
|
}
|
|
@@ -159,9 +109,7 @@ const findNodeModulesPath = () => {
|
|
|
159
109
|
return nodeModulesPath;
|
|
160
110
|
}
|
|
161
111
|
|
|
162
|
-
console.warn(
|
|
163
|
-
'Could not find node_modules path, falling back to default'
|
|
164
|
-
);
|
|
112
|
+
console.warn('Could not find node_modules path, falling back to default');
|
|
165
113
|
return path.resolve(process.cwd(), '../node_modules');
|
|
166
114
|
} catch (error) {
|
|
167
115
|
console.error('Error finding node_modules path:', error);
|
|
@@ -169,14 +117,7 @@ const findNodeModulesPath = () => {
|
|
|
169
117
|
}
|
|
170
118
|
};
|
|
171
119
|
|
|
172
|
-
/**
|
|
173
|
-
* Modify handler paths to point to the correct node_modules location
|
|
174
|
-
* Only modifies paths when running in offline mode
|
|
175
|
-
* @param {Object} functions - Serverless functions configuration object
|
|
176
|
-
* @returns {Object} Modified functions object with updated handler paths
|
|
177
|
-
*/
|
|
178
120
|
const modifyHandlerPaths = (functions) => {
|
|
179
|
-
// Check if we're running in offline mode
|
|
180
121
|
const isOffline = process.argv.includes('offline');
|
|
181
122
|
console.log('isOffline', isOffline);
|
|
182
123
|
|
|
@@ -192,32 +133,17 @@ const modifyHandlerPaths = (functions) => {
|
|
|
192
133
|
console.log('functionName', functionName);
|
|
193
134
|
const functionDef = modifiedFunctions[functionName];
|
|
194
135
|
if (functionDef?.handler?.includes('node_modules/')) {
|
|
195
|
-
// Replace node_modules/ with the actual path to node_modules/
|
|
196
136
|
const relativePath = path.relative(process.cwd(), nodeModulesPath);
|
|
197
|
-
functionDef.handler = functionDef.handler.replace(
|
|
198
|
-
|
|
199
|
-
`${relativePath}/`
|
|
200
|
-
);
|
|
201
|
-
console.log(
|
|
202
|
-
`Updated handler for ${functionName}: ${functionDef.handler}`
|
|
203
|
-
);
|
|
137
|
+
functionDef.handler = functionDef.handler.replace('node_modules/', `${relativePath}/`);
|
|
138
|
+
console.log(`Updated handler for ${functionName}: ${functionDef.handler}`);
|
|
204
139
|
}
|
|
205
140
|
}
|
|
206
141
|
|
|
207
142
|
return modifiedFunctions;
|
|
208
143
|
};
|
|
209
144
|
|
|
210
|
-
/**
|
|
211
|
-
* Create VPC infrastructure resources for CloudFormation
|
|
212
|
-
* Creates VPC, subnets, NAT gateway, route tables, and security groups
|
|
213
|
-
* @param {Object} AppDefinition - Application definition object
|
|
214
|
-
* @param {Object} AppDefinition.vpc - VPC configuration
|
|
215
|
-
* @param {string} [AppDefinition.vpc.cidrBlock='10.0.0.0/16'] - CIDR block for VPC
|
|
216
|
-
* @returns {Object} CloudFormation resources for VPC infrastructure
|
|
217
|
-
*/
|
|
218
145
|
const createVPCInfrastructure = (AppDefinition) => {
|
|
219
146
|
const vpcResources = {
|
|
220
|
-
// VPC
|
|
221
147
|
FriggVPC: {
|
|
222
148
|
Type: 'AWS::EC2::VPC',
|
|
223
149
|
Properties: {
|
|
@@ -225,52 +151,24 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
225
151
|
EnableDnsHostnames: true,
|
|
226
152
|
EnableDnsSupport: true,
|
|
227
153
|
Tags: [
|
|
228
|
-
{
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
Key: 'ManagedBy',
|
|
234
|
-
Value: 'Frigg',
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
Key: 'Service',
|
|
238
|
-
Value: '${self:service}',
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
Key: 'Stage',
|
|
242
|
-
Value: '${self:provider.stage}',
|
|
243
|
-
},
|
|
154
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-vpc' },
|
|
155
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
156
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
157
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
244
158
|
],
|
|
245
159
|
},
|
|
246
160
|
},
|
|
247
|
-
|
|
248
|
-
// Internet Gateway
|
|
249
161
|
FriggInternetGateway: {
|
|
250
162
|
Type: 'AWS::EC2::InternetGateway',
|
|
251
163
|
Properties: {
|
|
252
164
|
Tags: [
|
|
253
|
-
{
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
},
|
|
257
|
-
{
|
|
258
|
-
Key: 'ManagedBy',
|
|
259
|
-
Value: 'Frigg',
|
|
260
|
-
},
|
|
261
|
-
{
|
|
262
|
-
Key: 'Service',
|
|
263
|
-
Value: '${self:service}',
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
Key: 'Stage',
|
|
267
|
-
Value: '${self:provider.stage}',
|
|
268
|
-
},
|
|
165
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-igw' },
|
|
166
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
167
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
168
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
269
169
|
],
|
|
270
170
|
},
|
|
271
171
|
},
|
|
272
|
-
|
|
273
|
-
// Attach Internet Gateway to VPC
|
|
274
172
|
FriggVPCGatewayAttachment: {
|
|
275
173
|
Type: 'AWS::EC2::VPCGatewayAttachment',
|
|
276
174
|
Properties: {
|
|
@@ -278,8 +176,6 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
278
176
|
InternetGatewayId: { Ref: 'FriggInternetGateway' },
|
|
279
177
|
},
|
|
280
178
|
},
|
|
281
|
-
|
|
282
|
-
// Public Subnet for NAT Gateway
|
|
283
179
|
FriggPublicSubnet: {
|
|
284
180
|
Type: 'AWS::EC2::Subnet',
|
|
285
181
|
Properties: {
|
|
@@ -288,31 +184,14 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
288
184
|
AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
|
|
289
185
|
MapPublicIpOnLaunch: true,
|
|
290
186
|
Tags: [
|
|
291
|
-
{
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
},
|
|
295
|
-
{
|
|
296
|
-
Key: 'ManagedBy',
|
|
297
|
-
Value: 'Frigg',
|
|
298
|
-
},
|
|
299
|
-
{
|
|
300
|
-
Key: 'Service',
|
|
301
|
-
Value: '${self:service}',
|
|
302
|
-
},
|
|
303
|
-
{
|
|
304
|
-
Key: 'Stage',
|
|
305
|
-
Value: '${self:provider.stage}',
|
|
306
|
-
},
|
|
307
|
-
{
|
|
308
|
-
Key: 'Type',
|
|
309
|
-
Value: 'Public',
|
|
310
|
-
},
|
|
187
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-subnet' },
|
|
188
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
189
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
190
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
191
|
+
{ Key: 'Type', Value: 'Public' },
|
|
311
192
|
],
|
|
312
193
|
},
|
|
313
194
|
},
|
|
314
|
-
|
|
315
|
-
// Private Subnet 1 for Lambda
|
|
316
195
|
FriggPrivateSubnet1: {
|
|
317
196
|
Type: 'AWS::EC2::Subnet',
|
|
318
197
|
Properties: {
|
|
@@ -320,31 +199,14 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
320
199
|
CidrBlock: '10.0.2.0/24',
|
|
321
200
|
AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
|
|
322
201
|
Tags: [
|
|
323
|
-
{
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
},
|
|
327
|
-
{
|
|
328
|
-
Key: 'ManagedBy',
|
|
329
|
-
Value: 'Frigg',
|
|
330
|
-
},
|
|
331
|
-
{
|
|
332
|
-
Key: 'Service',
|
|
333
|
-
Value: '${self:service}',
|
|
334
|
-
},
|
|
335
|
-
{
|
|
336
|
-
Key: 'Stage',
|
|
337
|
-
Value: '${self:provider.stage}',
|
|
338
|
-
},
|
|
339
|
-
{
|
|
340
|
-
Key: 'Type',
|
|
341
|
-
Value: 'Private',
|
|
342
|
-
},
|
|
202
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-subnet-1' },
|
|
203
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
204
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
205
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
206
|
+
{ Key: 'Type', Value: 'Private' },
|
|
343
207
|
],
|
|
344
208
|
},
|
|
345
209
|
},
|
|
346
|
-
|
|
347
|
-
// Private Subnet 2 for Lambda (different AZ for redundancy)
|
|
348
210
|
FriggPrivateSubnet2: {
|
|
349
211
|
Type: 'AWS::EC2::Subnet',
|
|
350
212
|
Properties: {
|
|
@@ -352,117 +214,53 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
352
214
|
CidrBlock: '10.0.3.0/24',
|
|
353
215
|
AvailabilityZone: { 'Fn::Select': [1, { 'Fn::GetAZs': '' }] },
|
|
354
216
|
Tags: [
|
|
355
|
-
{
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
},
|
|
359
|
-
{
|
|
360
|
-
Key: 'ManagedBy',
|
|
361
|
-
Value: 'Frigg',
|
|
362
|
-
},
|
|
363
|
-
{
|
|
364
|
-
Key: 'Service',
|
|
365
|
-
Value: '${self:service}',
|
|
366
|
-
},
|
|
367
|
-
{
|
|
368
|
-
Key: 'Stage',
|
|
369
|
-
Value: '${self:provider.stage}',
|
|
370
|
-
},
|
|
371
|
-
{
|
|
372
|
-
Key: 'Type',
|
|
373
|
-
Value: 'Private',
|
|
374
|
-
},
|
|
217
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-subnet-2' },
|
|
218
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
219
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
220
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
221
|
+
{ Key: 'Type', Value: 'Private' },
|
|
375
222
|
],
|
|
376
223
|
},
|
|
377
224
|
},
|
|
378
|
-
|
|
379
|
-
// Elastic IP for NAT Gateway
|
|
380
225
|
FriggNATGatewayEIP: {
|
|
381
226
|
Type: 'AWS::EC2::EIP',
|
|
382
227
|
Properties: {
|
|
383
228
|
Domain: 'vpc',
|
|
384
229
|
Tags: [
|
|
385
|
-
{
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
},
|
|
389
|
-
{
|
|
390
|
-
Key: 'ManagedBy',
|
|
391
|
-
Value: 'Frigg',
|
|
392
|
-
},
|
|
393
|
-
{
|
|
394
|
-
Key: 'Service',
|
|
395
|
-
Value: '${self:service}',
|
|
396
|
-
},
|
|
397
|
-
{
|
|
398
|
-
Key: 'Stage',
|
|
399
|
-
Value: '${self:provider.stage}',
|
|
400
|
-
},
|
|
230
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-eip' },
|
|
231
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
232
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
233
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
401
234
|
],
|
|
402
235
|
},
|
|
403
236
|
DependsOn: 'FriggVPCGatewayAttachment',
|
|
404
237
|
},
|
|
405
|
-
|
|
406
|
-
// NAT Gateway for private subnet internet access
|
|
407
238
|
FriggNATGateway: {
|
|
408
239
|
Type: 'AWS::EC2::NatGateway',
|
|
409
240
|
Properties: {
|
|
410
|
-
AllocationId: {
|
|
411
|
-
'Fn::GetAtt': ['FriggNATGatewayEIP', 'AllocationId'],
|
|
412
|
-
},
|
|
241
|
+
AllocationId: { 'Fn::GetAtt': ['FriggNATGatewayEIP', 'AllocationId'] },
|
|
413
242
|
SubnetId: { Ref: 'FriggPublicSubnet' },
|
|
414
243
|
Tags: [
|
|
415
|
-
{
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
},
|
|
419
|
-
{
|
|
420
|
-
Key: 'ManagedBy',
|
|
421
|
-
Value: 'Frigg',
|
|
422
|
-
},
|
|
423
|
-
{
|
|
424
|
-
Key: 'Service',
|
|
425
|
-
Value: '${self:service}',
|
|
426
|
-
},
|
|
427
|
-
{
|
|
428
|
-
Key: 'Stage',
|
|
429
|
-
Value: '${self:provider.stage}',
|
|
430
|
-
},
|
|
244
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-gateway' },
|
|
245
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
246
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
247
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
431
248
|
],
|
|
432
249
|
},
|
|
433
250
|
},
|
|
434
|
-
|
|
435
|
-
// Public Route Table
|
|
436
251
|
FriggPublicRouteTable: {
|
|
437
252
|
Type: 'AWS::EC2::RouteTable',
|
|
438
253
|
Properties: {
|
|
439
254
|
VpcId: { Ref: 'FriggVPC' },
|
|
440
255
|
Tags: [
|
|
441
|
-
{
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
},
|
|
445
|
-
{
|
|
446
|
-
Key: 'ManagedBy',
|
|
447
|
-
Value: 'Frigg',
|
|
448
|
-
},
|
|
449
|
-
{
|
|
450
|
-
Key: 'Service',
|
|
451
|
-
Value: '${self:service}',
|
|
452
|
-
},
|
|
453
|
-
{
|
|
454
|
-
Key: 'Stage',
|
|
455
|
-
Value: '${self:provider.stage}',
|
|
456
|
-
},
|
|
457
|
-
{
|
|
458
|
-
Key: 'Type',
|
|
459
|
-
Value: 'Public',
|
|
460
|
-
},
|
|
256
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-rt' },
|
|
257
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
258
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
259
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
260
|
+
{ Key: 'Type', Value: 'Public' },
|
|
461
261
|
],
|
|
462
262
|
},
|
|
463
263
|
},
|
|
464
|
-
|
|
465
|
-
// Public Route to Internet Gateway
|
|
466
264
|
FriggPublicRoute: {
|
|
467
265
|
Type: 'AWS::EC2::Route',
|
|
468
266
|
Properties: {
|
|
@@ -472,8 +270,6 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
472
270
|
},
|
|
473
271
|
DependsOn: 'FriggVPCGatewayAttachment',
|
|
474
272
|
},
|
|
475
|
-
|
|
476
|
-
// Associate Public Subnet with Public Route Table
|
|
477
273
|
FriggPublicSubnetRouteTableAssociation: {
|
|
478
274
|
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
479
275
|
Properties: {
|
|
@@ -481,38 +277,19 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
481
277
|
RouteTableId: { Ref: 'FriggPublicRouteTable' },
|
|
482
278
|
},
|
|
483
279
|
},
|
|
484
|
-
|
|
485
|
-
// Private Route Table for Private Subnets
|
|
486
280
|
FriggPrivateRouteTable: {
|
|
487
281
|
Type: 'AWS::EC2::RouteTable',
|
|
488
282
|
Properties: {
|
|
489
283
|
VpcId: { Ref: 'FriggVPC' },
|
|
490
284
|
Tags: [
|
|
491
|
-
{
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
},
|
|
495
|
-
{
|
|
496
|
-
Key: 'ManagedBy',
|
|
497
|
-
Value: 'Frigg',
|
|
498
|
-
},
|
|
499
|
-
{
|
|
500
|
-
Key: 'Service',
|
|
501
|
-
Value: '${self:service}',
|
|
502
|
-
},
|
|
503
|
-
{
|
|
504
|
-
Key: 'Stage',
|
|
505
|
-
Value: '${self:provider.stage}',
|
|
506
|
-
},
|
|
507
|
-
{
|
|
508
|
-
Key: 'Type',
|
|
509
|
-
Value: 'Private',
|
|
510
|
-
},
|
|
285
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-rt' },
|
|
286
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
287
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
288
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
289
|
+
{ Key: 'Type', Value: 'Private' },
|
|
511
290
|
],
|
|
512
291
|
},
|
|
513
292
|
},
|
|
514
|
-
|
|
515
|
-
// Private Route to NAT Gateway
|
|
516
293
|
FriggPrivateRoute: {
|
|
517
294
|
Type: 'AWS::EC2::Route',
|
|
518
295
|
Properties: {
|
|
@@ -521,8 +298,6 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
521
298
|
NatGatewayId: { Ref: 'FriggNATGateway' },
|
|
522
299
|
},
|
|
523
300
|
},
|
|
524
|
-
|
|
525
|
-
// Associate Private Subnet 1 with Private Route Table
|
|
526
301
|
FriggPrivateSubnet1RouteTableAssociation: {
|
|
527
302
|
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
528
303
|
Properties: {
|
|
@@ -530,8 +305,6 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
530
305
|
RouteTableId: { Ref: 'FriggPrivateRouteTable' },
|
|
531
306
|
},
|
|
532
307
|
},
|
|
533
|
-
|
|
534
|
-
// Associate Private Subnet 2 with Private Route Table
|
|
535
308
|
FriggPrivateSubnet2RouteTableAssociation: {
|
|
536
309
|
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
537
310
|
Properties: {
|
|
@@ -539,75 +312,29 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
539
312
|
RouteTableId: { Ref: 'FriggPrivateRouteTable' },
|
|
540
313
|
},
|
|
541
314
|
},
|
|
542
|
-
|
|
543
|
-
// Security Group for Lambda functions
|
|
544
315
|
FriggLambdaSecurityGroup: {
|
|
545
316
|
Type: 'AWS::EC2::SecurityGroup',
|
|
546
317
|
Properties: {
|
|
547
318
|
GroupDescription: 'Security group for Frigg Lambda functions',
|
|
548
319
|
VpcId: { Ref: 'FriggVPC' },
|
|
549
320
|
SecurityGroupEgress: [
|
|
550
|
-
{
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
Description: 'HTTPS outbound',
|
|
556
|
-
},
|
|
557
|
-
{
|
|
558
|
-
IpProtocol: 'tcp',
|
|
559
|
-
FromPort: 80,
|
|
560
|
-
ToPort: 80,
|
|
561
|
-
CidrIp: '0.0.0.0/0',
|
|
562
|
-
Description: 'HTTP outbound',
|
|
563
|
-
},
|
|
564
|
-
{
|
|
565
|
-
IpProtocol: 'tcp',
|
|
566
|
-
FromPort: 53,
|
|
567
|
-
ToPort: 53,
|
|
568
|
-
CidrIp: '0.0.0.0/0',
|
|
569
|
-
Description: 'DNS TCP',
|
|
570
|
-
},
|
|
571
|
-
{
|
|
572
|
-
IpProtocol: 'udp',
|
|
573
|
-
FromPort: 53,
|
|
574
|
-
ToPort: 53,
|
|
575
|
-
CidrIp: '0.0.0.0/0',
|
|
576
|
-
Description: 'DNS UDP',
|
|
577
|
-
},
|
|
578
|
-
{
|
|
579
|
-
IpProtocol: 'tcp',
|
|
580
|
-
FromPort: 27017,
|
|
581
|
-
ToPort: 27017,
|
|
582
|
-
CidrIp: '0.0.0.0/0',
|
|
583
|
-
Description: 'MongoDB outbound',
|
|
584
|
-
},
|
|
321
|
+
{ IpProtocol: 'tcp', FromPort: 443, ToPort: 443, CidrIp: '0.0.0.0/0', Description: 'HTTPS outbound' },
|
|
322
|
+
{ IpProtocol: 'tcp', FromPort: 80, ToPort: 80, CidrIp: '0.0.0.0/0', Description: 'HTTP outbound' },
|
|
323
|
+
{ IpProtocol: 'tcp', FromPort: 53, ToPort: 53, CidrIp: '0.0.0.0/0', Description: 'DNS TCP' },
|
|
324
|
+
{ IpProtocol: 'udp', FromPort: 53, ToPort: 53, CidrIp: '0.0.0.0/0', Description: 'DNS UDP' },
|
|
325
|
+
{ IpProtocol: 'tcp', FromPort: 27017, ToPort: 27017, CidrIp: '0.0.0.0/0', Description: 'MongoDB outbound' },
|
|
585
326
|
],
|
|
586
327
|
Tags: [
|
|
587
|
-
{
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
},
|
|
591
|
-
{
|
|
592
|
-
Key: 'ManagedBy',
|
|
593
|
-
Value: 'Frigg',
|
|
594
|
-
},
|
|
595
|
-
{
|
|
596
|
-
Key: 'Service',
|
|
597
|
-
Value: '${self:service}',
|
|
598
|
-
},
|
|
599
|
-
{
|
|
600
|
-
Key: 'Stage',
|
|
601
|
-
Value: '${self:provider.stage}',
|
|
602
|
-
},
|
|
328
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-sg' },
|
|
329
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
330
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
331
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
603
332
|
],
|
|
604
333
|
},
|
|
605
334
|
},
|
|
606
335
|
};
|
|
607
336
|
|
|
608
|
-
// Add VPC Endpoints for cost optimization
|
|
609
337
|
if (AppDefinition.vpc.enableVPCEndpoints !== false) {
|
|
610
|
-
// S3 Gateway Endpoint (free)
|
|
611
338
|
vpcResources.FriggS3VPCEndpoint = {
|
|
612
339
|
Type: 'AWS::EC2::VPCEndpoint',
|
|
613
340
|
Properties: {
|
|
@@ -618,7 +345,6 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
618
345
|
},
|
|
619
346
|
};
|
|
620
347
|
|
|
621
|
-
// DynamoDB Gateway Endpoint (free)
|
|
622
348
|
vpcResources.FriggDynamoDBVPCEndpoint = {
|
|
623
349
|
Type: 'AWS::EC2::VPCEndpoint',
|
|
624
350
|
Properties: {
|
|
@@ -629,7 +355,6 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
629
355
|
},
|
|
630
356
|
};
|
|
631
357
|
|
|
632
|
-
// KMS Interface Endpoint (paid, but useful if using KMS)
|
|
633
358
|
if (AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') {
|
|
634
359
|
vpcResources.FriggKMSVPCEndpoint = {
|
|
635
360
|
Type: 'AWS::EC2::VPCEndpoint',
|
|
@@ -637,36 +362,25 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
637
362
|
VpcId: { Ref: 'FriggVPC' },
|
|
638
363
|
ServiceName: 'com.amazonaws.${self:provider.region}.kms',
|
|
639
364
|
VpcEndpointType: 'Interface',
|
|
640
|
-
SubnetIds: [
|
|
641
|
-
|
|
642
|
-
{ Ref: 'FriggPrivateSubnet2' },
|
|
643
|
-
],
|
|
644
|
-
SecurityGroupIds: [
|
|
645
|
-
{ Ref: 'FriggVPCEndpointSecurityGroup' },
|
|
646
|
-
],
|
|
365
|
+
SubnetIds: [{ Ref: 'FriggPrivateSubnet1' }, { Ref: 'FriggPrivateSubnet2' }],
|
|
366
|
+
SecurityGroupIds: [{ Ref: 'FriggVPCEndpointSecurityGroup' }],
|
|
647
367
|
PrivateDnsEnabled: true,
|
|
648
368
|
},
|
|
649
369
|
};
|
|
650
370
|
}
|
|
651
371
|
|
|
652
|
-
// Secrets Manager Interface Endpoint (paid, but useful for secrets)
|
|
653
372
|
vpcResources.FriggSecretsManagerVPCEndpoint = {
|
|
654
373
|
Type: 'AWS::EC2::VPCEndpoint',
|
|
655
374
|
Properties: {
|
|
656
375
|
VpcId: { Ref: 'FriggVPC' },
|
|
657
|
-
ServiceName:
|
|
658
|
-
'com.amazonaws.${self:provider.region}.secretsmanager',
|
|
376
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.secretsmanager',
|
|
659
377
|
VpcEndpointType: 'Interface',
|
|
660
|
-
SubnetIds: [
|
|
661
|
-
{ Ref: 'FriggPrivateSubnet1' },
|
|
662
|
-
{ Ref: 'FriggPrivateSubnet2' },
|
|
663
|
-
],
|
|
378
|
+
SubnetIds: [{ Ref: 'FriggPrivateSubnet1' }, { Ref: 'FriggPrivateSubnet2' }],
|
|
664
379
|
SecurityGroupIds: [{ Ref: 'FriggVPCEndpointSecurityGroup' }],
|
|
665
380
|
PrivateDnsEnabled: true,
|
|
666
381
|
},
|
|
667
382
|
};
|
|
668
383
|
|
|
669
|
-
// Security Group for VPC Endpoints
|
|
670
384
|
vpcResources.FriggVPCEndpointSecurityGroup = {
|
|
671
385
|
Type: 'AWS::EC2::SecurityGroup',
|
|
672
386
|
Properties: {
|
|
@@ -677,13 +391,10 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
677
391
|
IpProtocol: 'tcp',
|
|
678
392
|
FromPort: 443,
|
|
679
393
|
ToPort: 443,
|
|
680
|
-
SourceSecurityGroupId: {
|
|
681
|
-
Ref: 'FriggLambdaSecurityGroup',
|
|
682
|
-
},
|
|
394
|
+
SourceSecurityGroupId: { Ref: 'FriggLambdaSecurityGroup' },
|
|
683
395
|
Description: 'HTTPS from Lambda security group',
|
|
684
396
|
},
|
|
685
397
|
{
|
|
686
|
-
// Also allow from VPC CIDR as fallback
|
|
687
398
|
IpProtocol: 'tcp',
|
|
688
399
|
FromPort: 443,
|
|
689
400
|
ToPort: 443,
|
|
@@ -692,30 +403,12 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
692
403
|
},
|
|
693
404
|
],
|
|
694
405
|
Tags: [
|
|
695
|
-
{
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
},
|
|
699
|
-
{
|
|
700
|
-
|
|
701
|
-
Value: 'Frigg',
|
|
702
|
-
},
|
|
703
|
-
{
|
|
704
|
-
Key: 'Service',
|
|
705
|
-
Value: '${self:service}',
|
|
706
|
-
},
|
|
707
|
-
{
|
|
708
|
-
Key: 'Stage',
|
|
709
|
-
Value: '${self:provider.stage}',
|
|
710
|
-
},
|
|
711
|
-
{
|
|
712
|
-
Key: 'Type',
|
|
713
|
-
Value: 'VPCEndpoint',
|
|
714
|
-
},
|
|
715
|
-
{
|
|
716
|
-
Key: 'Purpose',
|
|
717
|
-
Value: 'Allow Lambda functions to access VPC endpoints',
|
|
718
|
-
},
|
|
406
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-vpc-endpoint-sg' },
|
|
407
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
408
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
409
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
410
|
+
{ Key: 'Type', Value: 'VPCEndpoint' },
|
|
411
|
+
{ Key: 'Purpose', Value: 'Allow Lambda functions to access VPC endpoints' },
|
|
719
412
|
],
|
|
720
413
|
},
|
|
721
414
|
};
|
|
@@ -724,150 +417,105 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
724
417
|
return vpcResources;
|
|
725
418
|
};
|
|
726
419
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
* @param {string} [AppDefinition.provider='aws'] - Cloud provider
|
|
732
|
-
* @param {Array} AppDefinition.integrations - Array of integration definitions
|
|
733
|
-
* @param {Object} [AppDefinition.vpc] - VPC configuration
|
|
734
|
-
* @param {Object} [AppDefinition.encryption] - KMS encryption configuration
|
|
735
|
-
* @param {Object} [AppDefinition.ssm] - SSM parameter store configuration
|
|
736
|
-
* @param {Object} [AppDefinition.websockets] - WebSocket configuration
|
|
737
|
-
* @param {boolean} [AppDefinition.websockets.enable=false] - Enable WebSocket support for live update streaming
|
|
738
|
-
* @returns {Object} Complete serverless framework configuration
|
|
739
|
-
*/
|
|
740
|
-
const composeServerlessDefinition = async (AppDefinition) => {
|
|
741
|
-
console.log('composeServerlessDefinition', AppDefinition);
|
|
742
|
-
// Store discovered resources
|
|
743
|
-
let discoveredResources = {};
|
|
420
|
+
const gatherDiscoveredResources = async (AppDefinition) => {
|
|
421
|
+
if (!shouldRunDiscovery(AppDefinition)) {
|
|
422
|
+
return {};
|
|
423
|
+
}
|
|
744
424
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
const config = {
|
|
755
|
-
vpc: AppDefinition.vpc || {},
|
|
756
|
-
encryption: AppDefinition.encryption || {},
|
|
757
|
-
ssm: AppDefinition.ssm || {},
|
|
758
|
-
};
|
|
425
|
+
console.log('🔍 Running AWS resource discovery for serverless template...');
|
|
426
|
+
try {
|
|
427
|
+
const region = process.env.AWS_REGION || 'us-east-1';
|
|
428
|
+
const discovery = new AWSDiscovery(region);
|
|
429
|
+
const config = {
|
|
430
|
+
vpc: AppDefinition.vpc || {},
|
|
431
|
+
encryption: AppDefinition.encryption || {},
|
|
432
|
+
ssm: AppDefinition.ssm || {},
|
|
433
|
+
};
|
|
759
434
|
|
|
760
|
-
|
|
435
|
+
const discoveredResources = await discovery.discoverResources(config);
|
|
761
436
|
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
discoveredResources.privateSubnetId2
|
|
769
|
-
)
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
437
|
+
console.log('✅ AWS discovery completed successfully!');
|
|
438
|
+
if (discoveredResources.defaultVpcId) {
|
|
439
|
+
console.log(` VPC: ${discoveredResources.defaultVpcId}`);
|
|
440
|
+
}
|
|
441
|
+
if (discoveredResources.privateSubnetId1 && discoveredResources.privateSubnetId2) {
|
|
442
|
+
console.log(
|
|
443
|
+
` Subnets: ${discoveredResources.privateSubnetId1}, ${discoveredResources.privateSubnetId2}`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
if (discoveredResources.defaultSecurityGroupId) {
|
|
447
|
+
console.log(` Security Group: ${discoveredResources.defaultSecurityGroupId}`);
|
|
448
|
+
}
|
|
449
|
+
if (discoveredResources.defaultKmsKeyId) {
|
|
450
|
+
console.log(` KMS Key: ${discoveredResources.defaultKmsKeyId}`);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return discoveredResources;
|
|
454
|
+
} catch (error) {
|
|
455
|
+
console.error('❌ AWS discovery failed:', error.message);
|
|
456
|
+
throw new Error(`AWS discovery failed: ${error.message}`);
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const buildEnvironment = (appEnvironmentVars, discoveredResources) => {
|
|
461
|
+
const environment = {
|
|
462
|
+
STAGE: '${opt:stage, "dev"}',
|
|
463
|
+
AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1,
|
|
464
|
+
...appEnvironmentVars,
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const discoveryEnvMapping = {
|
|
468
|
+
defaultVpcId: 'AWS_DISCOVERY_VPC_ID',
|
|
469
|
+
defaultSecurityGroupId: 'AWS_DISCOVERY_SECURITY_GROUP_ID',
|
|
470
|
+
privateSubnetId1: 'AWS_DISCOVERY_SUBNET_ID_1',
|
|
471
|
+
privateSubnetId2: 'AWS_DISCOVERY_SUBNET_ID_2',
|
|
472
|
+
publicSubnetId: 'AWS_DISCOVERY_PUBLIC_SUBNET_ID',
|
|
473
|
+
defaultRouteTableId: 'AWS_DISCOVERY_ROUTE_TABLE_ID',
|
|
474
|
+
defaultKmsKeyId: 'AWS_DISCOVERY_KMS_KEY_ID',
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
for (const [key, envKey] of Object.entries(discoveryEnvMapping)) {
|
|
478
|
+
if (discoveredResources[key]) {
|
|
479
|
+
environment[envKey] = discoveredResources[key];
|
|
787
480
|
}
|
|
788
481
|
}
|
|
789
482
|
|
|
790
|
-
|
|
791
|
-
|
|
483
|
+
return environment;
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
const createBaseDefinition = (AppDefinition, appEnvironmentVars, discoveredResources) => {
|
|
487
|
+
const region = process.env.AWS_REGION || 'us-east-1';
|
|
792
488
|
|
|
793
|
-
|
|
489
|
+
return {
|
|
794
490
|
frameworkVersion: '>=3.17.0',
|
|
795
491
|
service: AppDefinition.name || 'create-frigg-app',
|
|
796
492
|
package: {
|
|
797
493
|
individually: true,
|
|
798
|
-
exclude: [
|
|
799
|
-
'!**/node_modules/aws-sdk/**',
|
|
800
|
-
'!**/node_modules/@aws-sdk/**',
|
|
801
|
-
'!package.json',
|
|
802
|
-
],
|
|
494
|
+
exclude: ['!**/node_modules/aws-sdk/**', '!**/node_modules/@aws-sdk/**', '!package.json'],
|
|
803
495
|
},
|
|
804
496
|
useDotenv: true,
|
|
805
497
|
provider: {
|
|
806
498
|
name: AppDefinition.provider || 'aws',
|
|
807
499
|
runtime: 'nodejs20.x',
|
|
808
500
|
timeout: 30,
|
|
809
|
-
region
|
|
501
|
+
region,
|
|
810
502
|
stage: '${opt:stage}',
|
|
811
|
-
environment:
|
|
812
|
-
STAGE: '${opt:stage, "dev"}',
|
|
813
|
-
AWS_NODEJS_CONNECTION_REUSE_ENABLED: 1,
|
|
814
|
-
// Add environment variables from appDefinition
|
|
815
|
-
...appEnvironmentVars,
|
|
816
|
-
// Add discovered resources to environment if available
|
|
817
|
-
...(discoveredResources.defaultVpcId && {
|
|
818
|
-
AWS_DISCOVERY_VPC_ID: discoveredResources.defaultVpcId,
|
|
819
|
-
}),
|
|
820
|
-
...(discoveredResources.defaultSecurityGroupId && {
|
|
821
|
-
AWS_DISCOVERY_SECURITY_GROUP_ID:
|
|
822
|
-
discoveredResources.defaultSecurityGroupId,
|
|
823
|
-
}),
|
|
824
|
-
...(discoveredResources.privateSubnetId1 && {
|
|
825
|
-
AWS_DISCOVERY_SUBNET_ID_1:
|
|
826
|
-
discoveredResources.privateSubnetId1,
|
|
827
|
-
}),
|
|
828
|
-
...(discoveredResources.privateSubnetId2 && {
|
|
829
|
-
AWS_DISCOVERY_SUBNET_ID_2:
|
|
830
|
-
discoveredResources.privateSubnetId2,
|
|
831
|
-
}),
|
|
832
|
-
...(discoveredResources.publicSubnetId && {
|
|
833
|
-
AWS_DISCOVERY_PUBLIC_SUBNET_ID:
|
|
834
|
-
discoveredResources.publicSubnetId,
|
|
835
|
-
}),
|
|
836
|
-
...(discoveredResources.defaultRouteTableId && {
|
|
837
|
-
AWS_DISCOVERY_ROUTE_TABLE_ID:
|
|
838
|
-
discoveredResources.defaultRouteTableId,
|
|
839
|
-
}),
|
|
840
|
-
...(discoveredResources.defaultKmsKeyId && {
|
|
841
|
-
AWS_DISCOVERY_KMS_KEY_ID:
|
|
842
|
-
discoveredResources.defaultKmsKeyId,
|
|
843
|
-
}),
|
|
844
|
-
},
|
|
503
|
+
environment: buildEnvironment(appEnvironmentVars, discoveredResources),
|
|
845
504
|
iamRoleStatements: [
|
|
846
505
|
{
|
|
847
506
|
Effect: 'Allow',
|
|
848
507
|
Action: ['sns:Publish'],
|
|
849
|
-
Resource: {
|
|
850
|
-
Ref: 'InternalErrorBridgeTopic',
|
|
851
|
-
},
|
|
508
|
+
Resource: { Ref: 'InternalErrorBridgeTopic' },
|
|
852
509
|
},
|
|
853
510
|
{
|
|
854
511
|
Effect: 'Allow',
|
|
855
|
-
Action: [
|
|
856
|
-
'sqs:SendMessage',
|
|
857
|
-
'sqs:SendMessageBatch',
|
|
858
|
-
'sqs:GetQueueUrl',
|
|
859
|
-
'sqs:GetQueueAttributes',
|
|
860
|
-
],
|
|
512
|
+
Action: ['sqs:SendMessage', 'sqs:SendMessageBatch', 'sqs:GetQueueUrl', 'sqs:GetQueueAttributes'],
|
|
861
513
|
Resource: [
|
|
862
|
-
{
|
|
863
|
-
'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
|
|
864
|
-
},
|
|
514
|
+
{ 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'] },
|
|
865
515
|
{
|
|
866
516
|
'Fn::Join': [
|
|
867
517
|
':',
|
|
868
|
-
[
|
|
869
|
-
'arn:aws:sqs:${self:provider.region}:*:${self:service}--${self:provider.stage}-*Queue',
|
|
870
|
-
],
|
|
518
|
+
['arn:aws:sqs:${self:provider.region}:*:${self:service}--${self:provider.stage}-*Queue'],
|
|
871
519
|
],
|
|
872
520
|
},
|
|
873
521
|
],
|
|
@@ -902,7 +550,7 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
902
550
|
autoCreate: false,
|
|
903
551
|
apiVersion: '2012-11-05',
|
|
904
552
|
endpoint: 'http://localhost:4566',
|
|
905
|
-
region
|
|
553
|
+
region,
|
|
906
554
|
accessKeyId: 'root',
|
|
907
555
|
secretAccessKey: 'root',
|
|
908
556
|
skipCacheInvalidation: false,
|
|
@@ -913,57 +561,22 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
913
561
|
},
|
|
914
562
|
functions: {
|
|
915
563
|
auth: {
|
|
916
|
-
handler:
|
|
917
|
-
'node_modules/@friggframework/core/handlers/routers/auth.handler',
|
|
564
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/auth.handler',
|
|
918
565
|
events: [
|
|
919
|
-
{
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
method: 'ANY',
|
|
923
|
-
},
|
|
924
|
-
},
|
|
925
|
-
{
|
|
926
|
-
httpApi: {
|
|
927
|
-
path: '/api/integrations/{proxy+}',
|
|
928
|
-
method: 'ANY',
|
|
929
|
-
},
|
|
930
|
-
},
|
|
931
|
-
{
|
|
932
|
-
httpApi: {
|
|
933
|
-
path: '/api/authorize',
|
|
934
|
-
method: 'ANY',
|
|
935
|
-
},
|
|
936
|
-
},
|
|
566
|
+
{ httpApi: { path: '/api/integrations', method: 'ANY' } },
|
|
567
|
+
{ httpApi: { path: '/api/integrations/{proxy+}', method: 'ANY' } },
|
|
568
|
+
{ httpApi: { path: '/api/authorize', method: 'ANY' } },
|
|
937
569
|
],
|
|
938
570
|
},
|
|
939
571
|
user: {
|
|
940
|
-
handler:
|
|
941
|
-
|
|
942
|
-
events: [
|
|
943
|
-
{
|
|
944
|
-
httpApi: {
|
|
945
|
-
path: '/user/{proxy+}',
|
|
946
|
-
method: 'ANY',
|
|
947
|
-
},
|
|
948
|
-
},
|
|
949
|
-
],
|
|
572
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/user.handler',
|
|
573
|
+
events: [{ httpApi: { path: '/user/{proxy+}', method: 'ANY' } }],
|
|
950
574
|
},
|
|
951
575
|
health: {
|
|
952
|
-
handler:
|
|
953
|
-
'node_modules/@friggframework/core/handlers/routers/health.handler',
|
|
576
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/health.handler',
|
|
954
577
|
events: [
|
|
955
|
-
{
|
|
956
|
-
|
|
957
|
-
path: '/health',
|
|
958
|
-
method: 'GET',
|
|
959
|
-
},
|
|
960
|
-
},
|
|
961
|
-
{
|
|
962
|
-
httpApi: {
|
|
963
|
-
path: '/health/{proxy+}',
|
|
964
|
-
method: 'GET',
|
|
965
|
-
},
|
|
966
|
-
},
|
|
578
|
+
{ httpApi: { path: '/health', method: 'GET' } },
|
|
579
|
+
{ httpApi: { path: '/health/{proxy+}', method: 'GET' } },
|
|
967
580
|
],
|
|
968
581
|
},
|
|
969
582
|
},
|
|
@@ -972,8 +585,7 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
972
585
|
InternalErrorQueue: {
|
|
973
586
|
Type: 'AWS::SQS::Queue',
|
|
974
587
|
Properties: {
|
|
975
|
-
QueueName:
|
|
976
|
-
'${self:service}-internal-error-queue-${self:provider.stage}',
|
|
588
|
+
QueueName: '${self:service}-internal-error-queue-${self:provider.stage}',
|
|
977
589
|
MessageRetentionPeriod: 300,
|
|
978
590
|
},
|
|
979
591
|
},
|
|
@@ -983,9 +595,7 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
983
595
|
Subscription: [
|
|
984
596
|
{
|
|
985
597
|
Protocol: 'sqs',
|
|
986
|
-
Endpoint: {
|
|
987
|
-
'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
|
|
988
|
-
},
|
|
598
|
+
Endpoint: { 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'] },
|
|
989
599
|
},
|
|
990
600
|
],
|
|
991
601
|
},
|
|
@@ -1000,25 +610,11 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
1000
610
|
{
|
|
1001
611
|
Sid: 'Allow Dead Letter SNS to publish to SQS',
|
|
1002
612
|
Effect: 'Allow',
|
|
1003
|
-
Principal: {
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
Resource: {
|
|
1007
|
-
'Fn::GetAtt': [
|
|
1008
|
-
'InternalErrorQueue',
|
|
1009
|
-
'Arn',
|
|
1010
|
-
],
|
|
1011
|
-
},
|
|
1012
|
-
Action: [
|
|
1013
|
-
'SQS:SendMessage',
|
|
1014
|
-
'SQS:SendMessageBatch',
|
|
1015
|
-
],
|
|
613
|
+
Principal: { Service: 'sns.amazonaws.com' },
|
|
614
|
+
Resource: { 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'] },
|
|
615
|
+
Action: ['SQS:SendMessage', 'SQS:SendMessageBatch'],
|
|
1016
616
|
Condition: {
|
|
1017
|
-
ArnEquals: {
|
|
1018
|
-
'aws:SourceArn': {
|
|
1019
|
-
Ref: 'InternalErrorBridgeTopic',
|
|
1020
|
-
},
|
|
1021
|
-
},
|
|
617
|
+
ArnEquals: { 'aws:SourceArn': { Ref: 'InternalErrorBridgeTopic' } },
|
|
1022
618
|
},
|
|
1023
619
|
},
|
|
1024
620
|
],
|
|
@@ -1038,1309 +634,989 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
1038
634
|
Period: 60,
|
|
1039
635
|
AlarmActions: [{ Ref: 'InternalErrorBridgeTopic' }],
|
|
1040
636
|
Dimensions: [
|
|
1041
|
-
{
|
|
1042
|
-
|
|
1043
|
-
Value: { Ref: 'HttpApi' },
|
|
1044
|
-
},
|
|
1045
|
-
{
|
|
1046
|
-
Name: 'Stage',
|
|
1047
|
-
Value: '${self:provider.stage}',
|
|
1048
|
-
},
|
|
637
|
+
{ Name: 'ApiId', Value: { Ref: 'HttpApi' } },
|
|
638
|
+
{ Name: 'Stage', Value: '${self:provider.stage}' },
|
|
1049
639
|
],
|
|
1050
640
|
},
|
|
1051
641
|
},
|
|
1052
642
|
},
|
|
1053
643
|
},
|
|
1054
644
|
};
|
|
645
|
+
};
|
|
1055
646
|
|
|
1056
|
-
|
|
1057
|
-
if (AppDefinition.encryption?.fieldLevelEncryptionMethod
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
// Use the existing discovered KMS key
|
|
1061
|
-
console.log(
|
|
1062
|
-
`Using existing KMS key: ${discoveredResources.defaultKmsKeyId}`
|
|
1063
|
-
);
|
|
647
|
+
const applyKmsConfiguration = (definition, AppDefinition, discoveredResources) => {
|
|
648
|
+
if (AppDefinition.encryption?.fieldLevelEncryptionMethod !== 'kms') {
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
1064
651
|
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
652
|
+
if (discoveredResources.defaultKmsKeyId) {
|
|
653
|
+
console.log(`Using existing KMS key: ${discoveredResources.defaultKmsKeyId}`);
|
|
654
|
+
definition.resources.Resources.FriggKMSKeyAlias = {
|
|
655
|
+
Type: 'AWS::KMS::Alias',
|
|
656
|
+
DeletionPolicy: 'Retain',
|
|
657
|
+
Properties: {
|
|
658
|
+
AliasName: 'alias/${self:service}-${self:provider.stage}-frigg-kms',
|
|
659
|
+
TargetKeyId: discoveredResources.defaultKmsKeyId,
|
|
660
|
+
},
|
|
661
|
+
};
|
|
1075
662
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
663
|
+
definition.provider.iamRoleStatements.push({
|
|
664
|
+
Effect: 'Allow',
|
|
665
|
+
Action: ['kms:GenerateDataKey', 'kms:Decrypt'],
|
|
666
|
+
Resource: [discoveredResources.defaultKmsKeyId],
|
|
667
|
+
});
|
|
668
|
+
} else {
|
|
669
|
+
if (AppDefinition.encryption?.createResourceIfNoneFound !== true) {
|
|
670
|
+
throw new Error(
|
|
671
|
+
'KMS field-level encryption is enabled but no KMS key was found. ' +
|
|
672
|
+
'Either provide an existing KMS key or set encryption.createResourceIfNoneFound to true to create a new key.'
|
|
673
|
+
);
|
|
674
|
+
}
|
|
1081
675
|
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
Statement: [
|
|
1099
|
-
{
|
|
1100
|
-
Sid: 'AllowRootAccountAdmin',
|
|
1101
|
-
Effect: 'Allow',
|
|
1102
|
-
Principal: {
|
|
1103
|
-
AWS: {
|
|
1104
|
-
'Fn::Sub':
|
|
1105
|
-
'arn:aws:iam::${AWS::AccountId}:root',
|
|
1106
|
-
},
|
|
1107
|
-
},
|
|
1108
|
-
Action: 'kms:*',
|
|
1109
|
-
Resource: '*',
|
|
1110
|
-
},
|
|
1111
|
-
{
|
|
1112
|
-
Sid: 'AllowLambdaService',
|
|
1113
|
-
Effect: 'Allow',
|
|
1114
|
-
Principal: {
|
|
1115
|
-
Service: 'lambda.amazonaws.com',
|
|
1116
|
-
},
|
|
1117
|
-
Action: [
|
|
1118
|
-
'kms:GenerateDataKey',
|
|
1119
|
-
'kms:Decrypt',
|
|
1120
|
-
'kms:DescribeKey',
|
|
1121
|
-
],
|
|
1122
|
-
Resource: '*',
|
|
1123
|
-
Condition: {
|
|
1124
|
-
StringEquals: {
|
|
1125
|
-
'kms:ViaService': `lambda.${process.env.AWS_REGION ||
|
|
1126
|
-
'us-east-1'
|
|
1127
|
-
}.amazonaws.com`,
|
|
1128
|
-
},
|
|
1129
|
-
},
|
|
1130
|
-
},
|
|
1131
|
-
],
|
|
1132
|
-
},
|
|
1133
|
-
Tags: [
|
|
1134
|
-
{
|
|
1135
|
-
Key: 'Name',
|
|
1136
|
-
Value: '${self:service}-${self:provider.stage}-frigg-kms-key',
|
|
1137
|
-
},
|
|
1138
|
-
{
|
|
1139
|
-
Key: 'ManagedBy',
|
|
1140
|
-
Value: 'Frigg',
|
|
676
|
+
console.log('No existing KMS key found, creating a new one...');
|
|
677
|
+
definition.resources.Resources.FriggKMSKey = {
|
|
678
|
+
Type: 'AWS::KMS::Key',
|
|
679
|
+
DeletionPolicy: 'Retain',
|
|
680
|
+
UpdateReplacePolicy: 'Retain',
|
|
681
|
+
Properties: {
|
|
682
|
+
EnableKeyRotation: true,
|
|
683
|
+
Description: 'Frigg KMS key for field-level encryption',
|
|
684
|
+
KeyPolicy: {
|
|
685
|
+
Version: '2012-10-17',
|
|
686
|
+
Statement: [
|
|
687
|
+
{
|
|
688
|
+
Sid: 'AllowRootAccountAdmin',
|
|
689
|
+
Effect: 'Allow',
|
|
690
|
+
Principal: {
|
|
691
|
+
AWS: { 'Fn::Sub': 'arn:aws:iam::${AWS::AccountId}:root' },
|
|
1141
692
|
},
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
693
|
+
Action: 'kms:*',
|
|
694
|
+
Resource: '*',
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
Sid: 'AllowLambdaService',
|
|
698
|
+
Effect: 'Allow',
|
|
699
|
+
Principal: { Service: 'lambda.amazonaws.com' },
|
|
700
|
+
Action: ['kms:GenerateDataKey', 'kms:Decrypt', 'kms:DescribeKey'],
|
|
701
|
+
Resource: '*',
|
|
702
|
+
Condition: {
|
|
703
|
+
StringEquals: {
|
|
704
|
+
'kms:ViaService': `lambda.${process.env.AWS_REGION || 'us-east-1'}.amazonaws.com`,
|
|
705
|
+
},
|
|
1145
706
|
},
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
}
|
|
707
|
+
},
|
|
708
|
+
],
|
|
709
|
+
},
|
|
710
|
+
Tags: [
|
|
711
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-frigg-kms-key' },
|
|
712
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
713
|
+
{ Key: 'Purpose', Value: 'Field-level encryption for Frigg application' },
|
|
714
|
+
],
|
|
715
|
+
},
|
|
716
|
+
};
|
|
1149
717
|
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
};
|
|
718
|
+
definition.resources.Resources.FriggKMSKeyAlias = {
|
|
719
|
+
Type: 'AWS::KMS::Alias',
|
|
720
|
+
DeletionPolicy: 'Retain',
|
|
721
|
+
Properties: {
|
|
722
|
+
AliasName: 'alias/${self:service}-${self:provider.stage}-frigg-kms',
|
|
723
|
+
TargetKeyId: { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] },
|
|
724
|
+
},
|
|
725
|
+
};
|
|
1159
726
|
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
727
|
+
definition.provider.iamRoleStatements.push({
|
|
728
|
+
Effect: 'Allow',
|
|
729
|
+
Action: ['kms:GenerateDataKey', 'kms:Decrypt'],
|
|
730
|
+
Resource: [{ 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] }],
|
|
731
|
+
});
|
|
1165
732
|
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
733
|
+
definition.provider.environment.KMS_KEY_ARN = { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] };
|
|
734
|
+
definition.custom.kmsGrants = {
|
|
735
|
+
kmsKeyId: { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] },
|
|
736
|
+
};
|
|
737
|
+
}
|
|
1169
738
|
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
739
|
+
definition.plugins.push('serverless-kms-grants');
|
|
740
|
+
if (!definition.custom.kmsGrants) {
|
|
741
|
+
definition.custom.kmsGrants = {
|
|
742
|
+
kmsKeyId: discoveredResources.defaultKmsKeyId || '${env:AWS_DISCOVERY_KMS_KEY_ID}',
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (!definition.provider.environment.KMS_KEY_ARN) {
|
|
747
|
+
definition.provider.environment.KMS_KEY_ARN =
|
|
748
|
+
discoveredResources.defaultKmsKeyId || '${env:AWS_DISCOVERY_KMS_KEY_ID}';
|
|
749
|
+
}
|
|
750
|
+
};
|
|
1182
751
|
|
|
1183
|
-
|
|
752
|
+
const healVpcConfiguration = (discoveredResources, AppDefinition) => {
|
|
753
|
+
const healingReport = {
|
|
754
|
+
healed: [],
|
|
755
|
+
warnings: [],
|
|
756
|
+
errors: [],
|
|
757
|
+
recommendations: [],
|
|
758
|
+
criticalActions: [],
|
|
759
|
+
};
|
|
1184
760
|
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
kmsKeyId:
|
|
1189
|
-
discoveredResources.defaultKmsKeyId ||
|
|
1190
|
-
'${env:AWS_DISCOVERY_KMS_KEY_ID}',
|
|
1191
|
-
};
|
|
1192
|
-
}
|
|
761
|
+
if (!AppDefinition.vpc?.selfHeal) {
|
|
762
|
+
return healingReport;
|
|
763
|
+
}
|
|
1193
764
|
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
765
|
+
console.log('🔧 Self-healing mode enabled - checking for VPC misconfigurations...');
|
|
766
|
+
|
|
767
|
+
if (discoveredResources.natGatewayInPrivateSubnet) {
|
|
768
|
+
healingReport.warnings.push(
|
|
769
|
+
`NAT Gateway ${discoveredResources.natGatewayInPrivateSubnet} is in a private subnet`
|
|
770
|
+
);
|
|
771
|
+
healingReport.recommendations.push(
|
|
772
|
+
'NAT Gateway should be recreated in a public subnet for proper internet connectivity'
|
|
773
|
+
);
|
|
774
|
+
discoveredResources.needsNewNatGateway = true;
|
|
775
|
+
healingReport.healed.push('Marked NAT Gateway for recreation in public subnet');
|
|
1203
776
|
}
|
|
1204
777
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
* @returns {Object} Healing report with actions taken and recommendations
|
|
1210
|
-
*/
|
|
1211
|
-
const healVPCConfiguration = (discoveredResources, AppDefinition) => {
|
|
1212
|
-
const healingReport = {
|
|
1213
|
-
healed: [],
|
|
1214
|
-
warnings: [],
|
|
1215
|
-
errors: [],
|
|
1216
|
-
recommendations: [],
|
|
1217
|
-
criticalActions: []
|
|
1218
|
-
};
|
|
778
|
+
if (discoveredResources.elasticIpAlreadyAssociated) {
|
|
779
|
+
healingReport.warnings.push(
|
|
780
|
+
`Elastic IP ${discoveredResources.existingElasticIp} is already associated`
|
|
781
|
+
);
|
|
1219
782
|
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
783
|
+
if (discoveredResources.existingNatGatewayId) {
|
|
784
|
+
healingReport.healed.push('Will reuse existing NAT Gateway instead of creating a new one');
|
|
785
|
+
discoveredResources.reuseExistingNatGateway = true;
|
|
786
|
+
} else {
|
|
787
|
+
healingReport.healed.push('Will allocate a new Elastic IP for NAT Gateway');
|
|
788
|
+
discoveredResources.allocateNewElasticIp = true;
|
|
1223
789
|
}
|
|
790
|
+
}
|
|
1224
791
|
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
792
|
+
if (
|
|
793
|
+
discoveredResources.privateSubnetsWithWrongRoutes &&
|
|
794
|
+
discoveredResources.privateSubnetsWithWrongRoutes.length > 0
|
|
795
|
+
) {
|
|
796
|
+
healingReport.warnings.push(
|
|
797
|
+
`Found ${discoveredResources.privateSubnetsWithWrongRoutes.length} subnets that are PUBLIC but will be used for Lambda`
|
|
798
|
+
);
|
|
799
|
+
healingReport.healed.push(
|
|
800
|
+
'Route tables will be corrected during deployment - converting public subnets to private'
|
|
801
|
+
);
|
|
802
|
+
healingReport.criticalActions.push(
|
|
803
|
+
'SUBNET ISOLATION: Will create separate route tables to ensure Lambda subnets are private'
|
|
804
|
+
);
|
|
805
|
+
}
|
|
1235
806
|
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
807
|
+
if (discoveredResources.subnetConversionRequired) {
|
|
808
|
+
healingReport.warnings.push(
|
|
809
|
+
'Subnet configuration mismatch detected - Lambda functions require private subnets'
|
|
810
|
+
);
|
|
811
|
+
healingReport.healed.push('Will create proper route table configuration for subnet isolation');
|
|
812
|
+
}
|
|
1240
813
|
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
814
|
+
if (discoveredResources.orphanedElasticIps?.length > 0) {
|
|
815
|
+
healingReport.warnings.push(
|
|
816
|
+
`Found ${discoveredResources.orphanedElasticIps.length} orphaned Elastic IPs`
|
|
817
|
+
);
|
|
818
|
+
healingReport.recommendations.push('Consider releasing orphaned Elastic IPs to avoid charges');
|
|
819
|
+
}
|
|
1246
820
|
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
);
|
|
1252
|
-
discoveredResources.reuseExistingNatGateway = true;
|
|
1253
|
-
} else {
|
|
1254
|
-
healingReport.healed.push(
|
|
1255
|
-
'Will allocate a new Elastic IP for NAT Gateway'
|
|
1256
|
-
);
|
|
1257
|
-
discoveredResources.allocateNewElasticIp = true;
|
|
1258
|
-
}
|
|
1259
|
-
}
|
|
821
|
+
if (healingReport.criticalActions.length > 0) {
|
|
822
|
+
console.log('🚨 CRITICAL ACTIONS:');
|
|
823
|
+
healingReport.criticalActions.forEach((action) => console.log(` - ${action}`));
|
|
824
|
+
}
|
|
1260
825
|
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
`Found ${discoveredResources.privateSubnetsWithWrongRoutes.length} subnets that are PUBLIC but will be used for Lambda`
|
|
1266
|
-
);
|
|
1267
|
-
healingReport.healed.push(
|
|
1268
|
-
'Route tables will be corrected during deployment - converting public subnets to private'
|
|
1269
|
-
);
|
|
1270
|
-
healingReport.criticalActions.push(
|
|
1271
|
-
'SUBNET ISOLATION: Will create separate route tables to ensure Lambda subnets are private'
|
|
1272
|
-
);
|
|
1273
|
-
}
|
|
826
|
+
if (healingReport.healed.length > 0) {
|
|
827
|
+
console.log('✅ Self-healing actions:');
|
|
828
|
+
healingReport.healed.forEach((action) => console.log(` - ${action}`));
|
|
829
|
+
}
|
|
1274
830
|
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
);
|
|
1280
|
-
healingReport.healed.push(
|
|
1281
|
-
'Will create proper route table configuration for subnet isolation'
|
|
1282
|
-
);
|
|
1283
|
-
}
|
|
831
|
+
if (healingReport.warnings.length > 0) {
|
|
832
|
+
console.log('⚠️ Issues detected:');
|
|
833
|
+
healingReport.warnings.forEach((warning) => console.log(` - ${warning}`));
|
|
834
|
+
}
|
|
1284
835
|
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
);
|
|
1290
|
-
healingReport.recommendations.push(
|
|
1291
|
-
'Consider releasing orphaned Elastic IPs to avoid charges'
|
|
1292
|
-
);
|
|
1293
|
-
}
|
|
836
|
+
if (healingReport.recommendations.length > 0) {
|
|
837
|
+
console.log('💡 Recommendations:');
|
|
838
|
+
healingReport.recommendations.forEach((rec) => console.log(` - ${rec}`));
|
|
839
|
+
}
|
|
1294
840
|
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
console.log('🚨 CRITICAL ACTIONS:');
|
|
1298
|
-
healingReport.criticalActions.forEach(action => console.log(` - ${action}`));
|
|
1299
|
-
}
|
|
841
|
+
return healingReport;
|
|
842
|
+
};
|
|
1300
843
|
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
844
|
+
const configureVpc = (definition, AppDefinition, discoveredResources) => {
|
|
845
|
+
if (AppDefinition.vpc?.enable !== true) {
|
|
846
|
+
return;
|
|
847
|
+
}
|
|
1305
848
|
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
849
|
+
definition.provider.iamRoleStatements.push({
|
|
850
|
+
Effect: 'Allow',
|
|
851
|
+
Action: [
|
|
852
|
+
'ec2:CreateNetworkInterface',
|
|
853
|
+
'ec2:DescribeNetworkInterfaces',
|
|
854
|
+
'ec2:DeleteNetworkInterface',
|
|
855
|
+
'ec2:AttachNetworkInterface',
|
|
856
|
+
'ec2:DetachNetworkInterface',
|
|
857
|
+
],
|
|
858
|
+
Resource: '*',
|
|
859
|
+
});
|
|
1310
860
|
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
861
|
+
if (Object.keys(discoveredResources).length > 0) {
|
|
862
|
+
const healingReport = healVpcConfiguration(discoveredResources, AppDefinition);
|
|
863
|
+
if (healingReport.errors.length > 0 && !AppDefinition.vpc?.selfHeal) {
|
|
864
|
+
throw new Error(`VPC configuration errors detected: ${healingReport.errors.join(', ')}`);
|
|
1314
865
|
}
|
|
866
|
+
}
|
|
1315
867
|
|
|
1316
|
-
|
|
868
|
+
const vpcManagement = AppDefinition.vpc.management || 'discover';
|
|
869
|
+
let vpcId = null;
|
|
870
|
+
const vpcConfig = {
|
|
871
|
+
securityGroupIds: [],
|
|
872
|
+
subnetIds: [],
|
|
1317
873
|
};
|
|
1318
874
|
|
|
1319
|
-
|
|
1320
|
-
if (AppDefinition.vpc?.enable === true) {
|
|
1321
|
-
// Add VPC-related IAM permissions
|
|
1322
|
-
definition.provider.iamRoleStatements.push({
|
|
1323
|
-
Effect: 'Allow',
|
|
1324
|
-
Action: [
|
|
1325
|
-
'ec2:CreateNetworkInterface',
|
|
1326
|
-
'ec2:DescribeNetworkInterfaces',
|
|
1327
|
-
'ec2:DeleteNetworkInterface',
|
|
1328
|
-
'ec2:AttachNetworkInterface',
|
|
1329
|
-
'ec2:DetachNetworkInterface',
|
|
1330
|
-
],
|
|
1331
|
-
Resource: '*',
|
|
1332
|
-
});
|
|
1333
|
-
|
|
1334
|
-
// Run healing if enabled and we have discovered resources
|
|
1335
|
-
if (discoveredResources && Object.keys(discoveredResources).length > 0) {
|
|
1336
|
-
const healingReport = healVPCConfiguration(discoveredResources, AppDefinition);
|
|
875
|
+
console.log(`VPC Management Mode: ${vpcManagement}`);
|
|
1337
876
|
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
877
|
+
if (vpcManagement === 'create-new') {
|
|
878
|
+
const vpcResources = createVPCInfrastructure(AppDefinition);
|
|
879
|
+
Object.assign(definition.resources.Resources, vpcResources);
|
|
880
|
+
vpcId = { Ref: 'FriggVPC' };
|
|
881
|
+
vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds || [{ Ref: 'FriggLambdaSecurityGroup' }];
|
|
882
|
+
} else if (vpcManagement === 'use-existing') {
|
|
883
|
+
if (!AppDefinition.vpc.vpcId) {
|
|
884
|
+
throw new Error('VPC management is set to "use-existing" but no vpcId was provided');
|
|
1342
885
|
}
|
|
886
|
+
vpcId = AppDefinition.vpc.vpcId;
|
|
887
|
+
vpcConfig.securityGroupIds =
|
|
888
|
+
AppDefinition.vpc.securityGroupIds ||
|
|
889
|
+
(discoveredResources.defaultSecurityGroupId ? [discoveredResources.defaultSecurityGroupId] : []);
|
|
890
|
+
} else {
|
|
891
|
+
if (!discoveredResources.defaultVpcId) {
|
|
892
|
+
throw new Error(
|
|
893
|
+
'VPC discovery failed: No VPC found. Either set vpc.management to "create-new" or provide vpc.vpcId with "use-existing".'
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
vpcId = discoveredResources.defaultVpcId;
|
|
897
|
+
vpcConfig.securityGroupIds =
|
|
898
|
+
AppDefinition.vpc.securityGroupIds ||
|
|
899
|
+
(discoveredResources.defaultSecurityGroupId ? [discoveredResources.defaultSecurityGroupId] : []);
|
|
900
|
+
}
|
|
1343
901
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
902
|
+
const defaultSubnetManagement = vpcManagement === 'create-new' ? 'create' : 'discover';
|
|
903
|
+
let subnetManagement = AppDefinition.vpc.subnets?.management || defaultSubnetManagement;
|
|
904
|
+
console.log(`Subnet Management Mode: ${subnetManagement}`);
|
|
905
|
+
|
|
906
|
+
const effectiveVpcId = vpcId || discoveredResources.defaultVpcId;
|
|
907
|
+
if (!effectiveVpcId) {
|
|
908
|
+
throw new Error('Cannot manage subnets without a VPC ID');
|
|
909
|
+
}
|
|
1351
910
|
|
|
1352
|
-
|
|
911
|
+
if (subnetManagement === 'create') {
|
|
912
|
+
console.log('Creating new subnets...');
|
|
913
|
+
const subnetVpcId = vpcManagement === 'create-new' ? { Ref: 'FriggVPC' } : effectiveVpcId;
|
|
914
|
+
let subnet1Cidr;
|
|
915
|
+
let subnet2Cidr;
|
|
916
|
+
let publicSubnetCidr;
|
|
1353
917
|
|
|
1354
|
-
// First, establish VPC context
|
|
1355
918
|
if (vpcManagement === 'create-new') {
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
// Default security group for new VPC
|
|
1362
|
-
vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds || [
|
|
1363
|
-
{ Ref: 'FriggLambdaSecurityGroup' }
|
|
1364
|
-
];
|
|
1365
|
-
} else if (vpcManagement === 'use-existing') {
|
|
1366
|
-
// Use explicitly provided VPC
|
|
1367
|
-
if (!AppDefinition.vpc.vpcId) {
|
|
1368
|
-
throw new Error('VPC management is set to "use-existing" but no vpcId was provided');
|
|
1369
|
-
}
|
|
1370
|
-
vpcId = AppDefinition.vpc.vpcId;
|
|
1371
|
-
// Use provided security groups or try to discover default security group for the VPC
|
|
1372
|
-
vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds ||
|
|
1373
|
-
(discoveredResources.defaultSecurityGroupId ? [discoveredResources.defaultSecurityGroupId] : []);
|
|
919
|
+
const generatedCidrs = { 'Fn::Cidr': ['10.0.0.0/16', 3, 8] };
|
|
920
|
+
subnet1Cidr = { 'Fn::Select': [0, generatedCidrs] };
|
|
921
|
+
subnet2Cidr = { 'Fn::Select': [1, generatedCidrs] };
|
|
922
|
+
publicSubnetCidr = { 'Fn::Select': [2, generatedCidrs] };
|
|
1374
923
|
} else {
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
'VPC discovery failed: No VPC found. ' +
|
|
1379
|
-
'Either set vpc.management to "create-new" or provide vpc.vpcId with "use-existing".'
|
|
1380
|
-
);
|
|
1381
|
-
}
|
|
1382
|
-
vpcId = discoveredResources.defaultVpcId;
|
|
1383
|
-
vpcConfig.securityGroupIds = AppDefinition.vpc.securityGroupIds ||
|
|
1384
|
-
(discoveredResources.defaultSecurityGroupId ? [discoveredResources.defaultSecurityGroupId] : []);
|
|
924
|
+
subnet1Cidr = '172.31.240.0/24';
|
|
925
|
+
subnet2Cidr = '172.31.241.0/24';
|
|
926
|
+
publicSubnetCidr = '172.31.250.0/24';
|
|
1385
927
|
}
|
|
1386
928
|
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
929
|
+
definition.resources.Resources.FriggPrivateSubnet1 = {
|
|
930
|
+
Type: 'AWS::EC2::Subnet',
|
|
931
|
+
Properties: {
|
|
932
|
+
VpcId: subnetVpcId,
|
|
933
|
+
CidrBlock: subnet1Cidr,
|
|
934
|
+
AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
|
|
935
|
+
Tags: [
|
|
936
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-1' },
|
|
937
|
+
{ Key: 'Type', Value: 'Private' },
|
|
938
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
939
|
+
],
|
|
940
|
+
},
|
|
941
|
+
};
|
|
1392
942
|
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
943
|
+
definition.resources.Resources.FriggPrivateSubnet2 = {
|
|
944
|
+
Type: 'AWS::EC2::Subnet',
|
|
945
|
+
Properties: {
|
|
946
|
+
VpcId: subnetVpcId,
|
|
947
|
+
CidrBlock: subnet2Cidr,
|
|
948
|
+
AvailabilityZone: { 'Fn::Select': [1, { 'Fn::GetAZs': '' }] },
|
|
949
|
+
Tags: [
|
|
950
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-private-2' },
|
|
951
|
+
{ Key: 'Type', Value: 'Private' },
|
|
952
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
953
|
+
],
|
|
954
|
+
},
|
|
955
|
+
};
|
|
1398
956
|
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
957
|
+
definition.resources.Resources.FriggPublicSubnet = {
|
|
958
|
+
Type: 'AWS::EC2::Subnet',
|
|
959
|
+
Properties: {
|
|
960
|
+
VpcId: subnetVpcId,
|
|
961
|
+
CidrBlock: publicSubnetCidr,
|
|
962
|
+
MapPublicIpOnLaunch: true,
|
|
963
|
+
AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
|
|
964
|
+
Tags: [
|
|
965
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-public' },
|
|
966
|
+
{ Key: 'Type', Value: 'Public' },
|
|
967
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
968
|
+
],
|
|
969
|
+
},
|
|
970
|
+
};
|
|
1403
971
|
|
|
1404
|
-
|
|
1405
|
-
const subnetVpcId = vpcManagement === 'create-new' ? { Ref: 'FriggVPC' } : effectiveVpcId;
|
|
972
|
+
vpcConfig.subnetIds = [{ Ref: 'FriggPrivateSubnet1' }, { Ref: 'FriggPrivateSubnet2' }];
|
|
1406
973
|
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
974
|
+
if (!AppDefinition.vpc.natGateway || AppDefinition.vpc.natGateway.management === 'discover') {
|
|
975
|
+
if (vpcManagement === 'create-new' || !discoveredResources.internetGatewayId) {
|
|
976
|
+
if (!definition.resources.Resources.FriggInternetGateway) {
|
|
977
|
+
definition.resources.Resources.FriggInternetGateway = {
|
|
978
|
+
Type: 'AWS::EC2::InternetGateway',
|
|
979
|
+
Properties: {
|
|
980
|
+
Tags: [
|
|
981
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-igw' },
|
|
982
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
983
|
+
],
|
|
984
|
+
},
|
|
985
|
+
};
|
|
1411
986
|
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
publicSubnetCidr = { 'Fn::Select': [2, generatedCidrs] }; // 10.0.2.0/24
|
|
1421
|
-
} else {
|
|
1422
|
-
// For existing VPCs, use high-range /24 blocks less likely to conflict
|
|
1423
|
-
// These are in the 172.31.x.x range for default VPC or high ranges for custom VPCs
|
|
1424
|
-
subnet1Cidr = '172.31.240.0/24';
|
|
1425
|
-
subnet2Cidr = '172.31.241.0/24';
|
|
1426
|
-
publicSubnetCidr = '172.31.250.0/24';
|
|
987
|
+
definition.resources.Resources.FriggIGWAttachment = {
|
|
988
|
+
Type: 'AWS::EC2::VPCGatewayAttachment',
|
|
989
|
+
Properties: {
|
|
990
|
+
VpcId: subnetVpcId,
|
|
991
|
+
InternetGatewayId: { Ref: 'FriggInternetGateway' },
|
|
992
|
+
},
|
|
993
|
+
};
|
|
994
|
+
}
|
|
1427
995
|
}
|
|
1428
996
|
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
Type: 'AWS::EC2::Subnet',
|
|
997
|
+
definition.resources.Resources.FriggPublicRouteTable = {
|
|
998
|
+
Type: 'AWS::EC2::RouteTable',
|
|
1432
999
|
Properties: {
|
|
1433
1000
|
VpcId: subnetVpcId,
|
|
1434
|
-
CidrBlock: subnet1Cidr,
|
|
1435
|
-
AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
|
|
1436
1001
|
Tags: [
|
|
1437
|
-
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-
|
|
1438
|
-
{ Key: '
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
}
|
|
1002
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-rt' },
|
|
1003
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
1004
|
+
],
|
|
1005
|
+
},
|
|
1442
1006
|
};
|
|
1443
1007
|
|
|
1444
|
-
definition.resources.Resources.
|
|
1445
|
-
Type: 'AWS::EC2::
|
|
1008
|
+
definition.resources.Resources.FriggPublicRoute = {
|
|
1009
|
+
Type: 'AWS::EC2::Route',
|
|
1010
|
+
DependsOn: vpcManagement === 'create-new' ? 'FriggIGWAttachment' : undefined,
|
|
1446
1011
|
Properties: {
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1012
|
+
RouteTableId: { Ref: 'FriggPublicRouteTable' },
|
|
1013
|
+
DestinationCidrBlock: '0.0.0.0/0',
|
|
1014
|
+
GatewayId: discoveredResources.internetGatewayId || { Ref: 'FriggInternetGateway' },
|
|
1015
|
+
},
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
definition.resources.Resources.FriggPublicSubnetRouteTableAssociation = {
|
|
1019
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
1020
|
+
Properties: {
|
|
1021
|
+
SubnetId: { Ref: 'FriggPublicSubnet' },
|
|
1022
|
+
RouteTableId: { Ref: 'FriggPublicRouteTable' },
|
|
1023
|
+
},
|
|
1456
1024
|
};
|
|
1457
1025
|
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
Type: 'AWS::EC2::Subnet',
|
|
1026
|
+
definition.resources.Resources.FriggLambdaRouteTable = {
|
|
1027
|
+
Type: 'AWS::EC2::RouteTable',
|
|
1461
1028
|
Properties: {
|
|
1462
1029
|
VpcId: subnetVpcId,
|
|
1463
|
-
CidrBlock: publicSubnetCidr,
|
|
1464
|
-
MapPublicIpOnLaunch: true,
|
|
1465
|
-
AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
|
|
1466
1030
|
Tags: [
|
|
1467
|
-
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-
|
|
1468
|
-
{ Key: '
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
}
|
|
1031
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-rt' },
|
|
1032
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
1033
|
+
],
|
|
1034
|
+
},
|
|
1472
1035
|
};
|
|
1473
1036
|
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
if (!AppDefinition.vpc.natGateway || AppDefinition.vpc.natGateway.management === 'discover') {
|
|
1482
|
-
// Need to ensure public subnet has IGW route
|
|
1483
|
-
if (vpcManagement === 'create-new' || !discoveredResources.internetGatewayId) {
|
|
1484
|
-
// Create or reference IGW for public subnet
|
|
1485
|
-
if (!definition.resources.Resources.FriggInternetGateway) {
|
|
1486
|
-
definition.resources.Resources.FriggInternetGateway = {
|
|
1487
|
-
Type: 'AWS::EC2::InternetGateway',
|
|
1488
|
-
Properties: {
|
|
1489
|
-
Tags: [
|
|
1490
|
-
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-igw' },
|
|
1491
|
-
{ Key: 'ManagedBy', Value: 'Frigg' }
|
|
1492
|
-
]
|
|
1493
|
-
}
|
|
1494
|
-
};
|
|
1495
|
-
|
|
1496
|
-
definition.resources.Resources.FriggIGWAttachment = {
|
|
1497
|
-
Type: 'AWS::EC2::VPCGatewayAttachment',
|
|
1498
|
-
Properties: {
|
|
1499
|
-
VpcId: subnetVpcId,
|
|
1500
|
-
InternetGatewayId: { Ref: 'FriggInternetGateway' }
|
|
1501
|
-
}
|
|
1502
|
-
};
|
|
1503
|
-
}
|
|
1504
|
-
}
|
|
1505
|
-
|
|
1506
|
-
// Create public route table with IGW route
|
|
1507
|
-
definition.resources.Resources.FriggPublicRouteTable = {
|
|
1508
|
-
Type: 'AWS::EC2::RouteTable',
|
|
1509
|
-
Properties: {
|
|
1510
|
-
VpcId: subnetVpcId,
|
|
1511
|
-
Tags: [
|
|
1512
|
-
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-rt' },
|
|
1513
|
-
{ Key: 'ManagedBy', Value: 'Frigg' }
|
|
1514
|
-
]
|
|
1515
|
-
}
|
|
1516
|
-
};
|
|
1517
|
-
|
|
1518
|
-
definition.resources.Resources.FriggPublicRoute = {
|
|
1519
|
-
Type: 'AWS::EC2::Route',
|
|
1520
|
-
DependsOn: vpcManagement === 'create-new' ? 'FriggIGWAttachment' : undefined,
|
|
1521
|
-
Properties: {
|
|
1522
|
-
RouteTableId: { Ref: 'FriggPublicRouteTable' },
|
|
1523
|
-
DestinationCidrBlock: '0.0.0.0/0',
|
|
1524
|
-
GatewayId: discoveredResources.internetGatewayId || { Ref: 'FriggInternetGateway' }
|
|
1525
|
-
}
|
|
1526
|
-
};
|
|
1527
|
-
|
|
1528
|
-
// Associate public subnet with public route table
|
|
1529
|
-
definition.resources.Resources.FriggPublicSubnetRouteTableAssociation = {
|
|
1530
|
-
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
1531
|
-
Properties: {
|
|
1532
|
-
SubnetId: { Ref: 'FriggPublicSubnet' },
|
|
1533
|
-
RouteTableId: { Ref: 'FriggPublicRouteTable' }
|
|
1534
|
-
}
|
|
1535
|
-
};
|
|
1536
|
-
|
|
1537
|
-
// Create private route table for Lambda subnets
|
|
1538
|
-
definition.resources.Resources.FriggLambdaRouteTable = {
|
|
1539
|
-
Type: 'AWS::EC2::RouteTable',
|
|
1540
|
-
Properties: {
|
|
1541
|
-
VpcId: subnetVpcId,
|
|
1542
|
-
Tags: [
|
|
1543
|
-
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-rt' },
|
|
1544
|
-
{ Key: 'ManagedBy', Value: 'Frigg' }
|
|
1545
|
-
]
|
|
1546
|
-
}
|
|
1547
|
-
};
|
|
1548
|
-
|
|
1549
|
-
// Associate private subnets with route table
|
|
1550
|
-
definition.resources.Resources.FriggPrivateSubnet1RouteTableAssociation = {
|
|
1551
|
-
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
1552
|
-
Properties: {
|
|
1553
|
-
SubnetId: { Ref: 'FriggPrivateSubnet1' },
|
|
1554
|
-
RouteTableId: { Ref: 'FriggLambdaRouteTable' }
|
|
1555
|
-
}
|
|
1556
|
-
};
|
|
1037
|
+
definition.resources.Resources.FriggPrivateSubnet1RouteTableAssociation = {
|
|
1038
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
1039
|
+
Properties: {
|
|
1040
|
+
SubnetId: { Ref: 'FriggPrivateSubnet1' },
|
|
1041
|
+
RouteTableId: { Ref: 'FriggLambdaRouteTable' },
|
|
1042
|
+
},
|
|
1043
|
+
};
|
|
1557
1044
|
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1045
|
+
definition.resources.Resources.FriggPrivateSubnet2RouteTableAssociation = {
|
|
1046
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
1047
|
+
Properties: {
|
|
1048
|
+
SubnetId: { Ref: 'FriggPrivateSubnet2' },
|
|
1049
|
+
RouteTableId: { Ref: 'FriggLambdaRouteTable' },
|
|
1050
|
+
},
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
1053
|
+
} else if (subnetManagement === 'use-existing') {
|
|
1054
|
+
if (!AppDefinition.vpc.subnets?.ids || AppDefinition.vpc.subnets.ids.length < 2) {
|
|
1055
|
+
throw new Error(
|
|
1056
|
+
'Subnet management is "use-existing" but less than 2 subnet IDs provided. Provide at least 2 subnet IDs in vpc.subnets.ids.'
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
vpcConfig.subnetIds = AppDefinition.vpc.subnets.ids;
|
|
1060
|
+
} else {
|
|
1061
|
+
vpcConfig.subnetIds =
|
|
1062
|
+
AppDefinition.vpc.subnets?.ids?.length > 0
|
|
1063
|
+
? AppDefinition.vpc.subnets.ids
|
|
1064
|
+
: discoveredResources.privateSubnetId1 && discoveredResources.privateSubnetId2
|
|
1065
|
+
? [discoveredResources.privateSubnetId1, discoveredResources.privateSubnetId2]
|
|
1066
|
+
: [];
|
|
1067
|
+
|
|
1068
|
+
if (vpcConfig.subnetIds.length < 2) {
|
|
1069
|
+
if (AppDefinition.vpc.selfHeal) {
|
|
1070
|
+
console.log('No subnets found but self-heal enabled - creating minimal subnet setup');
|
|
1071
|
+
subnetManagement = 'create';
|
|
1072
|
+
discoveredResources.createSubnets = true;
|
|
1073
|
+
} else {
|
|
1569
1074
|
throw new Error(
|
|
1570
|
-
'
|
|
1571
|
-
'Provide at least 2 subnet IDs in vpc.subnets.ids.'
|
|
1075
|
+
'No subnets discovered and subnets.management is "discover". Either enable vpc.selfHeal, set subnets.management to "create", or provide subnet IDs.'
|
|
1572
1076
|
);
|
|
1573
1077
|
}
|
|
1574
|
-
vpcConfig.subnetIds = AppDefinition.vpc.subnets.ids;
|
|
1575
|
-
} else {
|
|
1576
|
-
// Discover mode (default)
|
|
1577
|
-
vpcConfig.subnetIds =
|
|
1578
|
-
AppDefinition.vpc.subnets?.ids?.length > 0
|
|
1579
|
-
? AppDefinition.vpc.subnets.ids
|
|
1580
|
-
: (discoveredResources.privateSubnetId1 &&
|
|
1581
|
-
discoveredResources.privateSubnetId2
|
|
1582
|
-
? [
|
|
1583
|
-
discoveredResources.privateSubnetId1,
|
|
1584
|
-
discoveredResources.privateSubnetId2,
|
|
1585
|
-
]
|
|
1586
|
-
: []);
|
|
1587
|
-
|
|
1588
|
-
if (vpcConfig.subnetIds.length < 2) {
|
|
1589
|
-
if (AppDefinition.vpc.selfHeal) {
|
|
1590
|
-
console.log('No subnets found but self-heal enabled - creating minimal subnet setup');
|
|
1591
|
-
// Fall back to creating subnets
|
|
1592
|
-
subnetManagement = 'create';
|
|
1593
|
-
// Recursion would be complex here, so just set flag
|
|
1594
|
-
discoveredResources.createSubnets = true;
|
|
1595
|
-
} else {
|
|
1596
|
-
throw new Error(
|
|
1597
|
-
'No subnets discovered and subnets.management is "discover". ' +
|
|
1598
|
-
'Either enable vpc.selfHeal, set subnets.management to "create", or provide subnet IDs.'
|
|
1599
|
-
);
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
1078
|
}
|
|
1079
|
+
}
|
|
1603
1080
|
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1081
|
+
if (subnetManagement === 'create' && discoveredResources.createSubnets) {
|
|
1082
|
+
definition.resources.Resources.FriggLambdaRouteTable =
|
|
1083
|
+
definition.resources.Resources.FriggLambdaRouteTable || {
|
|
1084
|
+
Type: 'AWS::EC2::RouteTable',
|
|
1085
|
+
Properties: {
|
|
1086
|
+
VpcId: effectiveVpcId,
|
|
1087
|
+
Tags: [
|
|
1088
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-rt' },
|
|
1089
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
1090
|
+
{ Key: 'Environment', Value: '${self:provider.stage}' },
|
|
1091
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
1092
|
+
],
|
|
1093
|
+
},
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1619
1096
|
|
|
1620
|
-
|
|
1097
|
+
if (
|
|
1098
|
+
vpcConfig.subnetIds.length >= 2 &&
|
|
1099
|
+
vpcConfig.securityGroupIds.length > 0
|
|
1100
|
+
) {
|
|
1101
|
+
definition.provider.vpc = vpcConfig;
|
|
1621
1102
|
|
|
1622
|
-
|
|
1103
|
+
const natGatewayManagement = AppDefinition.vpc.natGateway?.management || 'discover';
|
|
1104
|
+
let needsNewNatGateway =
|
|
1105
|
+
natGatewayManagement === 'createAndManage' ||
|
|
1106
|
+
discoveredResources.needsNewNatGateway === true;
|
|
1623
1107
|
|
|
1624
|
-
|
|
1625
|
-
let reuseExistingNatGateway = false;
|
|
1626
|
-
let useExistingEip = false;
|
|
1108
|
+
console.log('needsNewNatGateway', needsNewNatGateway);
|
|
1627
1109
|
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
console.log(
|
|
1631
|
-
'Create mode: Creating dedicated EIP, public subnet, and NAT Gateway...'
|
|
1632
|
-
);
|
|
1110
|
+
let reuseExistingNatGateway = false;
|
|
1111
|
+
let useExistingEip = false;
|
|
1633
1112
|
|
|
1634
|
-
|
|
1113
|
+
if (needsNewNatGateway) {
|
|
1114
|
+
console.log('Create mode: Creating dedicated EIP, public subnet, and NAT Gateway...');
|
|
1635
1115
|
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1116
|
+
if (
|
|
1117
|
+
discoveredResources.existingNatGatewayId &&
|
|
1118
|
+
discoveredResources.existingElasticIpAllocationId
|
|
1119
|
+
) {
|
|
1120
|
+
console.log('Found existing Frigg-managed NAT Gateway and EIP');
|
|
1121
|
+
if (!discoveredResources.natGatewayInPrivateSubnet) {
|
|
1122
|
+
console.log('✅ Existing NAT Gateway is in PUBLIC subnet, will reuse it');
|
|
1123
|
+
reuseExistingNatGateway = true;
|
|
1124
|
+
} else {
|
|
1125
|
+
console.log('❌ NAT Gateway is in PRIVATE subnet - MUST create new one in PUBLIC subnet');
|
|
1126
|
+
if (AppDefinition.vpc.selfHeal) {
|
|
1127
|
+
console.log('Self-heal enabled: Creating new NAT Gateway in PUBLIC subnet');
|
|
1128
|
+
reuseExistingNatGateway = false;
|
|
1129
|
+
useExistingEip = false;
|
|
1130
|
+
discoveredResources.needsCleanup = true;
|
|
1647
1131
|
} else {
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
if (AppDefinition.vpc.selfHeal) {
|
|
1652
|
-
console.log('Self-heal enabled: Creating new NAT Gateway in PUBLIC subnet');
|
|
1653
|
-
// Force creation of new NAT in public subnet
|
|
1654
|
-
reuseExistingNatGateway = false;
|
|
1655
|
-
// Cannot reuse the EIP since it's associated with wrong NAT
|
|
1656
|
-
useExistingEip = false;
|
|
1657
|
-
// Mark for cleanup recommendations
|
|
1658
|
-
discoveredResources.needsCleanup = true;
|
|
1659
|
-
} else {
|
|
1660
|
-
throw new Error(
|
|
1661
|
-
'CRITICAL: NAT Gateway is in PRIVATE subnet (will not work!). ' +
|
|
1662
|
-
'Enable vpc.selfHeal to auto-fix or set natGateway.management to "createAndManage".'
|
|
1663
|
-
);
|
|
1664
|
-
}
|
|
1132
|
+
throw new Error(
|
|
1133
|
+
'CRITICAL: NAT Gateway is in PRIVATE subnet (will not work!). Enable vpc.selfHeal to auto-fix or set natGateway.management to "createAndManage".'
|
|
1134
|
+
);
|
|
1665
1135
|
}
|
|
1666
|
-
} else if (discoveredResources.existingElasticIpAllocationId &&
|
|
1667
|
-
!discoveredResources.existingNatGatewayId) {
|
|
1668
|
-
// We have an EIP but no NAT Gateway - can reuse the EIP
|
|
1669
|
-
console.log('Found orphaned EIP, will reuse it for new NAT Gateway in PUBLIC subnet');
|
|
1670
|
-
useExistingEip = true;
|
|
1671
1136
|
}
|
|
1137
|
+
} else if (
|
|
1138
|
+
discoveredResources.existingElasticIpAllocationId &&
|
|
1139
|
+
!discoveredResources.existingNatGatewayId
|
|
1140
|
+
) {
|
|
1141
|
+
console.log('Found orphaned EIP, will reuse it for new NAT Gateway in PUBLIC subnet');
|
|
1142
|
+
useExistingEip = true;
|
|
1143
|
+
}
|
|
1672
1144
|
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
{
|
|
1693
|
-
Key: 'ManagedBy',
|
|
1694
|
-
Value: 'Frigg',
|
|
1695
|
-
},
|
|
1696
|
-
{
|
|
1697
|
-
Key: 'Service',
|
|
1698
|
-
Value: '${self:service}',
|
|
1699
|
-
},
|
|
1700
|
-
{
|
|
1701
|
-
Key: 'Stage',
|
|
1702
|
-
Value: '${self:provider.stage}',
|
|
1703
|
-
},
|
|
1704
|
-
],
|
|
1705
|
-
},
|
|
1706
|
-
};
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
// Create public subnet if needed (for NAT Gateway placement)
|
|
1710
|
-
if (!discoveredResources.publicSubnetId || discoveredResources.createPublicSubnet) {
|
|
1711
|
-
console.log(
|
|
1712
|
-
'No public subnet found, creating one for NAT Gateway placement...'
|
|
1713
|
-
);
|
|
1145
|
+
if (reuseExistingNatGateway) {
|
|
1146
|
+
console.log('Reusing existing NAT Gateway - skipping resource creation');
|
|
1147
|
+
} else {
|
|
1148
|
+
if (!useExistingEip) {
|
|
1149
|
+
definition.resources.Resources.FriggNATGatewayEIP = {
|
|
1150
|
+
Type: 'AWS::EC2::EIP',
|
|
1151
|
+
DeletionPolicy: 'Retain',
|
|
1152
|
+
UpdateReplacePolicy: 'Retain',
|
|
1153
|
+
Properties: {
|
|
1154
|
+
Domain: 'vpc',
|
|
1155
|
+
Tags: [
|
|
1156
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-eip' },
|
|
1157
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
1158
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
1159
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
1160
|
+
],
|
|
1161
|
+
},
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1714
1164
|
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1165
|
+
if (!discoveredResources.publicSubnetId) {
|
|
1166
|
+
if (discoveredResources.internetGatewayId) {
|
|
1167
|
+
console.log('Reusing existing Internet Gateway for NAT Gateway');
|
|
1168
|
+
} else {
|
|
1169
|
+
definition.resources.Resources.FriggInternetGateway =
|
|
1170
|
+
definition.resources.Resources.FriggInternetGateway || {
|
|
1719
1171
|
Type: 'AWS::EC2::InternetGateway',
|
|
1720
1172
|
Properties: {
|
|
1721
1173
|
Tags: [
|
|
1722
|
-
{
|
|
1723
|
-
|
|
1724
|
-
Value: '${self:service}-${self:provider.stage}-igw',
|
|
1725
|
-
},
|
|
1174
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-igw' },
|
|
1175
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
1726
1176
|
],
|
|
1727
1177
|
},
|
|
1728
1178
|
};
|
|
1729
1179
|
|
|
1730
|
-
|
|
1731
|
-
{
|
|
1180
|
+
definition.resources.Resources.FriggIGWAttachment =
|
|
1181
|
+
definition.resources.Resources.FriggIGWAttachment || {
|
|
1732
1182
|
Type: 'AWS::EC2::VPCGatewayAttachment',
|
|
1733
1183
|
Properties: {
|
|
1734
1184
|
VpcId: discoveredResources.defaultVpcId,
|
|
1735
|
-
InternetGatewayId: {
|
|
1736
|
-
Ref: 'FriggInternetGateway',
|
|
1737
|
-
},
|
|
1185
|
+
InternetGatewayId: { Ref: 'FriggInternetGateway' },
|
|
1738
1186
|
},
|
|
1739
1187
|
};
|
|
1740
|
-
}
|
|
1741
|
-
|
|
1742
|
-
// Create a small public subnet for NAT Gateway
|
|
1743
|
-
definition.resources.Resources.FriggPublicSubnet = {
|
|
1744
|
-
Type: 'AWS::EC2::Subnet',
|
|
1745
|
-
Properties: {
|
|
1746
|
-
VpcId: discoveredResources.defaultVpcId,
|
|
1747
|
-
CidrBlock:
|
|
1748
|
-
AppDefinition.vpc.natGateway
|
|
1749
|
-
?.publicSubnetCidr || '172.31.250.0/24',
|
|
1750
|
-
AvailabilityZone: {
|
|
1751
|
-
'Fn::Select': [0, { 'Fn::GetAZs': '' }],
|
|
1752
|
-
},
|
|
1753
|
-
MapPublicIpOnLaunch: true,
|
|
1754
|
-
Tags: [
|
|
1755
|
-
{
|
|
1756
|
-
Key: 'Name',
|
|
1757
|
-
Value: '${self:service}-${self:provider.stage}-public-subnet',
|
|
1758
|
-
},
|
|
1759
|
-
{
|
|
1760
|
-
Key: 'Type',
|
|
1761
|
-
Value: 'Public',
|
|
1762
|
-
},
|
|
1763
|
-
],
|
|
1764
|
-
},
|
|
1765
|
-
};
|
|
1766
|
-
|
|
1767
|
-
// Create route table for public subnet
|
|
1768
|
-
definition.resources.Resources.FriggPublicRouteTable = {
|
|
1769
|
-
Type: 'AWS::EC2::RouteTable',
|
|
1770
|
-
Properties: {
|
|
1771
|
-
VpcId: discoveredResources.defaultVpcId,
|
|
1772
|
-
Tags: [
|
|
1773
|
-
{
|
|
1774
|
-
Key: 'Name',
|
|
1775
|
-
Value: '${self:service}-${self:provider.stage}-public-rt',
|
|
1776
|
-
},
|
|
1777
|
-
],
|
|
1778
|
-
},
|
|
1779
|
-
};
|
|
1780
|
-
|
|
1781
|
-
// Add route to Internet Gateway
|
|
1782
|
-
definition.resources.Resources.FriggPublicRoute = {
|
|
1783
|
-
Type: 'AWS::EC2::Route',
|
|
1784
|
-
DependsOn: discoveredResources.internetGatewayId
|
|
1785
|
-
? []
|
|
1786
|
-
: 'FriggIGWAttachment',
|
|
1787
|
-
Properties: {
|
|
1788
|
-
RouteTableId: { Ref: 'FriggPublicRouteTable' },
|
|
1789
|
-
DestinationCidrBlock: '0.0.0.0/0',
|
|
1790
|
-
GatewayId:
|
|
1791
|
-
discoveredResources.internetGatewayId || {
|
|
1792
|
-
Ref: 'FriggInternetGateway',
|
|
1793
|
-
},
|
|
1794
|
-
},
|
|
1795
|
-
};
|
|
1796
|
-
|
|
1797
|
-
// Associate public subnet with public route table
|
|
1798
|
-
definition.resources.Resources.FriggPublicSubnetRouteTableAssociation =
|
|
1799
|
-
{
|
|
1800
|
-
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
1801
|
-
Properties: {
|
|
1802
|
-
SubnetId: { Ref: 'FriggPublicSubnet' },
|
|
1803
|
-
RouteTableId: {
|
|
1804
|
-
Ref: 'FriggPublicRouteTable',
|
|
1805
|
-
},
|
|
1806
|
-
},
|
|
1807
|
-
};
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
// Create NAT Gateway only if not reusing existing one
|
|
1811
|
-
definition.resources.Resources.FriggNATGateway = {
|
|
1812
|
-
Type: 'AWS::EC2::NatGateway',
|
|
1813
|
-
DeletionPolicy: 'Retain', // Prevent accidental deletion
|
|
1814
|
-
UpdateReplacePolicy: 'Retain', // Prevent replacement during updates
|
|
1815
|
-
Properties: {
|
|
1816
|
-
AllocationId: useExistingEip ?
|
|
1817
|
-
discoveredResources.existingElasticIpAllocationId :
|
|
1818
|
-
{
|
|
1819
|
-
'Fn::GetAtt': [
|
|
1820
|
-
'FriggNATGatewayEIP',
|
|
1821
|
-
'AllocationId',
|
|
1822
|
-
],
|
|
1823
|
-
},
|
|
1824
|
-
SubnetId: discoveredResources.publicSubnetId || {
|
|
1825
|
-
Ref: 'FriggPublicSubnet',
|
|
1826
|
-
},
|
|
1827
|
-
Tags: [
|
|
1828
|
-
{
|
|
1829
|
-
Key: 'Name',
|
|
1830
|
-
Value: '${self:service}-${self:provider.stage}-nat-gateway',
|
|
1831
|
-
},
|
|
1832
|
-
{
|
|
1833
|
-
Key: 'ManagedBy',
|
|
1834
|
-
Value: 'Frigg',
|
|
1835
|
-
},
|
|
1836
|
-
{
|
|
1837
|
-
Key: 'Service',
|
|
1838
|
-
Value: '${self:service}',
|
|
1839
|
-
},
|
|
1840
|
-
{
|
|
1841
|
-
Key: 'Stage',
|
|
1842
|
-
Value: '${self:provider.stage}',
|
|
1843
|
-
},
|
|
1844
|
-
],
|
|
1845
|
-
},
|
|
1846
|
-
};
|
|
1847
|
-
}
|
|
1848
|
-
} else if (natGatewayManagement === 'discover' || natGatewayManagement === 'useExisting') {
|
|
1849
|
-
// Discover or use existing NAT Gateway
|
|
1850
|
-
if (natGatewayManagement === 'useExisting' && AppDefinition.vpc.natGateway?.id) {
|
|
1851
|
-
// Use explicitly provided NAT Gateway ID
|
|
1852
|
-
console.log(`Using explicitly provided NAT Gateway: ${AppDefinition.vpc.natGateway.id}`);
|
|
1853
|
-
discoveredResources.existingNatGatewayId = AppDefinition.vpc.natGateway.id;
|
|
1854
|
-
}
|
|
1855
|
-
|
|
1856
|
-
if (discoveredResources.existingNatGatewayId) {
|
|
1857
|
-
console.log('discoveredResources.existingNatGatewayId', discoveredResources.existingNatGatewayId);
|
|
1858
|
-
|
|
1859
|
-
// CRITICAL: Verify NAT Gateway is in PUBLIC subnet
|
|
1860
|
-
if (discoveredResources.natGatewayInPrivateSubnet) {
|
|
1861
|
-
// NAT is in PRIVATE subnet - CANNOT use it
|
|
1862
|
-
console.log('❌ CRITICAL: NAT Gateway is in PRIVATE subnet - Internet connectivity will NOT work!');
|
|
1863
|
-
|
|
1864
|
-
if (AppDefinition.vpc.selfHeal === true) {
|
|
1865
|
-
console.log('Self-heal enabled: Will create new NAT Gateway in PUBLIC subnet');
|
|
1866
|
-
// Force creation of new NAT Gateway in public subnet
|
|
1867
|
-
needsNewNatGateway = true;
|
|
1868
|
-
discoveredResources.existingNatGatewayId = null; // Don't use the misconfigured NAT
|
|
1869
|
-
// Ensure we have a public subnet for the NAT
|
|
1870
|
-
if (!discoveredResources.publicSubnetId) {
|
|
1871
|
-
console.log('No public subnet found - will create one for NAT Gateway');
|
|
1872
|
-
discoveredResources.createPublicSubnet = true;
|
|
1873
|
-
}
|
|
1874
|
-
} else {
|
|
1875
|
-
throw new Error(
|
|
1876
|
-
'CRITICAL: NAT Gateway is in PRIVATE subnet and will NOT provide internet connectivity! ' +
|
|
1877
|
-
'Options: 1) Enable vpc.selfHeal to auto-create proper NAT, ' +
|
|
1878
|
-
'2) Set natGateway.management to "createAndManage", or ' +
|
|
1879
|
-
'3) Manually fix the NAT Gateway placement.'
|
|
1880
|
-
);
|
|
1881
|
-
}
|
|
1882
|
-
} else {
|
|
1883
|
-
// NAT is correctly in public subnet
|
|
1884
|
-
console.log('✅ NAT Gateway is correctly placed in PUBLIC subnet');
|
|
1885
|
-
}
|
|
1886
|
-
} else {
|
|
1887
|
-
// No existing NAT Gateway found
|
|
1888
|
-
if (natGatewayManagement === 'useExisting') {
|
|
1889
|
-
throw new Error(
|
|
1890
|
-
'NAT Gateway management set to "useExisting" but no NAT Gateway found. ' +
|
|
1891
|
-
'Either provide natGateway.id or change management to "discover" or "createAndManage".'
|
|
1892
|
-
);
|
|
1893
|
-
} else if (AppDefinition.vpc.selfHeal === true) {
|
|
1894
|
-
// Self-healing enabled, create a new NAT Gateway
|
|
1895
|
-
console.log('No NAT Gateway found but self-healing enabled - creating new NAT Gateway in PUBLIC subnet');
|
|
1896
|
-
needsNewNatGateway = true;
|
|
1897
|
-
// Ensure we have a public subnet for the NAT
|
|
1898
|
-
if (!discoveredResources.publicSubnetId) {
|
|
1899
|
-
console.log('No public subnet found - will create one for NAT Gateway');
|
|
1900
|
-
discoveredResources.createPublicSubnet = true;
|
|
1901
|
-
}
|
|
1902
|
-
} else {
|
|
1903
|
-
throw new Error(
|
|
1904
|
-
'No existing NAT Gateway found in discovery mode. ' +
|
|
1905
|
-
'Set natGateway.management to "createAndManage" to create a new NAT Gateway.'
|
|
1906
|
-
);
|
|
1907
|
-
}
|
|
1908
1188
|
}
|
|
1909
|
-
}
|
|
1910
|
-
|
|
1911
|
-
// ALWAYS create the route table resource in CloudFormation for consistency
|
|
1912
|
-
// Use DeletionPolicy: Retain to prevent deletion when removed from template
|
|
1913
|
-
// This ensures CloudFormation maintains consistent state management
|
|
1914
|
-
console.log('Setting up route table for Lambda subnets');
|
|
1915
|
-
|
|
1916
|
-
definition.resources.Resources.FriggLambdaRouteTable = {
|
|
1917
|
-
Type: 'AWS::EC2::RouteTable',
|
|
1918
|
-
DeletionPolicy: 'Retain', // Critical: Prevents deletion when resource is removed
|
|
1919
|
-
UpdateReplacePolicy: 'Retain', // Prevents replacement during stack updates
|
|
1920
|
-
Properties: {
|
|
1921
|
-
VpcId: discoveredResources.defaultVpcId || {
|
|
1922
|
-
Ref: 'FriggVPC',
|
|
1923
|
-
},
|
|
1924
|
-
Tags: [
|
|
1925
|
-
{
|
|
1926
|
-
Key: 'Name',
|
|
1927
|
-
Value: '${self:service}-${self:provider.stage}-lambda-rt',
|
|
1928
|
-
},
|
|
1929
|
-
{
|
|
1930
|
-
Key: 'ManagedBy',
|
|
1931
|
-
Value: 'Frigg',
|
|
1932
|
-
},
|
|
1933
|
-
{
|
|
1934
|
-
Key: 'Environment',
|
|
1935
|
-
Value: '${self:provider.stage}',
|
|
1936
|
-
},
|
|
1937
|
-
{
|
|
1938
|
-
Key: 'Service',
|
|
1939
|
-
Value: '${self:service}',
|
|
1940
|
-
},
|
|
1941
|
-
],
|
|
1942
|
-
},
|
|
1943
|
-
};
|
|
1944
|
-
|
|
1945
|
-
// Always use CloudFormation reference for consistency
|
|
1946
|
-
const routeTableId = { Ref: 'FriggLambdaRouteTable' };
|
|
1947
|
-
|
|
1948
|
-
// Determine which NAT Gateway ID to use for routing
|
|
1949
|
-
let natGatewayIdForRoute;
|
|
1950
|
-
|
|
1951
|
-
if (reuseExistingNatGateway) {
|
|
1952
|
-
// Use the existing NAT Gateway that we're reusing
|
|
1953
|
-
natGatewayIdForRoute = discoveredResources.existingNatGatewayId;
|
|
1954
|
-
console.log(`Using discovered NAT Gateway for routing: ${natGatewayIdForRoute}`);
|
|
1955
|
-
} else if (needsNewNatGateway && !reuseExistingNatGateway) {
|
|
1956
|
-
// Reference the new NAT Gateway being created
|
|
1957
|
-
natGatewayIdForRoute = { Ref: 'FriggNATGateway' };
|
|
1958
|
-
console.log('Using newly created NAT Gateway for routing');
|
|
1959
|
-
} else if (discoveredResources.existingNatGatewayId) {
|
|
1960
|
-
// Use the existing NAT Gateway ID
|
|
1961
|
-
natGatewayIdForRoute = discoveredResources.existingNatGatewayId;
|
|
1962
|
-
console.log(`Using discovered NAT Gateway for routing: ${natGatewayIdForRoute}`);
|
|
1963
|
-
} else if (AppDefinition.vpc.natGateway?.id) {
|
|
1964
|
-
// Use explicitly provided NAT Gateway ID
|
|
1965
|
-
natGatewayIdForRoute = AppDefinition.vpc.natGateway.id;
|
|
1966
|
-
console.log(`Using explicitly provided NAT Gateway for routing: ${natGatewayIdForRoute}`);
|
|
1967
|
-
} else if (AppDefinition.vpc.selfHeal === true) {
|
|
1968
|
-
// Self-healing enabled but no NAT Gateway - skip NAT route
|
|
1969
|
-
natGatewayIdForRoute = null;
|
|
1970
|
-
console.log('No NAT Gateway available - skipping NAT route creation');
|
|
1971
|
-
} else {
|
|
1972
|
-
throw new Error(
|
|
1973
|
-
'Unable to determine NAT Gateway ID for routing. ' +
|
|
1974
|
-
'Please check your configuration.'
|
|
1975
|
-
);
|
|
1976
|
-
}
|
|
1977
1189
|
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
if (natGatewayIdForRoute) {
|
|
1981
|
-
console.log(`Configuring NAT route: 0.0.0.0/0 → ${natGatewayIdForRoute}`);
|
|
1982
|
-
definition.resources.Resources.FriggNATRoute = {
|
|
1983
|
-
Type: 'AWS::EC2::Route',
|
|
1984
|
-
DependsOn: 'FriggLambdaRouteTable',
|
|
1190
|
+
definition.resources.Resources.FriggPublicSubnet = {
|
|
1191
|
+
Type: 'AWS::EC2::Subnet',
|
|
1985
1192
|
Properties: {
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
// Associate Lambda subnets with NAT Gateway route table
|
|
1996
|
-
// CRITICAL: This fixes the "NAT Gateway in private subnet" issue by ensuring correct routing
|
|
1997
|
-
if (AppDefinition.vpc.selfHeal === true) {
|
|
1998
|
-
console.log('✅ Self-healing: Ensuring subnets have correct route table associations');
|
|
1999
|
-
// In self-heal mode, we force the associations even if they might conflict
|
|
2000
|
-
// CloudFormation will automatically disassociate from old route table first
|
|
2001
|
-
}
|
|
2002
|
-
|
|
2003
|
-
// ALWAYS create subnet associations to ensure correct routing
|
|
2004
|
-
// CloudFormation will handle existing associations gracefully
|
|
2005
|
-
// Only create associations for discovered subnets (not for Refs)
|
|
2006
|
-
if (typeof vpcConfig.subnetIds[0] === 'string') {
|
|
2007
|
-
definition.resources.Resources.FriggSubnet1RouteAssociation = {
|
|
2008
|
-
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
2009
|
-
Properties: {
|
|
2010
|
-
SubnetId: vpcConfig.subnetIds[0],
|
|
2011
|
-
RouteTableId: routeTableId,
|
|
1193
|
+
VpcId: discoveredResources.defaultVpcId,
|
|
1194
|
+
CidrBlock:
|
|
1195
|
+
AppDefinition.vpc.natGateway?.publicSubnetCidr || '172.31.250.0/24',
|
|
1196
|
+
AvailabilityZone: { 'Fn::Select': [0, { 'Fn::GetAZs': '' }] },
|
|
1197
|
+
MapPublicIpOnLaunch: true,
|
|
1198
|
+
Tags: [
|
|
1199
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-subnet' },
|
|
1200
|
+
{ Key: 'Type', Value: 'Public' },
|
|
1201
|
+
],
|
|
2012
1202
|
},
|
|
2013
|
-
DependsOn: 'FriggLambdaRouteTable',
|
|
2014
1203
|
};
|
|
2015
|
-
}
|
|
2016
1204
|
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
1205
|
+
definition.resources.Resources.FriggPublicRouteTable = {
|
|
1206
|
+
Type: 'AWS::EC2::RouteTable',
|
|
2020
1207
|
Properties: {
|
|
2021
|
-
|
|
2022
|
-
|
|
1208
|
+
VpcId: discoveredResources.defaultVpcId,
|
|
1209
|
+
Tags: [
|
|
1210
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-public-rt' },
|
|
1211
|
+
],
|
|
2023
1212
|
},
|
|
2024
|
-
DependsOn: 'FriggLambdaRouteTable',
|
|
2025
1213
|
};
|
|
2026
|
-
}
|
|
2027
1214
|
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
1215
|
+
definition.resources.Resources.FriggPublicRoute = {
|
|
1216
|
+
Type: 'AWS::EC2::Route',
|
|
1217
|
+
DependsOn: discoveredResources.internetGatewayId ? [] : 'FriggIGWAttachment',
|
|
2032
1218
|
Properties: {
|
|
2033
|
-
|
|
2034
|
-
|
|
1219
|
+
RouteTableId: { Ref: 'FriggPublicRouteTable' },
|
|
1220
|
+
DestinationCidrBlock: '0.0.0.0/0',
|
|
1221
|
+
GatewayId: discoveredResources.internetGatewayId || { Ref: 'FriggInternetGateway' },
|
|
2035
1222
|
},
|
|
2036
|
-
DependsOn: ['FriggLambdaRouteTable', vpcConfig.subnetIds[0].Ref],
|
|
2037
1223
|
};
|
|
2038
|
-
}
|
|
2039
1224
|
|
|
2040
|
-
|
|
2041
|
-
definition.resources.Resources.FriggNewSubnet2RouteAssociation = {
|
|
1225
|
+
definition.resources.Resources.FriggPublicSubnetRouteTableAssociation = {
|
|
2042
1226
|
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
2043
1227
|
Properties: {
|
|
2044
|
-
SubnetId:
|
|
2045
|
-
RouteTableId:
|
|
2046
|
-
},
|
|
2047
|
-
DependsOn: ['FriggLambdaRouteTable', vpcConfig.subnetIds[1].Ref],
|
|
2048
|
-
};
|
|
2049
|
-
}
|
|
2050
|
-
|
|
2051
|
-
// Add VPC endpoints for AWS service optimization
|
|
2052
|
-
// ALWAYS create these to ensure Lambda functions have optimized access to AWS services
|
|
2053
|
-
if (AppDefinition.vpc.enableVPCEndpoints !== false) {
|
|
2054
|
-
definition.resources.Resources.VPCEndpointS3 = {
|
|
2055
|
-
Type: 'AWS::EC2::VPCEndpoint',
|
|
2056
|
-
Properties: {
|
|
2057
|
-
VpcId: discoveredResources.defaultVpcId,
|
|
2058
|
-
ServiceName:
|
|
2059
|
-
'com.amazonaws.${self:provider.region}.s3',
|
|
2060
|
-
VpcEndpointType: 'Gateway',
|
|
2061
|
-
RouteTableIds: [routeTableId],
|
|
2062
|
-
},
|
|
2063
|
-
};
|
|
2064
|
-
|
|
2065
|
-
definition.resources.Resources.VPCEndpointDynamoDB = {
|
|
2066
|
-
Type: 'AWS::EC2::VPCEndpoint',
|
|
2067
|
-
Properties: {
|
|
2068
|
-
VpcId: discoveredResources.defaultVpcId,
|
|
2069
|
-
ServiceName:
|
|
2070
|
-
'com.amazonaws.${self:provider.region}.dynamodb',
|
|
2071
|
-
VpcEndpointType: 'Gateway',
|
|
2072
|
-
RouteTableIds: [routeTableId],
|
|
1228
|
+
SubnetId: { Ref: 'FriggPublicSubnet' },
|
|
1229
|
+
RouteTableId: { Ref: 'FriggPublicRouteTable' },
|
|
2073
1230
|
},
|
|
2074
1231
|
};
|
|
2075
1232
|
}
|
|
2076
1233
|
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
1234
|
+
definition.resources.Resources.FriggNATGateway = {
|
|
1235
|
+
Type: 'AWS::EC2::NatGateway',
|
|
1236
|
+
DeletionPolicy: 'Retain',
|
|
1237
|
+
UpdateReplacePolicy: 'Retain',
|
|
1238
|
+
Properties: {
|
|
1239
|
+
AllocationId: useExistingEip
|
|
1240
|
+
? discoveredResources.existingElasticIpAllocationId
|
|
1241
|
+
: { 'Fn::GetAtt': ['FriggNATGatewayEIP', 'AllocationId'] },
|
|
1242
|
+
SubnetId:
|
|
1243
|
+
discoveredResources.publicSubnetId || { Ref: 'FriggPublicSubnet' },
|
|
1244
|
+
Tags: [
|
|
1245
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-nat-gateway' },
|
|
1246
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
1247
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
1248
|
+
{ Key: 'Stage', Value: '${self:provider.stage}' },
|
|
1249
|
+
],
|
|
1250
|
+
},
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
} else if (
|
|
1254
|
+
natGatewayManagement === 'discover' ||
|
|
1255
|
+
natGatewayManagement === 'useExisting'
|
|
1256
|
+
) {
|
|
1257
|
+
if (natGatewayManagement === 'useExisting' && AppDefinition.vpc.natGateway?.id) {
|
|
1258
|
+
console.log(`Using explicitly provided NAT Gateway: ${AppDefinition.vpc.natGateway.id}`);
|
|
1259
|
+
discoveredResources.existingNatGatewayId = AppDefinition.vpc.natGateway.id;
|
|
1260
|
+
}
|
|
2087
1261
|
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
// Build ingress rules based on what we have
|
|
2094
|
-
const vpcEndpointIngressRules = [];
|
|
2095
|
-
|
|
2096
|
-
// CRITICAL: Allow from Lambda's security group (preferred method)
|
|
2097
|
-
if (vpcConfig.securityGroupIds && vpcConfig.securityGroupIds.length > 0) {
|
|
2098
|
-
// If we have the Lambda security group, reference it directly
|
|
2099
|
-
const lambdaSgId = vpcConfig.securityGroupIds[0];
|
|
2100
|
-
if (typeof lambdaSgId === 'string') {
|
|
2101
|
-
// It's a discovered security group ID
|
|
2102
|
-
vpcEndpointIngressRules.push({
|
|
2103
|
-
IpProtocol: 'tcp',
|
|
2104
|
-
FromPort: 443,
|
|
2105
|
-
ToPort: 443,
|
|
2106
|
-
SourceSecurityGroupId: lambdaSgId,
|
|
2107
|
-
Description: 'HTTPS from Lambda security group',
|
|
2108
|
-
});
|
|
2109
|
-
} else if (lambdaSgId && lambdaSgId.Ref) {
|
|
2110
|
-
// It's a CloudFormation reference
|
|
2111
|
-
vpcEndpointIngressRules.push({
|
|
2112
|
-
IpProtocol: 'tcp',
|
|
2113
|
-
FromPort: 443,
|
|
2114
|
-
ToPort: 443,
|
|
2115
|
-
SourceSecurityGroupId: lambdaSgId,
|
|
2116
|
-
Description: 'HTTPS from Lambda security group',
|
|
2117
|
-
});
|
|
2118
|
-
}
|
|
2119
|
-
}
|
|
2120
|
-
|
|
2121
|
-
// Fallback: If we don't have Lambda SG, use VPC CIDR
|
|
2122
|
-
if (vpcEndpointIngressRules.length === 0 && discoveredResources.vpcCidr) {
|
|
2123
|
-
vpcEndpointIngressRules.push({
|
|
2124
|
-
IpProtocol: 'tcp',
|
|
2125
|
-
FromPort: 443,
|
|
2126
|
-
ToPort: 443,
|
|
2127
|
-
CidrIp: discoveredResources.vpcCidr,
|
|
2128
|
-
Description: 'HTTPS from VPC CIDR (fallback)',
|
|
2129
|
-
});
|
|
2130
|
-
}
|
|
2131
|
-
|
|
2132
|
-
// Last resort: Allow from common private IP ranges
|
|
2133
|
-
if (vpcEndpointIngressRules.length === 0) {
|
|
2134
|
-
console.warn(
|
|
2135
|
-
'⚠️ WARNING: No Lambda security group or VPC CIDR found. Using default private IP ranges.'
|
|
2136
|
-
);
|
|
2137
|
-
vpcEndpointIngressRules.push({
|
|
2138
|
-
IpProtocol: 'tcp',
|
|
2139
|
-
FromPort: 443,
|
|
2140
|
-
ToPort: 443,
|
|
2141
|
-
CidrIp: '172.31.0.0/16', // Default VPC CIDR
|
|
2142
|
-
Description: 'HTTPS from default VPC range',
|
|
2143
|
-
});
|
|
2144
|
-
}
|
|
2145
|
-
|
|
2146
|
-
definition.resources.Resources.VPCEndpointSecurityGroup =
|
|
2147
|
-
{
|
|
2148
|
-
Type: 'AWS::EC2::SecurityGroup',
|
|
2149
|
-
Properties: {
|
|
2150
|
-
GroupDescription:
|
|
2151
|
-
'Security group for VPC endpoints - allows HTTPS from Lambda functions',
|
|
2152
|
-
VpcId: discoveredResources.defaultVpcId,
|
|
2153
|
-
SecurityGroupIngress: vpcEndpointIngressRules,
|
|
2154
|
-
Tags: [
|
|
2155
|
-
{
|
|
2156
|
-
Key: 'Name',
|
|
2157
|
-
Value: '${self:service}-${self:provider.stage}-vpc-endpoints-sg',
|
|
2158
|
-
},
|
|
2159
|
-
{
|
|
2160
|
-
Key: 'ManagedBy',
|
|
2161
|
-
Value: 'Frigg',
|
|
2162
|
-
},
|
|
2163
|
-
{
|
|
2164
|
-
Key: 'Purpose',
|
|
2165
|
-
Value: 'Allow Lambda functions to access VPC endpoints',
|
|
2166
|
-
},
|
|
2167
|
-
],
|
|
2168
|
-
},
|
|
2169
|
-
};
|
|
2170
|
-
}
|
|
1262
|
+
if (discoveredResources.existingNatGatewayId) {
|
|
1263
|
+
console.log(
|
|
1264
|
+
'discoveredResources.existingNatGatewayId',
|
|
1265
|
+
discoveredResources.existingNatGatewayId
|
|
1266
|
+
);
|
|
2171
1267
|
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
Properties: {
|
|
2175
|
-
VpcId: discoveredResources.defaultVpcId,
|
|
2176
|
-
ServiceName:
|
|
2177
|
-
'com.amazonaws.${self:provider.region}.kms',
|
|
2178
|
-
VpcEndpointType: 'Interface',
|
|
2179
|
-
SubnetIds: vpcConfig.subnetIds,
|
|
2180
|
-
SecurityGroupIds: [
|
|
2181
|
-
{ Ref: 'VPCEndpointSecurityGroup' },
|
|
2182
|
-
],
|
|
2183
|
-
PrivateDnsEnabled: true,
|
|
2184
|
-
},
|
|
2185
|
-
};
|
|
1268
|
+
if (discoveredResources.natGatewayInPrivateSubnet) {
|
|
1269
|
+
console.log('❌ CRITICAL: NAT Gateway is in PRIVATE subnet - Internet connectivity will NOT work!');
|
|
2186
1270
|
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
ServiceName:
|
|
2195
|
-
'com.amazonaws.${self:provider.region}.secretsmanager',
|
|
2196
|
-
VpcEndpointType: 'Interface',
|
|
2197
|
-
SubnetIds: vpcConfig.subnetIds,
|
|
2198
|
-
SecurityGroupIds: [
|
|
2199
|
-
{ Ref: 'VPCEndpointSecurityGroup' },
|
|
2200
|
-
],
|
|
2201
|
-
PrivateDnsEnabled: true,
|
|
2202
|
-
},
|
|
2203
|
-
};
|
|
1271
|
+
if (AppDefinition.vpc.selfHeal === true) {
|
|
1272
|
+
console.log('Self-heal enabled: Will create new NAT Gateway in PUBLIC subnet');
|
|
1273
|
+
needsNewNatGateway = true;
|
|
1274
|
+
discoveredResources.existingNatGatewayId = null;
|
|
1275
|
+
if (!discoveredResources.publicSubnetId) {
|
|
1276
|
+
console.log('No public subnet found - will create one for NAT Gateway');
|
|
1277
|
+
discoveredResources.createPublicSubnet = true;
|
|
2204
1278
|
}
|
|
1279
|
+
} else {
|
|
1280
|
+
throw new Error(
|
|
1281
|
+
'CRITICAL: NAT Gateway is in PRIVATE subnet and will NOT provide internet connectivity! Options: 1) Enable vpc.selfHeal to auto-create proper NAT, 2) Set natGateway.management to "createAndManage", or 3) Manually fix the NAT Gateway placement.'
|
|
1282
|
+
);
|
|
2205
1283
|
}
|
|
1284
|
+
} else {
|
|
1285
|
+
console.log(`Using discovered NAT Gateway for routing: ${discoveredResources.existingNatGatewayId}`);
|
|
2206
1286
|
}
|
|
1287
|
+
} else if (!needsNewNatGateway && AppDefinition.vpc.natGateway?.id) {
|
|
1288
|
+
console.log(`Using explicitly provided NAT Gateway: ${AppDefinition.vpc.natGateway.id}`);
|
|
1289
|
+
discoveredResources.existingNatGatewayId = AppDefinition.vpc.natGateway.id;
|
|
2207
1290
|
}
|
|
1291
|
+
}
|
|
2208
1292
|
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
1293
|
+
definition.resources.Resources.FriggLambdaRouteTable =
|
|
1294
|
+
definition.resources.Resources.FriggLambdaRouteTable || {
|
|
1295
|
+
Type: 'AWS::EC2::RouteTable',
|
|
1296
|
+
Properties: {
|
|
1297
|
+
VpcId: discoveredResources.defaultVpcId || vpcId,
|
|
1298
|
+
Tags: [
|
|
1299
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-lambda-rt' },
|
|
1300
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
1301
|
+
{ Key: 'Environment', Value: '${self:provider.stage}' },
|
|
1302
|
+
{ Key: 'Service', Value: '${self:service}' },
|
|
1303
|
+
],
|
|
1304
|
+
},
|
|
1305
|
+
};
|
|
2215
1306
|
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
1307
|
+
const routeTableId = { Ref: 'FriggLambdaRouteTable' };
|
|
1308
|
+
let natGatewayIdForRoute;
|
|
1309
|
+
|
|
1310
|
+
if (reuseExistingNatGateway) {
|
|
1311
|
+
natGatewayIdForRoute = discoveredResources.existingNatGatewayId;
|
|
1312
|
+
console.log(`Using discovered NAT Gateway for routing: ${natGatewayIdForRoute}`);
|
|
1313
|
+
} else if (needsNewNatGateway && !reuseExistingNatGateway) {
|
|
1314
|
+
natGatewayIdForRoute = { Ref: 'FriggNATGateway' };
|
|
1315
|
+
console.log('Using newly created NAT Gateway for routing');
|
|
1316
|
+
} else if (discoveredResources.existingNatGatewayId) {
|
|
1317
|
+
natGatewayIdForRoute = discoveredResources.existingNatGatewayId;
|
|
1318
|
+
console.log(`Using discovered NAT Gateway for routing: ${natGatewayIdForRoute}`);
|
|
1319
|
+
} else if (AppDefinition.vpc.natGateway?.id) {
|
|
1320
|
+
natGatewayIdForRoute = AppDefinition.vpc.natGateway.id;
|
|
1321
|
+
console.log(`Using explicitly provided NAT Gateway for routing: ${natGatewayIdForRoute}`);
|
|
1322
|
+
} else if (AppDefinition.vpc.selfHeal === true) {
|
|
1323
|
+
natGatewayIdForRoute = null;
|
|
1324
|
+
console.log('No NAT Gateway available - skipping NAT route creation');
|
|
1325
|
+
} else {
|
|
1326
|
+
throw new Error('No existing NAT Gateway found in discovery mode');
|
|
1327
|
+
}
|
|
2228
1328
|
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
1329
|
+
if (natGatewayIdForRoute) {
|
|
1330
|
+
console.log(`Configuring NAT route: 0.0.0.0/0 → ${natGatewayIdForRoute}`);
|
|
1331
|
+
definition.resources.Resources.FriggNATRoute = {
|
|
1332
|
+
Type: 'AWS::EC2::Route',
|
|
1333
|
+
DependsOn: 'FriggLambdaRouteTable',
|
|
1334
|
+
Properties: {
|
|
1335
|
+
RouteTableId: routeTableId,
|
|
1336
|
+
DestinationCidrBlock: '0.0.0.0/0',
|
|
1337
|
+
NatGatewayId: natGatewayIdForRoute,
|
|
1338
|
+
},
|
|
1339
|
+
};
|
|
1340
|
+
} else {
|
|
1341
|
+
console.warn('⚠️ No NAT Gateway configured - Lambda functions will not have internet access');
|
|
1342
|
+
}
|
|
2233
1343
|
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
!integration.Definition.name
|
|
2245
|
-
) {
|
|
2246
|
-
throw new Error(
|
|
2247
|
-
'Invalid integration: missing Definition or name'
|
|
2248
|
-
);
|
|
2249
|
-
}
|
|
2250
|
-
const integrationName = integration.Definition.name;
|
|
1344
|
+
if (typeof vpcConfig.subnetIds[0] === 'string') {
|
|
1345
|
+
definition.resources.Resources.FriggSubnet1RouteAssociation = {
|
|
1346
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
1347
|
+
Properties: {
|
|
1348
|
+
SubnetId: vpcConfig.subnetIds[0],
|
|
1349
|
+
RouteTableId: routeTableId,
|
|
1350
|
+
},
|
|
1351
|
+
DependsOn: 'FriggLambdaRouteTable',
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
2251
1354
|
|
|
2252
|
-
|
|
2253
|
-
definition.
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
},
|
|
2261
|
-
},
|
|
2262
|
-
],
|
|
1355
|
+
if (typeof vpcConfig.subnetIds[1] === 'string') {
|
|
1356
|
+
definition.resources.Resources.FriggSubnet2RouteAssociation = {
|
|
1357
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
1358
|
+
Properties: {
|
|
1359
|
+
SubnetId: vpcConfig.subnetIds[1],
|
|
1360
|
+
RouteTableId: routeTableId,
|
|
1361
|
+
},
|
|
1362
|
+
DependsOn: 'FriggLambdaRouteTable',
|
|
2263
1363
|
};
|
|
1364
|
+
}
|
|
2264
1365
|
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
}Queue`;
|
|
2269
|
-
const queueName = `\${self:service}--\${self:provider.stage}-${queueReference}`;
|
|
2270
|
-
definition.resources.Resources[queueReference] = {
|
|
2271
|
-
Type: 'AWS::SQS::Queue',
|
|
1366
|
+
if (typeof vpcConfig.subnetIds[0] === 'object' && vpcConfig.subnetIds[0].Ref) {
|
|
1367
|
+
definition.resources.Resources.FriggNewSubnet1RouteAssociation = {
|
|
1368
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
2272
1369
|
Properties: {
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
VisibilityTimeout: 1800, // 30 minutes
|
|
2276
|
-
RedrivePolicy: {
|
|
2277
|
-
maxReceiveCount: 1,
|
|
2278
|
-
deadLetterTargetArn: {
|
|
2279
|
-
'Fn::GetAtt': ['InternalErrorQueue', 'Arn'],
|
|
2280
|
-
},
|
|
2281
|
-
},
|
|
1370
|
+
SubnetId: vpcConfig.subnetIds[0],
|
|
1371
|
+
RouteTableId: routeTableId,
|
|
2282
1372
|
},
|
|
1373
|
+
DependsOn: ['FriggLambdaRouteTable', vpcConfig.subnetIds[0].Ref],
|
|
2283
1374
|
};
|
|
1375
|
+
}
|
|
2284
1376
|
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
arn: {
|
|
2294
|
-
'Fn::GetAtt': [queueReference, 'Arn'],
|
|
2295
|
-
},
|
|
2296
|
-
batchSize: 1,
|
|
2297
|
-
},
|
|
2298
|
-
},
|
|
2299
|
-
],
|
|
2300
|
-
timeout: 600,
|
|
1377
|
+
if (typeof vpcConfig.subnetIds[1] === 'object' && vpcConfig.subnetIds[1].Ref) {
|
|
1378
|
+
definition.resources.Resources.FriggNewSubnet2RouteAssociation = {
|
|
1379
|
+
Type: 'AWS::EC2::SubnetRouteTableAssociation',
|
|
1380
|
+
Properties: {
|
|
1381
|
+
SubnetId: vpcConfig.subnetIds[1],
|
|
1382
|
+
RouteTableId: routeTableId,
|
|
1383
|
+
},
|
|
1384
|
+
DependsOn: ['FriggLambdaRouteTable', vpcConfig.subnetIds[1].Ref],
|
|
2301
1385
|
};
|
|
1386
|
+
}
|
|
2302
1387
|
|
|
2303
|
-
|
|
2304
|
-
definition.
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
1388
|
+
if (AppDefinition.vpc.enableVPCEndpoints !== false) {
|
|
1389
|
+
definition.resources.Resources.VPCEndpointS3 = {
|
|
1390
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
1391
|
+
Properties: {
|
|
1392
|
+
VpcId: discoveredResources.defaultVpcId,
|
|
1393
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.s3',
|
|
1394
|
+
VpcEndpointType: 'Gateway',
|
|
1395
|
+
RouteTableIds: [routeTableId],
|
|
2308
1396
|
},
|
|
2309
1397
|
};
|
|
2310
1398
|
|
|
2311
|
-
definition.
|
|
1399
|
+
definition.resources.Resources.VPCEndpointDynamoDB = {
|
|
1400
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
1401
|
+
Properties: {
|
|
1402
|
+
VpcId: discoveredResources.defaultVpcId,
|
|
1403
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.dynamodb',
|
|
1404
|
+
VpcEndpointType: 'Gateway',
|
|
1405
|
+
RouteTableIds: [routeTableId],
|
|
1406
|
+
},
|
|
1407
|
+
};
|
|
2312
1408
|
}
|
|
1409
|
+
|
|
1410
|
+
if (AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') {
|
|
1411
|
+
if (!discoveredResources.vpcCidr) {
|
|
1412
|
+
console.warn(
|
|
1413
|
+
'⚠️ Warning: VPC CIDR not discovered. VPC endpoint security group may not work correctly.'
|
|
1414
|
+
);
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
if (!definition.resources.Resources.VPCEndpointSecurityGroup) {
|
|
1418
|
+
const vpcEndpointIngressRules = [];
|
|
1419
|
+
|
|
1420
|
+
if (vpcConfig.securityGroupIds && vpcConfig.securityGroupIds.length > 0) {
|
|
1421
|
+
for (const sg of vpcConfig.securityGroupIds) {
|
|
1422
|
+
if (typeof sg === 'string') {
|
|
1423
|
+
vpcEndpointIngressRules.push({
|
|
1424
|
+
IpProtocol: 'tcp',
|
|
1425
|
+
FromPort: 443,
|
|
1426
|
+
ToPort: 443,
|
|
1427
|
+
SourceSecurityGroupId: sg,
|
|
1428
|
+
Description: 'HTTPS from Lambda security group',
|
|
1429
|
+
});
|
|
1430
|
+
} else if (sg.Ref) {
|
|
1431
|
+
vpcEndpointIngressRules.push({
|
|
1432
|
+
IpProtocol: 'tcp',
|
|
1433
|
+
FromPort: 443,
|
|
1434
|
+
ToPort: 443,
|
|
1435
|
+
SourceSecurityGroupId: { Ref: sg.Ref },
|
|
1436
|
+
Description: 'HTTPS from Lambda security group',
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
if (vpcEndpointIngressRules.length === 0) {
|
|
1443
|
+
if (discoveredResources.vpcCidr) {
|
|
1444
|
+
vpcEndpointIngressRules.push({
|
|
1445
|
+
IpProtocol: 'tcp',
|
|
1446
|
+
FromPort: 443,
|
|
1447
|
+
ToPort: 443,
|
|
1448
|
+
CidrIp: discoveredResources.vpcCidr,
|
|
1449
|
+
Description: 'HTTPS from VPC CIDR (fallback)',
|
|
1450
|
+
});
|
|
1451
|
+
} else {
|
|
1452
|
+
console.warn(
|
|
1453
|
+
'⚠️ WARNING: No Lambda security group or VPC CIDR found. Using default private IP ranges.'
|
|
1454
|
+
);
|
|
1455
|
+
vpcEndpointIngressRules.push({
|
|
1456
|
+
IpProtocol: 'tcp',
|
|
1457
|
+
FromPort: 443,
|
|
1458
|
+
ToPort: 443,
|
|
1459
|
+
CidrIp: '172.31.0.0/16',
|
|
1460
|
+
Description: 'HTTPS from default VPC range',
|
|
1461
|
+
});
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
definition.resources.Resources.VPCEndpointSecurityGroup = {
|
|
1466
|
+
Type: 'AWS::EC2::SecurityGroup',
|
|
1467
|
+
Properties: {
|
|
1468
|
+
GroupDescription: 'Security group for VPC endpoints - allows HTTPS from Lambda functions',
|
|
1469
|
+
VpcId: discoveredResources.defaultVpcId,
|
|
1470
|
+
SecurityGroupIngress: vpcEndpointIngressRules,
|
|
1471
|
+
Tags: [
|
|
1472
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-vpc-endpoints-sg' },
|
|
1473
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
1474
|
+
{ Key: 'Purpose', Value: 'Allow Lambda functions to access VPC endpoints' },
|
|
1475
|
+
],
|
|
1476
|
+
},
|
|
1477
|
+
};
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
definition.resources.Resources.VPCEndpointKMS = {
|
|
1481
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
1482
|
+
Properties: {
|
|
1483
|
+
VpcId: discoveredResources.defaultVpcId,
|
|
1484
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.kms',
|
|
1485
|
+
VpcEndpointType: 'Interface',
|
|
1486
|
+
SubnetIds: vpcConfig.subnetIds,
|
|
1487
|
+
SecurityGroupIds: [{ Ref: 'VPCEndpointSecurityGroup' }],
|
|
1488
|
+
PrivateDnsEnabled: true,
|
|
1489
|
+
},
|
|
1490
|
+
};
|
|
1491
|
+
|
|
1492
|
+
if (AppDefinition.secretsManager?.enable === true) {
|
|
1493
|
+
definition.resources.Resources.VPCEndpointSecretsManager = {
|
|
1494
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
1495
|
+
Properties: {
|
|
1496
|
+
VpcId: discoveredResources.defaultVpcId,
|
|
1497
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.secretsmanager',
|
|
1498
|
+
VpcEndpointType: 'Interface',
|
|
1499
|
+
SubnetIds: vpcConfig.subnetIds,
|
|
1500
|
+
SecurityGroupIds: [{ Ref: 'VPCEndpointSecurityGroup' }],
|
|
1501
|
+
PrivateDnsEnabled: true,
|
|
1502
|
+
},
|
|
1503
|
+
};
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
}
|
|
1507
|
+
};
|
|
1508
|
+
|
|
1509
|
+
const configureSsm = (definition, AppDefinition) => {
|
|
1510
|
+
if (AppDefinition.ssm?.enable !== true) {
|
|
1511
|
+
return;
|
|
2313
1512
|
}
|
|
2314
1513
|
|
|
2315
|
-
|
|
2316
|
-
|
|
1514
|
+
definition.provider.layers = [
|
|
1515
|
+
'arn:aws:lambda:${self:provider.region}:177933569100:layer:AWS-Parameters-and-Secrets-Lambda-Extension:11',
|
|
1516
|
+
];
|
|
2317
1517
|
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
1518
|
+
definition.provider.iamRoleStatements.push({
|
|
1519
|
+
Effect: 'Allow',
|
|
1520
|
+
Action: ['ssm:GetParameter', 'ssm:GetParameters', 'ssm:GetParametersByPath'],
|
|
1521
|
+
Resource: ['arn:aws:ssm:${self:provider.region}:*:parameter/${self:service}/${self:provider.stage}/*'],
|
|
1522
|
+
});
|
|
1523
|
+
|
|
1524
|
+
definition.provider.environment.SSM_PARAMETER_PREFIX = '/${self:service}/${self:provider.stage}';
|
|
1525
|
+
};
|
|
1526
|
+
|
|
1527
|
+
const attachIntegrations = (definition, AppDefinition) => {
|
|
1528
|
+
if (!Array.isArray(AppDefinition.integrations) || AppDefinition.integrations.length === 0) {
|
|
1529
|
+
return;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
console.log(`Processing ${AppDefinition.integrations.length} integrations...`);
|
|
1533
|
+
|
|
1534
|
+
for (const integration of AppDefinition.integrations) {
|
|
1535
|
+
if (!integration?.Definition?.name) {
|
|
1536
|
+
throw new Error('Invalid integration: missing Definition or name');
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
const integrationName = integration.Definition.name;
|
|
1540
|
+
const queueReference = `${integrationName.charAt(0).toUpperCase() + integrationName.slice(1)}Queue`;
|
|
1541
|
+
const queueName = `\${self:service}--\${self:provider.stage}-${queueReference}`;
|
|
1542
|
+
|
|
1543
|
+
definition.functions[integrationName] = {
|
|
1544
|
+
handler: `node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.${integrationName}.handler`,
|
|
2323
1545
|
events: [
|
|
2324
1546
|
{
|
|
2325
|
-
|
|
2326
|
-
|
|
1547
|
+
httpApi: {
|
|
1548
|
+
path: `/api/${integrationName}-integration/{proxy+}`,
|
|
1549
|
+
method: 'ANY',
|
|
2327
1550
|
},
|
|
2328
1551
|
},
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
1552
|
+
],
|
|
1553
|
+
};
|
|
1554
|
+
|
|
1555
|
+
definition.resources.Resources[queueReference] = {
|
|
1556
|
+
Type: 'AWS::SQS::Queue',
|
|
1557
|
+
Properties: {
|
|
1558
|
+
QueueName: `\${self:custom.${queueReference}}`,
|
|
1559
|
+
MessageRetentionPeriod: 60,
|
|
1560
|
+
VisibilityTimeout: 1800,
|
|
1561
|
+
RedrivePolicy: {
|
|
1562
|
+
maxReceiveCount: 1,
|
|
1563
|
+
deadLetterTargetArn: { 'Fn::GetAtt': ['InternalErrorQueue', 'Arn'] },
|
|
2333
1564
|
},
|
|
1565
|
+
},
|
|
1566
|
+
};
|
|
1567
|
+
|
|
1568
|
+
const queueWorkerName = `${integrationName}QueueWorker`;
|
|
1569
|
+
definition.functions[queueWorkerName] = {
|
|
1570
|
+
handler: `node_modules/@friggframework/core/handlers/workers/integration-defined-workers.handlers.${integrationName}.queueWorker`,
|
|
1571
|
+
reservedConcurrency: 5,
|
|
1572
|
+
events: [
|
|
2334
1573
|
{
|
|
2335
|
-
|
|
2336
|
-
|
|
1574
|
+
sqs: {
|
|
1575
|
+
arn: { 'Fn::GetAtt': [queueReference, 'Arn'] },
|
|
1576
|
+
batchSize: 1,
|
|
2337
1577
|
},
|
|
2338
1578
|
},
|
|
2339
1579
|
],
|
|
1580
|
+
timeout: 600,
|
|
2340
1581
|
};
|
|
1582
|
+
|
|
1583
|
+
definition.provider.environment = {
|
|
1584
|
+
...definition.provider.environment,
|
|
1585
|
+
[`${integrationName.toUpperCase()}_QUEUE_URL`]: { Ref: queueReference },
|
|
1586
|
+
};
|
|
1587
|
+
|
|
1588
|
+
definition.custom[queueReference] = queueName;
|
|
1589
|
+
}
|
|
1590
|
+
};
|
|
1591
|
+
|
|
1592
|
+
const configureWebsockets = (definition, AppDefinition) => {
|
|
1593
|
+
if (AppDefinition.websockets?.enable !== true) {
|
|
1594
|
+
return;
|
|
2341
1595
|
}
|
|
2342
1596
|
|
|
2343
|
-
|
|
1597
|
+
definition.functions.defaultWebsocket = {
|
|
1598
|
+
handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
|
|
1599
|
+
events: [
|
|
1600
|
+
{ websocket: { route: '$connect' } },
|
|
1601
|
+
{ websocket: { route: '$default' } },
|
|
1602
|
+
{ websocket: { route: '$disconnect' } },
|
|
1603
|
+
],
|
|
1604
|
+
};
|
|
1605
|
+
};
|
|
1606
|
+
|
|
1607
|
+
const composeServerlessDefinition = async (AppDefinition) => {
|
|
1608
|
+
console.log('composeServerlessDefinition', AppDefinition);
|
|
1609
|
+
|
|
1610
|
+
const discoveredResources = await gatherDiscoveredResources(AppDefinition);
|
|
1611
|
+
const appEnvironmentVars = getAppEnvironmentVars(AppDefinition);
|
|
1612
|
+
const definition = createBaseDefinition(AppDefinition, appEnvironmentVars, discoveredResources);
|
|
1613
|
+
|
|
1614
|
+
applyKmsConfiguration(definition, AppDefinition, discoveredResources);
|
|
1615
|
+
configureVpc(definition, AppDefinition, discoveredResources);
|
|
1616
|
+
configureSsm(definition, AppDefinition);
|
|
1617
|
+
attachIntegrations(definition, AppDefinition);
|
|
1618
|
+
configureWebsockets(definition, AppDefinition);
|
|
1619
|
+
|
|
2344
1620
|
definition.functions = modifyHandlerPaths(definition.functions);
|
|
2345
1621
|
|
|
2346
1622
|
return definition;
|