@friggframework/devtools 2.0.0-next.36 → 2.0.0-next.38
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 +116 -105
- package/infrastructure/__tests__/fixtures/mock-aws-resources.js +4 -4
- package/infrastructure/__tests__/helpers/test-utils.js +1 -1
- package/infrastructure/aws-discovery.js +14 -12
- package/infrastructure/build-time-discovery.js +3 -3
- package/infrastructure/build-time-discovery.test.js +1 -1
- package/infrastructure/iam-generator.js +15 -3
- 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 +102 -103
- package/infrastructure/serverless-template.test.js +241 -38
- package/package.json +7 -6
|
@@ -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: { fieldLevelEncryptionMethod: 'kms' },
|
|
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 =
|
|
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: { fieldLevelEncryptionMethod: 'kms' },
|
|
177
177
|
integrations: []
|
|
178
178
|
};
|
|
179
179
|
|
|
180
|
-
process.env.AWS_DISCOVERY_KMS_KEY_ID =
|
|
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: { fieldLevelEncryptionMethod: 'kms' }
|
|
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 =
|
|
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: { fieldLevelEncryptionMethod: 'kms' },
|
|
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?.fieldLevelEncryptionMethod === 'kms' ||
|
|
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?.fieldLevelEncryptionMethod === 'kms') 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?.fieldLevelEncryptionMethod === 'kms') console.error(' ❌ KMS encryption (encryption.fieldLevelEncryptionMethod: \'kms\')');
|
|
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: { fieldLevelEncryptionMethod: \'aes\' }');
|
|
99
99
|
console.error(' ssm: { enable: false }');
|
|
100
100
|
|
|
101
101
|
process.exit(1);
|
|
@@ -10,8 +10,7 @@ const { AWSDiscovery } = require('./aws-discovery');
|
|
|
10
10
|
const shouldRunDiscovery = (AppDefinition) => {
|
|
11
11
|
return (
|
|
12
12
|
AppDefinition.vpc?.enable === true ||
|
|
13
|
-
AppDefinition.encryption?.
|
|
14
|
-
true ||
|
|
13
|
+
AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms' ||
|
|
15
14
|
AppDefinition.ssm?.enable === true
|
|
16
15
|
);
|
|
17
16
|
};
|
|
@@ -450,6 +449,13 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
450
449
|
CidrIp: '0.0.0.0/0',
|
|
451
450
|
Description: 'DNS UDP',
|
|
452
451
|
},
|
|
452
|
+
{
|
|
453
|
+
IpProtocol: 'tcp',
|
|
454
|
+
FromPort: 27017,
|
|
455
|
+
ToPort: 27017,
|
|
456
|
+
CidrIp: '0.0.0.0/0',
|
|
457
|
+
Description: 'MongoDB outbound',
|
|
458
|
+
},
|
|
453
459
|
],
|
|
454
460
|
Tags: [
|
|
455
461
|
{
|
|
@@ -486,10 +492,7 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
486
492
|
};
|
|
487
493
|
|
|
488
494
|
// KMS Interface Endpoint (paid, but useful if using KMS)
|
|
489
|
-
if (
|
|
490
|
-
AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption ===
|
|
491
|
-
true
|
|
492
|
-
) {
|
|
495
|
+
if (AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') {
|
|
493
496
|
vpcResources.FriggKMSVPCEndpoint = {
|
|
494
497
|
Type: 'AWS::EC2::VPCEndpoint',
|
|
495
498
|
Properties: {
|
|
@@ -884,9 +887,7 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
884
887
|
};
|
|
885
888
|
|
|
886
889
|
// KMS Configuration based on App Definition
|
|
887
|
-
if (
|
|
888
|
-
AppDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true
|
|
889
|
-
) {
|
|
890
|
+
if (AppDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') {
|
|
890
891
|
// Check if a KMS key was discovered
|
|
891
892
|
if (discoveredResources.defaultKmsKeyId) {
|
|
892
893
|
// Use the existing discovered KMS key
|
|
@@ -900,55 +901,112 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
900
901
|
Resource: [discoveredResources.defaultKmsKeyId],
|
|
901
902
|
});
|
|
902
903
|
|
|
903
|
-
|
|
904
|
-
discoveredResources.defaultKmsKeyId;
|
|
904
|
+
// KMS_KEY_ARN will be set later from custom.kmsGrants for consistency
|
|
905
905
|
} else {
|
|
906
|
-
// No existing key found
|
|
907
|
-
|
|
906
|
+
// No existing key found - check if we should create one or error
|
|
907
|
+
if (AppDefinition.encryption?.createResourceIfNoneFound === true) {
|
|
908
|
+
// Create a new KMS key
|
|
909
|
+
console.log('No existing KMS key found, creating a new one...');
|
|
908
910
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
'
|
|
911
|
+
definition.resources.Resources.FriggKMSKey = {
|
|
912
|
+
Type: 'AWS::KMS::Key',
|
|
913
|
+
Properties: {
|
|
914
|
+
EnableKeyRotation: true,
|
|
915
|
+
Description: 'Frigg KMS key for field-level encryption',
|
|
916
|
+
KeyPolicy: {
|
|
917
|
+
Version: '2012-10-17',
|
|
918
|
+
Statement: [
|
|
919
|
+
{
|
|
920
|
+
Sid: 'AllowRootAccountAdmin',
|
|
921
|
+
Effect: 'Allow',
|
|
922
|
+
Principal: {
|
|
923
|
+
AWS: {
|
|
924
|
+
'Fn::Sub':
|
|
925
|
+
'arn:aws:iam::${AWS::AccountId}:root',
|
|
926
|
+
},
|
|
923
927
|
},
|
|
928
|
+
Action: 'kms:*',
|
|
929
|
+
Resource: '*',
|
|
924
930
|
},
|
|
925
|
-
|
|
926
|
-
|
|
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
|
+
},
|
|
950
|
+
},
|
|
951
|
+
},
|
|
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',
|
|
927
962
|
},
|
|
928
963
|
],
|
|
929
964
|
},
|
|
930
|
-
}
|
|
931
|
-
};
|
|
965
|
+
};
|
|
932
966
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
967
|
+
definition.provider.iamRoleStatements.push({
|
|
968
|
+
Effect: 'Allow',
|
|
969
|
+
Action: ['kms:GenerateDataKey', 'kms:Decrypt'],
|
|
970
|
+
Resource: [{ 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] }],
|
|
971
|
+
});
|
|
938
972
|
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
973
|
+
definition.provider.environment.KMS_KEY_ARN = {
|
|
974
|
+
'Fn::GetAtt': ['FriggKMSKey', 'Arn'],
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
// Configure KMS grants to reference the created key
|
|
978
|
+
definition.custom.kmsGrants = {
|
|
979
|
+
kmsKeyId: { 'Fn::GetAtt': ['FriggKMSKey', 'Arn'] }
|
|
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
|
+
}
|
|
942
988
|
}
|
|
943
989
|
|
|
944
990
|
definition.plugins.push('serverless-kms-grants');
|
|
945
991
|
|
|
946
|
-
// Configure KMS grants
|
|
947
|
-
definition.custom.kmsGrants
|
|
948
|
-
|
|
992
|
+
// Configure KMS grants if not already set (when using existing key)
|
|
993
|
+
if (!definition.custom.kmsGrants) {
|
|
994
|
+
definition.custom.kmsGrants = {
|
|
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 =
|
|
949
1007
|
discoveredResources.defaultKmsKeyId ||
|
|
950
|
-
'${env:AWS_DISCOVERY_KMS_KEY_ID}'
|
|
951
|
-
}
|
|
1008
|
+
'${env:AWS_DISCOVERY_KMS_KEY_ID}';
|
|
1009
|
+
}
|
|
952
1010
|
}
|
|
953
1011
|
|
|
954
1012
|
// VPC Configuration based on App Definition
|
|
@@ -1245,62 +1303,6 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
1245
1303
|
definition.custom[queueReference] = queueName;
|
|
1246
1304
|
}
|
|
1247
1305
|
}
|
|
1248
|
-
|
|
1249
|
-
// Discovery has already run successfully at this point if needed
|
|
1250
|
-
// The discoveredResources object contains all the necessary AWS resources
|
|
1251
|
-
|
|
1252
|
-
// Add websocket function if enabled
|
|
1253
|
-
if (AppDefinition.websockets?.enable === true) {
|
|
1254
|
-
definition.functions.defaultWebsocket = {
|
|
1255
|
-
handler:
|
|
1256
|
-
'node_modules/@friggframework/core/handlers/routers/websocket.handler',
|
|
1257
|
-
events: [
|
|
1258
|
-
{
|
|
1259
|
-
websocket: {
|
|
1260
|
-
route: '$connect',
|
|
1261
|
-
},
|
|
1262
|
-
},
|
|
1263
|
-
{
|
|
1264
|
-
websocket: {
|
|
1265
|
-
route: '$default',
|
|
1266
|
-
},
|
|
1267
|
-
},
|
|
1268
|
-
{
|
|
1269
|
-
websocket: {
|
|
1270
|
-
route: '$disconnect',
|
|
1271
|
-
},
|
|
1272
|
-
},
|
|
1273
|
-
],
|
|
1274
|
-
};
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
// Discovery has already run successfully at this point if needed
|
|
1278
|
-
// The discoveredResources object contains all the necessary AWS resources
|
|
1279
|
-
|
|
1280
|
-
// Add websocket function if enabled
|
|
1281
|
-
if (AppDefinition.websockets?.enable === true) {
|
|
1282
|
-
definition.functions.defaultWebsocket = {
|
|
1283
|
-
handler:
|
|
1284
|
-
'node_modules/@friggframework/core/handlers/routers/websocket.handler',
|
|
1285
|
-
events: [
|
|
1286
|
-
{
|
|
1287
|
-
websocket: {
|
|
1288
|
-
route: '$connect',
|
|
1289
|
-
},
|
|
1290
|
-
},
|
|
1291
|
-
{
|
|
1292
|
-
websocket: {
|
|
1293
|
-
route: '$default',
|
|
1294
|
-
},
|
|
1295
|
-
},
|
|
1296
|
-
{
|
|
1297
|
-
websocket: {
|
|
1298
|
-
route: '$disconnect',
|
|
1299
|
-
},
|
|
1300
|
-
},
|
|
1301
|
-
],
|
|
1302
|
-
};
|
|
1303
|
-
}
|
|
1304
1306
|
}
|
|
1305
1307
|
|
|
1306
1308
|
// Discovery has already run successfully at this point if needed
|
|
@@ -1331,9 +1333,6 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
1331
1333
|
};
|
|
1332
1334
|
}
|
|
1333
1335
|
|
|
1334
|
-
// Discovery has already run successfully at this point if needed
|
|
1335
|
-
// The discoveredResources object contains all the necessary AWS resources
|
|
1336
|
-
|
|
1337
1336
|
// Modify handler paths to point to the correct node_modules location
|
|
1338
1337
|
definition.functions = modifyHandlerPaths(definition.functions);
|
|
1339
1338
|
|