@friggframework/devtools 2.0.0--canary.425.1dfec23.0 → 2.0.0--canary.427.c8ff14b.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/frigg-cli/generate-command/__tests__/generate-command.test.js +1 -1
- package/frigg-cli/generate-command/index.js +1 -1
- package/frigg-cli/init-command/backend-first-handler.js +1 -1
- package/infrastructure/AWS-DISCOVERY-TROUBLESHOOTING.md +2 -2
- package/infrastructure/AWS-IAM-CREDENTIAL-NEEDS.md +1 -1
- package/infrastructure/GENERATE-IAM-DOCS.md +2 -2
- package/infrastructure/README.md +2 -3
- package/infrastructure/__tests__/fixtures/mock-aws-resources.js +4 -4
- package/infrastructure/__tests__/helpers/test-utils.js +1 -1
- package/infrastructure/aws-discovery.js +12 -14
- package/infrastructure/build-time-discovery.js +3 -3
- package/infrastructure/build-time-discovery.test.js +1 -1
- package/infrastructure/iam-generator.js +3 -2
- package/infrastructure/iam-generator.test.js +4 -4
- package/infrastructure/integration.test.js +7 -7
- package/infrastructure/run-discovery.js +4 -4
- package/infrastructure/serverless-template.js +103 -95
- package/infrastructure/serverless-template.test.js +38 -241
- package/package.json +6 -7
|
@@ -54,7 +54,7 @@ describe('Generate Command', () => {
|
|
|
54
54
|
// Mock app definition
|
|
55
55
|
jest.doMock(mockAppDefinitionPath, () => ({
|
|
56
56
|
vpc: { enable: true },
|
|
57
|
-
encryption: {
|
|
57
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
58
58
|
ssm: { enable: true },
|
|
59
59
|
websockets: { enable: false }
|
|
60
60
|
}), { virtual: true });
|
|
@@ -228,7 +228,7 @@ async function generateCommand(options = {}) {
|
|
|
228
228
|
function analyzeAppFeatures(appDefinition) {
|
|
229
229
|
const features = {
|
|
230
230
|
vpc: appDefinition.vpc?.enable === true,
|
|
231
|
-
kms: appDefinition.encryption?.
|
|
231
|
+
kms: appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true,
|
|
232
232
|
ssm: appDefinition.ssm?.enable === true,
|
|
233
233
|
websockets: appDefinition.websockets?.enable === true,
|
|
234
234
|
// Add more feature detection as needed
|
|
@@ -670,7 +670,7 @@ To integrate Frigg into your production application:
|
|
|
670
670
|
const appDefinition = {
|
|
671
671
|
integrations: [], // Will be populated based on selected integrations
|
|
672
672
|
user: { password: true },
|
|
673
|
-
encryption: {
|
|
673
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
674
674
|
vpc: { enable: true },
|
|
675
675
|
security: {
|
|
676
676
|
cors: {
|
|
@@ -9,7 +9,7 @@ AWS Discovery automatically finds your default AWS resources (VPC, subnets, secu
|
|
|
9
9
|
AWS Discovery runs automatically during `frigg build` and `frigg deploy` when your AppDefinition includes:
|
|
10
10
|
|
|
11
11
|
- `vpc.enable: true` - VPC support
|
|
12
|
-
- `encryption.
|
|
12
|
+
- `encryption.useDefaultKMSForFieldLevelEncryption: true` - KMS encryption
|
|
13
13
|
- `ssm.enable: true` - SSM Parameter Store
|
|
14
14
|
|
|
15
15
|
## Fail-Fast Behavior
|
|
@@ -222,7 +222,7 @@ If you're stuck, try this recovery process:
|
|
|
222
222
|
// backend/index.js - temporarily disable problematic features
|
|
223
223
|
const appDefinition = {
|
|
224
224
|
vpc: { enable: false },
|
|
225
|
-
encryption: {
|
|
225
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: false },
|
|
226
226
|
ssm: { enable: false }
|
|
227
227
|
};
|
|
228
228
|
```
|
|
@@ -354,7 +354,7 @@ Additional permissions needed when your app definition includes `vpc: { enable:
|
|
|
354
354
|
|
|
355
355
|
### KMS Support
|
|
356
356
|
|
|
357
|
-
Additional permissions needed when your app definition includes `encryption: {
|
|
357
|
+
Additional permissions needed when your app definition includes `encryption: { useDefaultKMSForFieldLevelEncryption: true }`:
|
|
358
358
|
|
|
359
359
|
```json
|
|
360
360
|
{
|
|
@@ -58,7 +58,7 @@ The command analyzes your `backend/index.js` AppDefinition and generates IAM pol
|
|
|
58
58
|
- Route table and security group management
|
|
59
59
|
- Elastic IP allocation
|
|
60
60
|
|
|
61
|
-
#### KMS Encryption (`encryption.
|
|
61
|
+
#### KMS Encryption (`encryption.useDefaultKMSForFieldLevelEncryption: true`)
|
|
62
62
|
|
|
63
63
|
- KMS key usage for Lambda and S3
|
|
64
64
|
- Data encryption and decryption permissions
|
|
@@ -85,7 +85,7 @@ const appDefinition = {
|
|
|
85
85
|
enable: true,
|
|
86
86
|
},
|
|
87
87
|
encryption: {
|
|
88
|
-
|
|
88
|
+
useDefaultKMSForFieldLevelEncryption: true,
|
|
89
89
|
},
|
|
90
90
|
ssm: {
|
|
91
91
|
enable: false,
|
package/infrastructure/README.md
CHANGED
|
@@ -155,8 +155,7 @@ const appDefinition = {
|
|
|
155
155
|
|
|
156
156
|
// KMS encryption
|
|
157
157
|
encryption: {
|
|
158
|
-
|
|
159
|
-
createResourceIfNoneFound: true
|
|
158
|
+
useDefaultKMSForFieldLevelEncryption: true
|
|
160
159
|
},
|
|
161
160
|
|
|
162
161
|
// SSM Parameter Store
|
|
@@ -218,7 +217,7 @@ const serverlessConfig = await composeServerlessDefinition(appDefinition);
|
|
|
218
217
|
const appDefinition = {
|
|
219
218
|
name: 'secure-app',
|
|
220
219
|
vpc: { enable: true },
|
|
221
|
-
encryption: {
|
|
220
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
222
221
|
ssm: { enable: true },
|
|
223
222
|
integrations: [{ Definition: { name: 'salesforce' } }],
|
|
224
223
|
};
|
|
@@ -151,7 +151,7 @@ const mockAppDefinitions = {
|
|
|
151
151
|
|
|
152
152
|
kmsOnly: {
|
|
153
153
|
name: 'kms-test-app',
|
|
154
|
-
encryption: {
|
|
154
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
155
155
|
integrations: []
|
|
156
156
|
},
|
|
157
157
|
|
|
@@ -164,7 +164,7 @@ const mockAppDefinitions = {
|
|
|
164
164
|
allFeatures: {
|
|
165
165
|
name: 'full-feature-app',
|
|
166
166
|
vpc: { enable: true },
|
|
167
|
-
encryption: {
|
|
167
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
168
168
|
ssm: { enable: true },
|
|
169
169
|
integrations: [{
|
|
170
170
|
Definition: {
|
|
@@ -281,7 +281,7 @@ const mockEnvironmentVariables = {
|
|
|
281
281
|
AWS_DISCOVERY_SUBNET_ID_1: mockSubnets[0].SubnetId,
|
|
282
282
|
AWS_DISCOVERY_SUBNET_ID_2: mockSubnets[1].SubnetId,
|
|
283
283
|
AWS_DISCOVERY_ROUTE_TABLE_ID: mockRouteTables[0].RouteTableId,
|
|
284
|
-
AWS_DISCOVERY_KMS_KEY_ID:mockKmsKeyMetadata.Arn
|
|
284
|
+
AWS_DISCOVERY_KMS_KEY_ID: mockKmsKeyMetadata.Arn
|
|
285
285
|
};
|
|
286
286
|
|
|
287
287
|
// Fallback environment variables for error scenarios
|
|
@@ -291,7 +291,7 @@ const mockFallbackEnvironmentVariables = {
|
|
|
291
291
|
AWS_DISCOVERY_SUBNET_ID_1: 'subnet-fallback-1',
|
|
292
292
|
AWS_DISCOVERY_SUBNET_ID_2: 'subnet-fallback-2',
|
|
293
293
|
AWS_DISCOVERY_ROUTE_TABLE_ID: 'rtb-fallback',
|
|
294
|
-
AWS_DISCOVERY_KMS_KEY_ID:'arn:aws:kms:*:*:key/*'
|
|
294
|
+
AWS_DISCOVERY_KMS_KEY_ID: 'arn:aws:kms:*:*:key/*'
|
|
295
295
|
};
|
|
296
296
|
|
|
297
297
|
// Mock AWS SDK responses
|
|
@@ -115,7 +115,7 @@ function createMockAppDefinition(features = {}, integrations = []) {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
if (features.kms) {
|
|
118
|
-
appDefinition.encryption = {
|
|
118
|
+
appDefinition.encryption = { useDefaultKMSForFieldLevelEncryption: true };
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
if (features.ssm) {
|
|
@@ -453,16 +453,18 @@ class AWSDiscovery {
|
|
|
453
453
|
|
|
454
454
|
/**
|
|
455
455
|
* Find the default KMS key for the account
|
|
456
|
-
* @returns {Promise<string
|
|
456
|
+
* @returns {Promise<string>} KMS key ARN or wildcard pattern as fallback
|
|
457
457
|
*/
|
|
458
458
|
async findDefaultKmsKey() {
|
|
459
459
|
try {
|
|
460
|
+
// First try to find a key with alias/aws/lambda
|
|
460
461
|
const command = new ListKeysCommand({});
|
|
461
462
|
const response = await this.kmsClient.send(command);
|
|
462
463
|
|
|
463
464
|
if (!response.Keys || response.Keys.length === 0) {
|
|
464
|
-
|
|
465
|
-
|
|
465
|
+
// Return AWS managed key ARN pattern as fallback
|
|
466
|
+
const accountId = await this.getAccountId();
|
|
467
|
+
return `arn:aws:kms:${this.region}:${accountId}:key/*`;
|
|
466
468
|
}
|
|
467
469
|
|
|
468
470
|
// Look for customer managed keys first
|
|
@@ -474,21 +476,21 @@ class AWSDiscovery {
|
|
|
474
476
|
if (keyDetails.KeyMetadata &&
|
|
475
477
|
keyDetails.KeyMetadata.KeyManager === 'CUSTOMER' &&
|
|
476
478
|
keyDetails.KeyMetadata.KeyState === 'Enabled') {
|
|
477
|
-
console.log(`Found customer managed KMS key: ${keyDetails.KeyMetadata.Arn}`);
|
|
478
479
|
return keyDetails.KeyMetadata.Arn;
|
|
479
480
|
}
|
|
480
481
|
} catch (error) {
|
|
481
482
|
// Continue to next key if we can't describe this one
|
|
482
|
-
console.warn(`Could not describe key ${key.KeyId}:`, error.message);
|
|
483
483
|
continue;
|
|
484
484
|
}
|
|
485
485
|
}
|
|
486
486
|
|
|
487
|
-
|
|
488
|
-
|
|
487
|
+
// Fallback to wildcard pattern for AWS managed keys
|
|
488
|
+
const accountId = await this.getAccountId();
|
|
489
|
+
return `arn:aws:kms:${this.region}:${accountId}:key/*`;
|
|
489
490
|
} catch (error) {
|
|
490
491
|
console.error('Error finding default KMS key:', error);
|
|
491
|
-
|
|
492
|
+
// Return wildcard pattern as ultimate fallback
|
|
493
|
+
return '*';
|
|
492
494
|
}
|
|
493
495
|
}
|
|
494
496
|
|
|
@@ -501,7 +503,7 @@ class AWSDiscovery {
|
|
|
501
503
|
* @returns {string} return.privateSubnetId2 - Second private subnet ID
|
|
502
504
|
* @returns {string} return.publicSubnetId - Public subnet ID for NAT Gateway
|
|
503
505
|
* @returns {string} return.privateRouteTableId - Private route table ID
|
|
504
|
-
* @returns {string
|
|
506
|
+
* @returns {string} return.defaultKmsKeyId - Default KMS key ARN
|
|
505
507
|
* @throws {Error} If resource discovery fails
|
|
506
508
|
*/
|
|
507
509
|
async discoverResources() {
|
|
@@ -524,11 +526,7 @@ class AWSDiscovery {
|
|
|
524
526
|
console.log(`Found route table: ${routeTable.RouteTableId}`);
|
|
525
527
|
|
|
526
528
|
const kmsKeyArn = await this.findDefaultKmsKey();
|
|
527
|
-
|
|
528
|
-
console.log(`Found KMS key: ${kmsKeyArn}`);
|
|
529
|
-
} else {
|
|
530
|
-
console.log('No KMS key found');
|
|
531
|
-
}
|
|
529
|
+
console.log(`Found KMS key: ${kmsKeyArn}`);
|
|
532
530
|
|
|
533
531
|
// Try to find existing NAT Gateway
|
|
534
532
|
const existingNatGateway = await this.findExistingNatGateway(vpc.VpcId);
|
|
@@ -137,8 +137,8 @@ class BuildTimeDiscovery {
|
|
|
137
137
|
console.log('Running pre-build AWS discovery hook...');
|
|
138
138
|
|
|
139
139
|
// Only run discovery if VPC, KMS, or SSM features are enabled
|
|
140
|
-
const needsDiscovery = appDefinition.vpc?.enable ||
|
|
141
|
-
appDefinition.encryption?.
|
|
140
|
+
const needsDiscovery = appDefinition.vpc?.enable ||
|
|
141
|
+
appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption ||
|
|
142
142
|
appDefinition.ssm?.enable;
|
|
143
143
|
|
|
144
144
|
if (!needsDiscovery) {
|
|
@@ -159,7 +159,7 @@ class BuildTimeDiscovery {
|
|
|
159
159
|
AWS_DISCOVERY_SUBNET_ID_2: resources.privateSubnetId2,
|
|
160
160
|
AWS_DISCOVERY_PUBLIC_SUBNET_ID: resources.publicSubnetId,
|
|
161
161
|
AWS_DISCOVERY_ROUTE_TABLE_ID: resources.privateRouteTableId,
|
|
162
|
-
AWS_DISCOVERY_KMS_KEY_ID: resources.defaultKmsKeyId
|
|
162
|
+
AWS_DISCOVERY_KMS_KEY_ID: resources.defaultKmsKeyId
|
|
163
163
|
};
|
|
164
164
|
|
|
165
165
|
// Set environment variables for serverless to use
|
|
@@ -250,7 +250,7 @@ describe('BuildTimeDiscovery', () => {
|
|
|
250
250
|
|
|
251
251
|
it('should run discovery when KMS is enabled', async () => {
|
|
252
252
|
const appDefinition = {
|
|
253
|
-
encryption: {
|
|
253
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
254
254
|
integrations: []
|
|
255
255
|
};
|
|
256
256
|
|
|
@@ -35,7 +35,7 @@ function generateIAMCloudFormation(appDefinition, options = {}) {
|
|
|
35
35
|
vpc: appDefinition.vpc?.enable === true,
|
|
36
36
|
kms:
|
|
37
37
|
appDefinition.encryption
|
|
38
|
-
?.
|
|
38
|
+
?.useDefaultKMSForFieldLevelEncryption === true,
|
|
39
39
|
ssm: appDefinition.ssm?.enable === true,
|
|
40
40
|
websockets: appDefinition.websockets?.enable === true,
|
|
41
41
|
};
|
|
@@ -724,7 +724,8 @@ function getFeatureSummary(appDefinition) {
|
|
|
724
724
|
core: true, // Always enabled
|
|
725
725
|
vpc: appDefinition.vpc?.enable === true,
|
|
726
726
|
kms:
|
|
727
|
-
appDefinition.encryption?.
|
|
727
|
+
appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption ===
|
|
728
|
+
true,
|
|
728
729
|
ssm: appDefinition.ssm?.enable === true,
|
|
729
730
|
websockets: appDefinition.websockets?.enable === true,
|
|
730
731
|
};
|
|
@@ -7,7 +7,7 @@ describe('IAM Generator', () => {
|
|
|
7
7
|
name: 'test-app',
|
|
8
8
|
integrations: ['Integration1', 'Integration2'],
|
|
9
9
|
vpc: { enable: true },
|
|
10
|
-
encryption: {
|
|
10
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
11
11
|
ssm: { enable: true },
|
|
12
12
|
websockets: { enable: true }
|
|
13
13
|
};
|
|
@@ -46,7 +46,7 @@ describe('IAM Generator', () => {
|
|
|
46
46
|
name: 'test-app',
|
|
47
47
|
integrations: [],
|
|
48
48
|
vpc: { enable: false },
|
|
49
|
-
encryption: {
|
|
49
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: false },
|
|
50
50
|
ssm: { enable: false },
|
|
51
51
|
websockets: { enable: false }
|
|
52
52
|
};
|
|
@@ -77,7 +77,7 @@ describe('IAM Generator', () => {
|
|
|
77
77
|
const appDefinition = {
|
|
78
78
|
name: 'test-app',
|
|
79
79
|
integrations: [],
|
|
80
|
-
encryption: {
|
|
80
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true }
|
|
81
81
|
};
|
|
82
82
|
|
|
83
83
|
const yaml = generateIAMCloudFormation(appDefinition);
|
|
@@ -106,7 +106,7 @@ describe('IAM Generator', () => {
|
|
|
106
106
|
name: 'test-app',
|
|
107
107
|
integrations: [],
|
|
108
108
|
vpc: { enable: true },
|
|
109
|
-
encryption: {
|
|
109
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: false },
|
|
110
110
|
ssm: { enable: true }
|
|
111
111
|
};
|
|
112
112
|
|
|
@@ -49,7 +49,7 @@ describe('VPC/KMS/SSM Integration Tests', () => {
|
|
|
49
49
|
const appDefinition = {
|
|
50
50
|
name: 'test-frigg-app',
|
|
51
51
|
vpc: { enable: true },
|
|
52
|
-
encryption: {
|
|
52
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
53
53
|
ssm: { enable: true },
|
|
54
54
|
integrations: [{
|
|
55
55
|
Definition: {
|
|
@@ -67,7 +67,7 @@ describe('VPC/KMS/SSM Integration Tests', () => {
|
|
|
67
67
|
process.env.AWS_DISCOVERY_SUBNET_ID_1 = discoveredResources.privateSubnetId1;
|
|
68
68
|
process.env.AWS_DISCOVERY_SUBNET_ID_2 = discoveredResources.privateSubnetId2;
|
|
69
69
|
process.env.AWS_DISCOVERY_ROUTE_TABLE_ID = discoveredResources.privateRouteTableId;
|
|
70
|
-
process.env.AWS_DISCOVERY_KMS_KEY_ID =discoveredResources.defaultKmsKeyId;
|
|
70
|
+
process.env.AWS_DISCOVERY_KMS_KEY_ID = discoveredResources.defaultKmsKeyId;
|
|
71
71
|
|
|
72
72
|
// Generate serverless configuration
|
|
73
73
|
const serverlessConfig = composeServerlessDefinition(appDefinition);
|
|
@@ -173,11 +173,11 @@ describe('VPC/KMS/SSM Integration Tests', () => {
|
|
|
173
173
|
it('should generate config with only KMS enabled', async () => {
|
|
174
174
|
const appDefinition = {
|
|
175
175
|
name: 'kms-only-app',
|
|
176
|
-
encryption: {
|
|
176
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
177
177
|
integrations: []
|
|
178
178
|
};
|
|
179
179
|
|
|
180
|
-
process.env.AWS_DISCOVERY_KMS_KEY_ID =mockAWSResources.defaultKmsKeyId;
|
|
180
|
+
process.env.AWS_DISCOVERY_KMS_KEY_ID = mockAWSResources.defaultKmsKeyId;
|
|
181
181
|
|
|
182
182
|
const serverlessConfig = composeServerlessDefinition(appDefinition);
|
|
183
183
|
|
|
@@ -231,7 +231,7 @@ describe('VPC/KMS/SSM Integration Tests', () => {
|
|
|
231
231
|
expect(mockBuildTimeDiscovery.preBuildHook).toHaveBeenCalledWith(
|
|
232
232
|
expect.objectContaining({
|
|
233
233
|
vpc: { enable: true },
|
|
234
|
-
encryption: {
|
|
234
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true }
|
|
235
235
|
}),
|
|
236
236
|
'us-east-1'
|
|
237
237
|
);
|
|
@@ -302,7 +302,7 @@ describe('VPC/KMS/SSM Integration Tests', () => {
|
|
|
302
302
|
process.env.AWS_DISCOVERY_SECURITY_GROUP_ID = mockAWSResources.defaultSecurityGroupId;
|
|
303
303
|
process.env.AWS_DISCOVERY_SUBNET_ID_1 = mockAWSResources.privateSubnetId1;
|
|
304
304
|
process.env.AWS_DISCOVERY_SUBNET_ID_2 = mockAWSResources.privateSubnetId2;
|
|
305
|
-
process.env.AWS_DISCOVERY_KMS_KEY_ID =mockAWSResources.defaultKmsKeyId;
|
|
305
|
+
process.env.AWS_DISCOVERY_KMS_KEY_ID = mockAWSResources.defaultKmsKeyId;
|
|
306
306
|
|
|
307
307
|
// In a real deployment, serverless framework would resolve these environment variables
|
|
308
308
|
// For testing, we can verify the placeholders are correctly formatted
|
|
@@ -353,7 +353,7 @@ describe('VPC/KMS/SSM Integration Tests', () => {
|
|
|
353
353
|
|
|
354
354
|
const appDefinition = {
|
|
355
355
|
vpc: { enable: true },
|
|
356
|
-
encryption: {
|
|
356
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
357
357
|
integrations: []
|
|
358
358
|
};
|
|
359
359
|
|
|
@@ -41,7 +41,7 @@ async function runDiscovery() {
|
|
|
41
41
|
|
|
42
42
|
// Check if discovery is needed
|
|
43
43
|
const needsDiscovery = appDefinition.vpc?.enable ||
|
|
44
|
-
appDefinition.encryption?.
|
|
44
|
+
appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption ||
|
|
45
45
|
appDefinition.ssm?.enable;
|
|
46
46
|
|
|
47
47
|
if (!needsDiscovery) {
|
|
@@ -51,7 +51,7 @@ async function runDiscovery() {
|
|
|
51
51
|
|
|
52
52
|
console.log('📋 App requires AWS discovery for:');
|
|
53
53
|
if (appDefinition.vpc?.enable) console.log(' ✅ VPC support');
|
|
54
|
-
if (appDefinition.encryption?.
|
|
54
|
+
if (appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption) console.log(' ✅ KMS encryption');
|
|
55
55
|
if (appDefinition.ssm?.enable) console.log(' ✅ SSM parameters');
|
|
56
56
|
|
|
57
57
|
// Run discovery
|
|
@@ -82,7 +82,7 @@ async function runDiscovery() {
|
|
|
82
82
|
} else {
|
|
83
83
|
console.error('🚨 Discovery is required because your AppDefinition has these features enabled:');
|
|
84
84
|
if (appDefinition.vpc?.enable) console.error(' ❌ VPC support (vpc.enable: true)');
|
|
85
|
-
if (appDefinition.encryption?.
|
|
85
|
+
if (appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption) console.error(' ❌ KMS encryption (encryption.useDefaultKMSForFieldLevelEncryption: true)');
|
|
86
86
|
if (appDefinition.ssm?.enable) console.error(' ❌ SSM parameters (ssm.enable: true)');
|
|
87
87
|
console.error('');
|
|
88
88
|
console.error('💡 To fix this issue:');
|
|
@@ -95,7 +95,7 @@ async function runDiscovery() {
|
|
|
95
95
|
|
|
96
96
|
console.error('🔧 Or disable features in backend/index.js:');
|
|
97
97
|
console.error(' vpc: { enable: false }');
|
|
98
|
-
console.error(' encryption: {
|
|
98
|
+
console.error(' encryption: { useDefaultKMSForFieldLevelEncryption: false }');
|
|
99
99
|
console.error(' ssm: { enable: false }');
|
|
100
100
|
|
|
101
101
|
process.exit(1);
|
|
@@ -10,7 +10,8 @@ const { AWSDiscovery } = require('./aws-discovery');
|
|
|
10
10
|
const shouldRunDiscovery = (AppDefinition) => {
|
|
11
11
|
return (
|
|
12
12
|
AppDefinition.vpc?.enable === true ||
|
|
13
|
-
AppDefinition.encryption?.
|
|
13
|
+
AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption ===
|
|
14
|
+
true ||
|
|
14
15
|
AppDefinition.ssm?.enable === true
|
|
15
16
|
);
|
|
16
17
|
};
|
|
@@ -492,7 +493,10 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
492
493
|
};
|
|
493
494
|
|
|
494
495
|
// KMS Interface Endpoint (paid, but useful if using KMS)
|
|
495
|
-
if (
|
|
496
|
+
if (
|
|
497
|
+
AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption ===
|
|
498
|
+
true
|
|
499
|
+
) {
|
|
496
500
|
vpcResources.FriggKMSVPCEndpoint = {
|
|
497
501
|
Type: 'AWS::EC2::VPCEndpoint',
|
|
498
502
|
Properties: {
|
|
@@ -887,7 +891,9 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
887
891
|
};
|
|
888
892
|
|
|
889
893
|
// KMS Configuration based on App Definition
|
|
890
|
-
if (
|
|
894
|
+
if (
|
|
895
|
+
AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true
|
|
896
|
+
) {
|
|
891
897
|
// Check if a KMS key was discovered
|
|
892
898
|
if (discoveredResources.defaultKmsKeyId) {
|
|
893
899
|
// Use the existing discovered KMS key
|
|
@@ -901,112 +907,55 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
901
907
|
Resource: [discoveredResources.defaultKmsKeyId],
|
|
902
908
|
});
|
|
903
909
|
|
|
904
|
-
|
|
910
|
+
definition.provider.environment.KMS_KEY_ARN =
|
|
911
|
+
discoveredResources.defaultKmsKeyId;
|
|
905
912
|
} else {
|
|
906
|
-
// No existing key found
|
|
907
|
-
|
|
908
|
-
// Create a new KMS key
|
|
909
|
-
console.log('No existing KMS key found, creating a new one...');
|
|
913
|
+
// No existing key found, provision a dedicated KMS key
|
|
914
|
+
console.log('No existing KMS key found, creating a new one...');
|
|
910
915
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
'
|
|
925
|
-
'arn:aws:iam::${AWS::AccountId}:root',
|
|
926
|
-
},
|
|
927
|
-
},
|
|
928
|
-
Action: 'kms:*',
|
|
929
|
-
Resource: '*',
|
|
930
|
-
},
|
|
931
|
-
{
|
|
932
|
-
Sid: 'AllowLambdaService',
|
|
933
|
-
Effect: 'Allow',
|
|
934
|
-
Principal: {
|
|
935
|
-
Service: 'lambda.amazonaws.com',
|
|
936
|
-
},
|
|
937
|
-
Action: [
|
|
938
|
-
'kms:GenerateDataKey',
|
|
939
|
-
'kms:Decrypt',
|
|
940
|
-
'kms:DescribeKey',
|
|
941
|
-
],
|
|
942
|
-
Resource: '*',
|
|
943
|
-
Condition: {
|
|
944
|
-
StringEquals: {
|
|
945
|
-
'kms:ViaService': `lambda.${
|
|
946
|
-
process.env.AWS_REGION ||
|
|
947
|
-
'us-east-1'
|
|
948
|
-
}.amazonaws.com`,
|
|
949
|
-
},
|
|
916
|
+
definition.resources.Resources.FriggKMSKey = {
|
|
917
|
+
Type: 'AWS::KMS::Key',
|
|
918
|
+
Properties: {
|
|
919
|
+
EnableKeyRotation: true,
|
|
920
|
+
KeyPolicy: {
|
|
921
|
+
Version: '2012-10-17',
|
|
922
|
+
Statement: [
|
|
923
|
+
{
|
|
924
|
+
Sid: 'AllowRootAccountAdmin',
|
|
925
|
+
Effect: 'Allow',
|
|
926
|
+
Principal: {
|
|
927
|
+
AWS: {
|
|
928
|
+
'Fn::Sub':
|
|
929
|
+
'arn:aws:iam::${AWS::AccountId}:root',
|
|
950
930
|
},
|
|
951
931
|
},
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
Tags: [
|
|
955
|
-
{
|
|
956
|
-
Key: 'Name',
|
|
957
|
-
Value: '${self:service}-${self:provider.stage}-frigg-kms-key',
|
|
958
|
-
},
|
|
959
|
-
{
|
|
960
|
-
Key: 'Purpose',
|
|
961
|
-
Value: 'Field-level encryption for Frigg application',
|
|
932
|
+
Action: 'kms:*',
|
|
933
|
+
Resource: '*',
|
|
962
934
|
},
|
|
963
935
|
],
|
|
964
936
|
},
|
|
965
|
-
}
|
|
966
|
-
|
|
967
|
-
definition.provider.iamRoleStatements.push({
|
|
968
|
-
Effect: 'Allow',
|
|
969
|
-
Action: ['kms:GenerateDataKey', 'kms:Decrypt'],
|
|
970
|
-
Resource: [{ 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] }],
|
|
971
|
-
});
|
|
937
|
+
},
|
|
938
|
+
};
|
|
972
939
|
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
940
|
+
definition.provider.iamRoleStatements.push({
|
|
941
|
+
Effect: 'Allow',
|
|
942
|
+
Action: ['kms:GenerateDataKey', 'kms:Decrypt'],
|
|
943
|
+
Resource: [{ 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] }],
|
|
944
|
+
});
|
|
976
945
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
};
|
|
981
|
-
} else {
|
|
982
|
-
// No key found and createIfNoneFound is not enabled - error
|
|
983
|
-
throw new Error(
|
|
984
|
-
'KMS field-level encryption is enabled but no KMS key was found. ' +
|
|
985
|
-
'Either provide an existing KMS key or set encryption.createResourceIfNoneFound to true to create a new key.'
|
|
986
|
-
);
|
|
987
|
-
}
|
|
946
|
+
definition.provider.environment.KMS_KEY_ARN = {
|
|
947
|
+
'Fn::GetAtt': ['FriggKMSKey', 'Arn'],
|
|
948
|
+
};
|
|
988
949
|
}
|
|
989
950
|
|
|
990
951
|
definition.plugins.push('serverless-kms-grants');
|
|
991
952
|
|
|
992
|
-
// Configure KMS grants
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
kmsKeyId:
|
|
996
|
-
discoveredResources.defaultKmsKeyId ||
|
|
997
|
-
'${env:AWS_DISCOVERY_KMS_KEY_ID}',
|
|
998
|
-
};
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
// Always set KMS_KEY_ARN from custom.kmsGrants for consistency
|
|
1002
|
-
// This translates AWS_DISCOVERY_KMS_KEY_ID to the runtime variable KMS_KEY_ARN
|
|
1003
|
-
if (!definition.provider.environment.KMS_KEY_ARN) {
|
|
1004
|
-
// Use the discovered value directly when available (from in-process discovery)
|
|
1005
|
-
// Otherwise fall back to environment variable (from separate discovery process)
|
|
1006
|
-
definition.provider.environment.KMS_KEY_ARN =
|
|
953
|
+
// Configure KMS grants with discovered default key or environment variable
|
|
954
|
+
definition.custom.kmsGrants = {
|
|
955
|
+
kmsKeyId:
|
|
1007
956
|
discoveredResources.defaultKmsKeyId ||
|
|
1008
|
-
'${env:AWS_DISCOVERY_KMS_KEY_ID}'
|
|
1009
|
-
}
|
|
957
|
+
'${env:AWS_DISCOVERY_KMS_KEY_ID}',
|
|
958
|
+
};
|
|
1010
959
|
}
|
|
1011
960
|
|
|
1012
961
|
// VPC Configuration based on App Definition
|
|
@@ -1303,6 +1252,62 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
1303
1252
|
definition.custom[queueReference] = queueName;
|
|
1304
1253
|
}
|
|
1305
1254
|
}
|
|
1255
|
+
|
|
1256
|
+
// Discovery has already run successfully at this point if needed
|
|
1257
|
+
// The discoveredResources object contains all the necessary AWS resources
|
|
1258
|
+
|
|
1259
|
+
// Add websocket function if enabled
|
|
1260
|
+
if (AppDefinition.websockets?.enable === true) {
|
|
1261
|
+
definition.functions.defaultWebsocket = {
|
|
1262
|
+
handler:
|
|
1263
|
+
'node_modules/@friggframework/core/handlers/routers/websocket.handler',
|
|
1264
|
+
events: [
|
|
1265
|
+
{
|
|
1266
|
+
websocket: {
|
|
1267
|
+
route: '$connect',
|
|
1268
|
+
},
|
|
1269
|
+
},
|
|
1270
|
+
{
|
|
1271
|
+
websocket: {
|
|
1272
|
+
route: '$default',
|
|
1273
|
+
},
|
|
1274
|
+
},
|
|
1275
|
+
{
|
|
1276
|
+
websocket: {
|
|
1277
|
+
route: '$disconnect',
|
|
1278
|
+
},
|
|
1279
|
+
},
|
|
1280
|
+
],
|
|
1281
|
+
};
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
// Discovery has already run successfully at this point if needed
|
|
1285
|
+
// The discoveredResources object contains all the necessary AWS resources
|
|
1286
|
+
|
|
1287
|
+
// Add websocket function if enabled
|
|
1288
|
+
if (AppDefinition.websockets?.enable === true) {
|
|
1289
|
+
definition.functions.defaultWebsocket = {
|
|
1290
|
+
handler:
|
|
1291
|
+
'node_modules/@friggframework/core/handlers/routers/websocket.handler',
|
|
1292
|
+
events: [
|
|
1293
|
+
{
|
|
1294
|
+
websocket: {
|
|
1295
|
+
route: '$connect',
|
|
1296
|
+
},
|
|
1297
|
+
},
|
|
1298
|
+
{
|
|
1299
|
+
websocket: {
|
|
1300
|
+
route: '$default',
|
|
1301
|
+
},
|
|
1302
|
+
},
|
|
1303
|
+
{
|
|
1304
|
+
websocket: {
|
|
1305
|
+
route: '$disconnect',
|
|
1306
|
+
},
|
|
1307
|
+
},
|
|
1308
|
+
],
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1306
1311
|
}
|
|
1307
1312
|
|
|
1308
1313
|
// Discovery has already run successfully at this point if needed
|
|
@@ -1333,6 +1338,9 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
1333
1338
|
};
|
|
1334
1339
|
}
|
|
1335
1340
|
|
|
1341
|
+
// Discovery has already run successfully at this point if needed
|
|
1342
|
+
// The discoveredResources object contains all the necessary AWS resources
|
|
1343
|
+
|
|
1336
1344
|
// Modify handler paths to point to the correct node_modules location
|
|
1337
1345
|
definition.functions = modifyHandlerPaths(definition.functions);
|
|
1338
1346
|
|
|
@@ -118,9 +118,16 @@ describe('composeServerlessDefinition', () => {
|
|
|
118
118
|
|
|
119
119
|
const result = await composeServerlessDefinition(appDefinition);
|
|
120
120
|
|
|
121
|
-
expect(result.provider.vpc).
|
|
122
|
-
expect(result.
|
|
123
|
-
|
|
121
|
+
expect(result.provider.vpc).toBe('${self:custom.vpc.${self:provider.stage}}');
|
|
122
|
+
expect(result.custom.vpc).toEqual({
|
|
123
|
+
'${self:provider.stage}': {
|
|
124
|
+
securityGroupIds: ['${env:AWS_DISCOVERY_SECURITY_GROUP_ID}'],
|
|
125
|
+
subnetIds: [
|
|
126
|
+
'${env:AWS_DISCOVERY_SUBNET_ID_1}',
|
|
127
|
+
'${env:AWS_DISCOVERY_SUBNET_ID_2}'
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
});
|
|
124
131
|
});
|
|
125
132
|
|
|
126
133
|
it('should add VPC endpoint for S3 when VPC is enabled', async () => {
|
|
@@ -131,9 +138,15 @@ describe('composeServerlessDefinition', () => {
|
|
|
131
138
|
|
|
132
139
|
const result = await composeServerlessDefinition(appDefinition);
|
|
133
140
|
|
|
134
|
-
expect(result.resources.Resources.VPCEndpointS3).
|
|
135
|
-
|
|
136
|
-
|
|
141
|
+
expect(result.resources.Resources.VPCEndpointS3).toEqual({
|
|
142
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
143
|
+
Properties: {
|
|
144
|
+
VpcId: '${env:AWS_DISCOVERY_VPC_ID}',
|
|
145
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.s3',
|
|
146
|
+
VpcEndpointType: 'Gateway',
|
|
147
|
+
RouteTableIds: ['${env:AWS_DISCOVERY_ROUTE_TABLE_ID}']
|
|
148
|
+
}
|
|
149
|
+
});
|
|
137
150
|
});
|
|
138
151
|
|
|
139
152
|
it('should not add VPC configuration when vpc.enable is false', async () => {
|
|
@@ -145,6 +158,7 @@ describe('composeServerlessDefinition', () => {
|
|
|
145
158
|
const result = await composeServerlessDefinition(appDefinition);
|
|
146
159
|
|
|
147
160
|
expect(result.provider.vpc).toBeUndefined();
|
|
161
|
+
expect(result.custom.vpc).toBeUndefined();
|
|
148
162
|
expect(result.resources.Resources.VPCEndpointS3).toBeUndefined();
|
|
149
163
|
});
|
|
150
164
|
|
|
@@ -156,13 +170,14 @@ describe('composeServerlessDefinition', () => {
|
|
|
156
170
|
const result = await composeServerlessDefinition(appDefinition);
|
|
157
171
|
|
|
158
172
|
expect(result.provider.vpc).toBeUndefined();
|
|
173
|
+
expect(result.custom.vpc).toBeUndefined();
|
|
159
174
|
});
|
|
160
175
|
});
|
|
161
176
|
|
|
162
177
|
describe('KMS Configuration', () => {
|
|
163
|
-
it('should add KMS configuration when encryption is enabled
|
|
178
|
+
it('should add KMS configuration when encryption is enabled', async () => {
|
|
164
179
|
const appDefinition = {
|
|
165
|
-
encryption: {
|
|
180
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
166
181
|
integrations: []
|
|
167
182
|
};
|
|
168
183
|
|
|
@@ -178,188 +193,24 @@ describe('composeServerlessDefinition', () => {
|
|
|
178
193
|
'kms:GenerateDataKey',
|
|
179
194
|
'kms:Decrypt'
|
|
180
195
|
],
|
|
181
|
-
Resource: ['
|
|
196
|
+
Resource: ['${self:custom.kmsGrants.kmsKeyId}']
|
|
182
197
|
});
|
|
183
198
|
|
|
184
199
|
// Check environment variable
|
|
185
|
-
expect(result.provider.environment.KMS_KEY_ARN).toBe('
|
|
200
|
+
expect(result.provider.environment.KMS_KEY_ARN).toBe('${self:custom.kmsGrants.kmsKeyId}');
|
|
186
201
|
|
|
187
202
|
// Check plugin
|
|
188
203
|
expect(result.plugins).toContain('serverless-kms-grants');
|
|
189
204
|
|
|
190
205
|
// Check custom configuration
|
|
191
206
|
expect(result.custom.kmsGrants).toEqual({
|
|
192
|
-
kmsKeyId: '
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it('should create new KMS key when encryption is enabled, no key found, and createResourceIfNoneFound is true', async () => {
|
|
197
|
-
// Mock AWS discovery to return no KMS key
|
|
198
|
-
const { AWSDiscovery } = require('./aws-discovery');
|
|
199
|
-
const mockDiscoverResources = jest.fn().mockResolvedValue({
|
|
200
|
-
defaultVpcId: 'vpc-123456',
|
|
201
|
-
defaultSecurityGroupId: 'sg-123456',
|
|
202
|
-
privateSubnetId1: 'subnet-123456',
|
|
203
|
-
privateSubnetId2: 'subnet-789012',
|
|
204
|
-
publicSubnetId: 'subnet-public',
|
|
205
|
-
defaultRouteTableId: 'rtb-123456',
|
|
206
|
-
defaultKmsKeyId: null // No KMS key found
|
|
207
|
-
});
|
|
208
|
-
AWSDiscovery.mockImplementation(() => ({
|
|
209
|
-
discoverResources: mockDiscoverResources
|
|
210
|
-
}));
|
|
211
|
-
|
|
212
|
-
const appDefinition = {
|
|
213
|
-
encryption: {
|
|
214
|
-
fieldLevelEncryptionMethod: 'kms',
|
|
215
|
-
createResourceIfNoneFound: true
|
|
216
|
-
},
|
|
217
|
-
integrations: []
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
const result = await composeServerlessDefinition(appDefinition);
|
|
221
|
-
|
|
222
|
-
// Check that KMS key resource was created
|
|
223
|
-
expect(result.resources.Resources.FriggKMSKey).toEqual({
|
|
224
|
-
Type: 'AWS::KMS::Key',
|
|
225
|
-
Properties: {
|
|
226
|
-
EnableKeyRotation: true,
|
|
227
|
-
Description: 'Frigg KMS key for field-level encryption',
|
|
228
|
-
KeyPolicy: {
|
|
229
|
-
Version: '2012-10-17',
|
|
230
|
-
Statement: [
|
|
231
|
-
{
|
|
232
|
-
Sid: 'AllowRootAccountAdmin',
|
|
233
|
-
Effect: 'Allow',
|
|
234
|
-
Principal: {
|
|
235
|
-
AWS: {
|
|
236
|
-
'Fn::Sub': 'arn:aws:iam::${AWS::AccountId}:root'
|
|
237
|
-
}
|
|
238
|
-
},
|
|
239
|
-
Action: 'kms:*',
|
|
240
|
-
Resource: '*'
|
|
241
|
-
},
|
|
242
|
-
{
|
|
243
|
-
Sid: 'AllowLambdaService',
|
|
244
|
-
Effect: 'Allow',
|
|
245
|
-
Principal: {
|
|
246
|
-
Service: 'lambda.amazonaws.com'
|
|
247
|
-
},
|
|
248
|
-
Action: [
|
|
249
|
-
'kms:GenerateDataKey',
|
|
250
|
-
'kms:Decrypt',
|
|
251
|
-
'kms:DescribeKey'
|
|
252
|
-
],
|
|
253
|
-
Resource: '*',
|
|
254
|
-
Condition: {
|
|
255
|
-
StringEquals: {
|
|
256
|
-
'kms:ViaService': 'lambda.us-east-1.amazonaws.com'
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
]
|
|
261
|
-
},
|
|
262
|
-
Tags: [
|
|
263
|
-
{
|
|
264
|
-
Key: 'Name',
|
|
265
|
-
Value: '${self:service}-${self:provider.stage}-frigg-kms-key'
|
|
266
|
-
},
|
|
267
|
-
{
|
|
268
|
-
Key: 'Purpose',
|
|
269
|
-
Value: 'Field-level encryption for Frigg application'
|
|
270
|
-
}
|
|
271
|
-
]
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
// Check IAM permissions for the new key
|
|
276
|
-
const kmsPermission = result.provider.iamRoleStatements.find(
|
|
277
|
-
statement => statement.Action.includes('kms:GenerateDataKey')
|
|
278
|
-
);
|
|
279
|
-
expect(kmsPermission).toEqual({
|
|
280
|
-
Effect: 'Allow',
|
|
281
|
-
Action: ['kms:GenerateDataKey', 'kms:Decrypt'],
|
|
282
|
-
Resource: [{ 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] }]
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
// Check environment variable
|
|
286
|
-
expect(result.provider.environment.KMS_KEY_ARN).toEqual({
|
|
287
|
-
'Fn::GetAtt': ['FriggKMSKey', 'Arn']
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
// Check plugin
|
|
291
|
-
expect(result.plugins).toContain('serverless-kms-grants');
|
|
292
|
-
|
|
293
|
-
// Check custom configuration
|
|
294
|
-
// When creating a new key, it should reference the CloudFormation resource
|
|
295
|
-
expect(result.custom.kmsGrants).toEqual({
|
|
296
|
-
kmsKeyId: { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] }
|
|
297
|
-
});
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
it('should throw error when encryption is enabled, no key found, and createResourceIfNoneFound is false', async () => {
|
|
301
|
-
// Mock AWS discovery to return no KMS key
|
|
302
|
-
const { AWSDiscovery } = require('./aws-discovery');
|
|
303
|
-
const mockDiscoverResources = jest.fn().mockResolvedValue({
|
|
304
|
-
defaultVpcId: 'vpc-123456',
|
|
305
|
-
defaultSecurityGroupId: 'sg-123456',
|
|
306
|
-
privateSubnetId1: 'subnet-123456',
|
|
307
|
-
privateSubnetId2: 'subnet-789012',
|
|
308
|
-
publicSubnetId: 'subnet-public',
|
|
309
|
-
defaultRouteTableId: 'rtb-123456',
|
|
310
|
-
defaultKmsKeyId: null // No KMS key found
|
|
311
|
-
});
|
|
312
|
-
AWSDiscovery.mockImplementation(() => ({
|
|
313
|
-
discoverResources: mockDiscoverResources
|
|
314
|
-
}));
|
|
315
|
-
|
|
316
|
-
const appDefinition = {
|
|
317
|
-
encryption: {
|
|
318
|
-
fieldLevelEncryptionMethod: 'kms',
|
|
319
|
-
createResourceIfNoneFound: false
|
|
320
|
-
},
|
|
321
|
-
integrations: []
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow(
|
|
325
|
-
'KMS field-level encryption is enabled but no KMS key was found. ' +
|
|
326
|
-
'Either provide an existing KMS key or set encryption.createResourceIfNoneFound to true to create a new key.'
|
|
327
|
-
);
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
it('should throw error when encryption is enabled, no key found, and createResourceIfNoneFound is not specified', async () => {
|
|
331
|
-
// Mock AWS discovery to return no KMS key
|
|
332
|
-
const { AWSDiscovery } = require('./aws-discovery');
|
|
333
|
-
const mockDiscoverResources = jest.fn().mockResolvedValue({
|
|
334
|
-
defaultVpcId: 'vpc-123456',
|
|
335
|
-
defaultSecurityGroupId: 'sg-123456',
|
|
336
|
-
privateSubnetId1: 'subnet-123456',
|
|
337
|
-
privateSubnetId2: 'subnet-789012',
|
|
338
|
-
publicSubnetId: 'subnet-public',
|
|
339
|
-
defaultRouteTableId: 'rtb-123456',
|
|
340
|
-
defaultKmsKeyId: null // No KMS key found
|
|
207
|
+
kmsKeyId: '${env:AWS_DISCOVERY_KMS_KEY_ID}'
|
|
341
208
|
});
|
|
342
|
-
AWSDiscovery.mockImplementation(() => ({
|
|
343
|
-
discoverResources: mockDiscoverResources
|
|
344
|
-
}));
|
|
345
|
-
|
|
346
|
-
const appDefinition = {
|
|
347
|
-
encryption: {
|
|
348
|
-
fieldLevelEncryptionMethod: 'kms'
|
|
349
|
-
// createResourceIfNoneFound not specified, defaults to false
|
|
350
|
-
},
|
|
351
|
-
integrations: []
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow(
|
|
355
|
-
'KMS field-level encryption is enabled but no KMS key was found. ' +
|
|
356
|
-
'Either provide an existing KMS key or set encryption.createResourceIfNoneFound to true to create a new key.'
|
|
357
|
-
);
|
|
358
209
|
});
|
|
359
210
|
|
|
360
211
|
it('should not add KMS configuration when encryption is disabled', async () => {
|
|
361
212
|
const appDefinition = {
|
|
362
|
-
encryption: {
|
|
213
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: false },
|
|
363
214
|
integrations: []
|
|
364
215
|
};
|
|
365
216
|
|
|
@@ -464,9 +315,10 @@ describe('composeServerlessDefinition', () => {
|
|
|
464
315
|
expect(result.functions.testIntegration).toEqual({
|
|
465
316
|
handler: 'node_modules/@friggframework/core/handlers/routers/integration-defined-routers.handlers.testIntegration.handler',
|
|
466
317
|
events: [{
|
|
467
|
-
|
|
318
|
+
http: {
|
|
468
319
|
path: '/api/testIntegration-integration/{proxy+}',
|
|
469
|
-
method: 'ANY'
|
|
320
|
+
method: 'ANY',
|
|
321
|
+
cors: true
|
|
470
322
|
}
|
|
471
323
|
}]
|
|
472
324
|
});
|
|
@@ -537,7 +389,7 @@ describe('composeServerlessDefinition', () => {
|
|
|
537
389
|
it('should combine VPC, KMS, and SSM configurations', async () => {
|
|
538
390
|
const appDefinition = {
|
|
539
391
|
vpc: { enable: true },
|
|
540
|
-
encryption: {
|
|
392
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
541
393
|
ssm: { enable: true },
|
|
542
394
|
integrations: [mockIntegration]
|
|
543
395
|
};
|
|
@@ -546,7 +398,7 @@ describe('composeServerlessDefinition', () => {
|
|
|
546
398
|
|
|
547
399
|
// VPC
|
|
548
400
|
expect(result.provider.vpc).toBeDefined();
|
|
549
|
-
|
|
401
|
+
expect(result.custom.vpc).toBeDefined();
|
|
550
402
|
expect(result.resources.Resources.VPCEndpointS3).toBeDefined();
|
|
551
403
|
|
|
552
404
|
// KMS
|
|
@@ -576,7 +428,7 @@ describe('composeServerlessDefinition', () => {
|
|
|
576
428
|
it('should handle partial configuration combinations', async () => {
|
|
577
429
|
const appDefinition = {
|
|
578
430
|
vpc: { enable: true },
|
|
579
|
-
encryption: {
|
|
431
|
+
encryption: { useDefaultKMSForFieldLevelEncryption: true },
|
|
580
432
|
integrations: []
|
|
581
433
|
};
|
|
582
434
|
|
|
@@ -607,9 +459,9 @@ describe('composeServerlessDefinition', () => {
|
|
|
607
459
|
expect(result.resources.Resources.ApiGatewayAlarm5xx).toBeDefined();
|
|
608
460
|
|
|
609
461
|
// Check default functions
|
|
462
|
+
expect(result.functions.defaultWebsocket).toBeDefined();
|
|
610
463
|
expect(result.functions.auth).toBeDefined();
|
|
611
464
|
expect(result.functions.user).toBeDefined();
|
|
612
|
-
expect(result.functions.health).toBeDefined();
|
|
613
465
|
|
|
614
466
|
// Check default plugins
|
|
615
467
|
expect(result.plugins).toContain('serverless-jetpack');
|
|
@@ -644,64 +496,11 @@ describe('composeServerlessDefinition', () => {
|
|
|
644
496
|
|
|
645
497
|
const result = await composeServerlessDefinition(appDefinition);
|
|
646
498
|
|
|
647
|
-
expect(result.provider.environment.STAGE).toBe('${opt:stage
|
|
499
|
+
expect(result.provider.environment.STAGE).toBe('${opt:stage}');
|
|
648
500
|
expect(result.provider.environment.AWS_NODEJS_CONNECTION_REUSE_ENABLED).toBe(1);
|
|
649
501
|
});
|
|
650
502
|
});
|
|
651
503
|
|
|
652
|
-
describe('WebSocket Configuration', () => {
|
|
653
|
-
it('should add websocket function when websockets.enable is true', async () => {
|
|
654
|
-
const appDefinition = {
|
|
655
|
-
websockets: { enable: true },
|
|
656
|
-
integrations: []
|
|
657
|
-
};
|
|
658
|
-
|
|
659
|
-
const result = await composeServerlessDefinition(appDefinition);
|
|
660
|
-
|
|
661
|
-
expect(result.functions.defaultWebsocket).toEqual({
|
|
662
|
-
handler: 'node_modules/@friggframework/core/handlers/routers/websocket.handler',
|
|
663
|
-
events: [
|
|
664
|
-
{
|
|
665
|
-
websocket: {
|
|
666
|
-
route: '$connect',
|
|
667
|
-
},
|
|
668
|
-
},
|
|
669
|
-
{
|
|
670
|
-
websocket: {
|
|
671
|
-
route: '$default',
|
|
672
|
-
},
|
|
673
|
-
},
|
|
674
|
-
{
|
|
675
|
-
websocket: {
|
|
676
|
-
route: '$disconnect',
|
|
677
|
-
},
|
|
678
|
-
},
|
|
679
|
-
],
|
|
680
|
-
});
|
|
681
|
-
});
|
|
682
|
-
|
|
683
|
-
it('should not add websocket function when websockets.enable is false', async () => {
|
|
684
|
-
const appDefinition = {
|
|
685
|
-
websockets: { enable: false },
|
|
686
|
-
integrations: []
|
|
687
|
-
};
|
|
688
|
-
|
|
689
|
-
const result = await composeServerlessDefinition(appDefinition);
|
|
690
|
-
|
|
691
|
-
expect(result.functions.defaultWebsocket).toBeUndefined();
|
|
692
|
-
});
|
|
693
|
-
|
|
694
|
-
it('should not add websocket function when websockets is not defined', async () => {
|
|
695
|
-
const appDefinition = {
|
|
696
|
-
integrations: []
|
|
697
|
-
};
|
|
698
|
-
|
|
699
|
-
const result = await composeServerlessDefinition(appDefinition);
|
|
700
|
-
|
|
701
|
-
expect(result.functions.defaultWebsocket).toBeUndefined();
|
|
702
|
-
});
|
|
703
|
-
});
|
|
704
|
-
|
|
705
504
|
describe('Edge Cases', () => {
|
|
706
505
|
it('should handle empty app definition', async () => {
|
|
707
506
|
const appDefinition = {};
|
|
@@ -716,9 +515,7 @@ describe('composeServerlessDefinition', () => {
|
|
|
716
515
|
integrations: null
|
|
717
516
|
};
|
|
718
517
|
|
|
719
|
-
|
|
720
|
-
const result = await composeServerlessDefinition(appDefinition);
|
|
721
|
-
expect(result).toBeDefined();
|
|
518
|
+
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow();
|
|
722
519
|
});
|
|
723
520
|
|
|
724
521
|
it('should handle integration with missing Definition', async () => {
|
|
@@ -727,7 +524,7 @@ describe('composeServerlessDefinition', () => {
|
|
|
727
524
|
integrations: [invalidIntegration]
|
|
728
525
|
};
|
|
729
526
|
|
|
730
|
-
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow(
|
|
527
|
+
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow();
|
|
731
528
|
});
|
|
732
529
|
|
|
733
530
|
it('should handle integration with missing name', async () => {
|
|
@@ -738,7 +535,7 @@ describe('composeServerlessDefinition', () => {
|
|
|
738
535
|
integrations: [invalidIntegration]
|
|
739
536
|
};
|
|
740
537
|
|
|
741
|
-
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow(
|
|
538
|
+
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow();
|
|
742
539
|
});
|
|
743
540
|
});
|
|
744
541
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/devtools",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.
|
|
4
|
+
"version": "2.0.0--canary.427.c8ff14b.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-ec2": "^3.835.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.835.0",
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"@babel/eslint-parser": "^7.18.9",
|
|
10
10
|
"@babel/parser": "^7.25.3",
|
|
11
11
|
"@babel/traverse": "^7.25.3",
|
|
12
|
-
"@friggframework/schemas": "2.0.0--canary.
|
|
13
|
-
"@friggframework/test": "2.0.0--canary.
|
|
12
|
+
"@friggframework/schemas": "2.0.0--canary.427.c8ff14b.0",
|
|
13
|
+
"@friggframework/test": "2.0.0--canary.427.c8ff14b.0",
|
|
14
14
|
"@hapi/boom": "^10.0.1",
|
|
15
15
|
"@inquirer/prompts": "^5.3.8",
|
|
16
16
|
"axios": "^1.7.2",
|
|
@@ -32,9 +32,8 @@
|
|
|
32
32
|
"serverless-http": "^2.7.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@friggframework/eslint-config": "2.0.0--canary.
|
|
36
|
-
"@friggframework/prettier-config": "2.0.0--canary.
|
|
37
|
-
"jest": "^30.1.3",
|
|
35
|
+
"@friggframework/eslint-config": "2.0.0--canary.427.c8ff14b.0",
|
|
36
|
+
"@friggframework/prettier-config": "2.0.0--canary.427.c8ff14b.0",
|
|
38
37
|
"prettier": "^2.7.1",
|
|
39
38
|
"serverless": "3.39.0",
|
|
40
39
|
"serverless-dotenv-plugin": "^6.0.0",
|
|
@@ -66,5 +65,5 @@
|
|
|
66
65
|
"publishConfig": {
|
|
67
66
|
"access": "public"
|
|
68
67
|
},
|
|
69
|
-
"gitHead": "
|
|
68
|
+
"gitHead": "c8ff14b4f6c1a21bd6c254ad12f1c9963b0374db"
|
|
70
69
|
}
|