@friggframework/devtools 2.0.0--canary.428.4968614.0 → 2.0.0--canary.428.db65660.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.
|
@@ -670,7 +670,7 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
670
670
|
vpcResources.FriggVPCEndpointSecurityGroup = {
|
|
671
671
|
Type: 'AWS::EC2::SecurityGroup',
|
|
672
672
|
Properties: {
|
|
673
|
-
GroupDescription: 'Security group for Frigg VPC Endpoints',
|
|
673
|
+
GroupDescription: 'Security group for Frigg VPC Endpoints - allows HTTPS from Lambda functions',
|
|
674
674
|
VpcId: { Ref: 'FriggVPC' },
|
|
675
675
|
SecurityGroupIngress: [
|
|
676
676
|
{
|
|
@@ -680,7 +680,15 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
680
680
|
SourceSecurityGroupId: {
|
|
681
681
|
Ref: 'FriggLambdaSecurityGroup',
|
|
682
682
|
},
|
|
683
|
-
Description: 'HTTPS from Lambda',
|
|
683
|
+
Description: 'HTTPS from Lambda security group',
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
// Also allow from VPC CIDR as fallback
|
|
687
|
+
IpProtocol: 'tcp',
|
|
688
|
+
FromPort: 443,
|
|
689
|
+
ToPort: 443,
|
|
690
|
+
CidrIp: AppDefinition.vpc.cidrBlock || '10.0.0.0/16',
|
|
691
|
+
Description: 'HTTPS from VPC CIDR (fallback)',
|
|
684
692
|
},
|
|
685
693
|
],
|
|
686
694
|
Tags: [
|
|
@@ -704,6 +712,10 @@ const createVPCInfrastructure = (AppDefinition) => {
|
|
|
704
712
|
Key: 'Type',
|
|
705
713
|
Value: 'VPCEndpoint',
|
|
706
714
|
},
|
|
715
|
+
{
|
|
716
|
+
Key: 'Purpose',
|
|
717
|
+
Value: 'Allow Lambda functions to access VPC endpoints',
|
|
718
|
+
},
|
|
707
719
|
],
|
|
708
720
|
},
|
|
709
721
|
};
|
|
@@ -2054,28 +2066,80 @@ const composeServerlessDefinition = async (AppDefinition) => {
|
|
|
2054
2066
|
!definition.resources.Resources
|
|
2055
2067
|
.VPCEndpointSecurityGroup
|
|
2056
2068
|
) {
|
|
2069
|
+
// Build ingress rules based on what we have
|
|
2070
|
+
const vpcEndpointIngressRules = [];
|
|
2071
|
+
|
|
2072
|
+
// CRITICAL: Allow from Lambda's security group (preferred method)
|
|
2073
|
+
if (vpcConfig.securityGroupIds && vpcConfig.securityGroupIds.length > 0) {
|
|
2074
|
+
// If we have the Lambda security group, reference it directly
|
|
2075
|
+
const lambdaSgId = vpcConfig.securityGroupIds[0];
|
|
2076
|
+
if (typeof lambdaSgId === 'string') {
|
|
2077
|
+
// It's a discovered security group ID
|
|
2078
|
+
vpcEndpointIngressRules.push({
|
|
2079
|
+
IpProtocol: 'tcp',
|
|
2080
|
+
FromPort: 443,
|
|
2081
|
+
ToPort: 443,
|
|
2082
|
+
SourceSecurityGroupId: lambdaSgId,
|
|
2083
|
+
Description: 'HTTPS from Lambda security group',
|
|
2084
|
+
});
|
|
2085
|
+
} else if (lambdaSgId && lambdaSgId.Ref) {
|
|
2086
|
+
// It's a CloudFormation reference
|
|
2087
|
+
vpcEndpointIngressRules.push({
|
|
2088
|
+
IpProtocol: 'tcp',
|
|
2089
|
+
FromPort: 443,
|
|
2090
|
+
ToPort: 443,
|
|
2091
|
+
SourceSecurityGroupId: lambdaSgId,
|
|
2092
|
+
Description: 'HTTPS from Lambda security group',
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
// Fallback: If we don't have Lambda SG, use VPC CIDR
|
|
2098
|
+
if (vpcEndpointIngressRules.length === 0 && discoveredResources.vpcCidr) {
|
|
2099
|
+
vpcEndpointIngressRules.push({
|
|
2100
|
+
IpProtocol: 'tcp',
|
|
2101
|
+
FromPort: 443,
|
|
2102
|
+
ToPort: 443,
|
|
2103
|
+
CidrIp: discoveredResources.vpcCidr,
|
|
2104
|
+
Description: 'HTTPS from VPC CIDR (fallback)',
|
|
2105
|
+
});
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
// Last resort: Allow from common private IP ranges
|
|
2109
|
+
if (vpcEndpointIngressRules.length === 0) {
|
|
2110
|
+
console.warn(
|
|
2111
|
+
'⚠️ WARNING: No Lambda security group or VPC CIDR found. Using default private IP ranges.'
|
|
2112
|
+
);
|
|
2113
|
+
vpcEndpointIngressRules.push({
|
|
2114
|
+
IpProtocol: 'tcp',
|
|
2115
|
+
FromPort: 443,
|
|
2116
|
+
ToPort: 443,
|
|
2117
|
+
CidrIp: '172.31.0.0/16', // Default VPC CIDR
|
|
2118
|
+
Description: 'HTTPS from default VPC range',
|
|
2119
|
+
});
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2057
2122
|
definition.resources.Resources.VPCEndpointSecurityGroup =
|
|
2058
2123
|
{
|
|
2059
2124
|
Type: 'AWS::EC2::SecurityGroup',
|
|
2060
2125
|
Properties: {
|
|
2061
2126
|
GroupDescription:
|
|
2062
|
-
'Security group for VPC endpoints',
|
|
2127
|
+
'Security group for VPC endpoints - allows HTTPS from Lambda functions',
|
|
2063
2128
|
VpcId: discoveredResources.defaultVpcId,
|
|
2064
|
-
SecurityGroupIngress:
|
|
2065
|
-
? [
|
|
2066
|
-
{
|
|
2067
|
-
IpProtocol: 'tcp',
|
|
2068
|
-
FromPort: 443,
|
|
2069
|
-
ToPort: 443,
|
|
2070
|
-
CidrIp: discoveredResources.vpcCidr, // Use discovered VPC CIDR
|
|
2071
|
-
},
|
|
2072
|
-
]
|
|
2073
|
-
: [], // Empty array if no VPC CIDR discovered
|
|
2129
|
+
SecurityGroupIngress: vpcEndpointIngressRules,
|
|
2074
2130
|
Tags: [
|
|
2075
2131
|
{
|
|
2076
2132
|
Key: 'Name',
|
|
2077
2133
|
Value: '${self:service}-${self:provider.stage}-vpc-endpoints-sg',
|
|
2078
2134
|
},
|
|
2135
|
+
{
|
|
2136
|
+
Key: 'ManagedBy',
|
|
2137
|
+
Value: 'Frigg',
|
|
2138
|
+
},
|
|
2139
|
+
{
|
|
2140
|
+
Key: 'Purpose',
|
|
2141
|
+
Value: 'Allow Lambda functions to access VPC endpoints',
|
|
2142
|
+
},
|
|
2079
2143
|
],
|
|
2080
2144
|
},
|
|
2081
2145
|
};
|
|
@@ -1,30 +1,43 @@
|
|
|
1
1
|
const { composeServerlessDefinition } = require('./serverless-template');
|
|
2
2
|
|
|
3
|
+
// Helper to build discovery responses with overridable fields
|
|
4
|
+
const createDiscoveryResponse = (overrides = {}) => ({
|
|
5
|
+
defaultVpcId: 'vpc-123456',
|
|
6
|
+
vpcCidr: '172.31.0.0/16', // Provide VPC CIDR so security group fallbacks can be tested
|
|
7
|
+
defaultSecurityGroupId: 'sg-123456',
|
|
8
|
+
privateSubnetId1: 'subnet-123456',
|
|
9
|
+
privateSubnetId2: 'subnet-789012',
|
|
10
|
+
publicSubnetId: 'subnet-public',
|
|
11
|
+
defaultRouteTableId: 'rtb-123456',
|
|
12
|
+
defaultKmsKeyId:
|
|
13
|
+
'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012',
|
|
14
|
+
existingNatGatewayId: 'nat-default123',
|
|
15
|
+
...overrides,
|
|
16
|
+
});
|
|
17
|
+
|
|
3
18
|
// Mock AWS Discovery to prevent actual AWS calls
|
|
4
19
|
jest.mock('./aws-discovery', () => {
|
|
5
20
|
return {
|
|
6
|
-
AWSDiscovery: jest.fn().mockImplementation(() => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
defaultSecurityGroupId: 'sg-123456',
|
|
12
|
-
privateSubnetId1: 'subnet-123456',
|
|
13
|
-
privateSubnetId2: 'subnet-789012',
|
|
14
|
-
publicSubnetId: 'subnet-public',
|
|
15
|
-
defaultRouteTableId: 'rtb-123456',
|
|
16
|
-
defaultKmsKeyId: 'arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012',
|
|
17
|
-
existingNatGatewayId: 'nat-default123' // Add default NAT Gateway for discover mode
|
|
18
|
-
})
|
|
19
|
-
};
|
|
20
|
-
})
|
|
21
|
+
AWSDiscovery: jest.fn().mockImplementation(() => ({
|
|
22
|
+
discoverResources: jest
|
|
23
|
+
.fn()
|
|
24
|
+
.mockResolvedValue(createDiscoveryResponse()),
|
|
25
|
+
})),
|
|
21
26
|
};
|
|
22
27
|
});
|
|
23
28
|
|
|
29
|
+
const { AWSDiscovery } = require('./aws-discovery');
|
|
30
|
+
|
|
24
31
|
describe('composeServerlessDefinition', () => {
|
|
25
32
|
let mockIntegration;
|
|
26
33
|
|
|
27
34
|
beforeEach(() => {
|
|
35
|
+
AWSDiscovery.mockImplementation(() => ({
|
|
36
|
+
discoverResources: jest
|
|
37
|
+
.fn()
|
|
38
|
+
.mockResolvedValue(createDiscoveryResponse()),
|
|
39
|
+
}));
|
|
40
|
+
|
|
28
41
|
mockIntegration = {
|
|
29
42
|
Definition: {
|
|
30
43
|
name: 'testIntegration'
|
|
@@ -344,6 +357,137 @@ describe('composeServerlessDefinition', () => {
|
|
|
344
357
|
expect(result.resources.Resources.VPCEndpointS3.Properties.VpcId).toBe('vpc-123456');
|
|
345
358
|
});
|
|
346
359
|
|
|
360
|
+
it('should allow Lambda security group access for VPC endpoints when security group is discovered', async () => {
|
|
361
|
+
const appDefinition = {
|
|
362
|
+
vpc: {
|
|
363
|
+
enable: true,
|
|
364
|
+
management: 'discover'
|
|
365
|
+
},
|
|
366
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
367
|
+
integrations: []
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
371
|
+
const endpointSg = result.resources.Resources.VPCEndpointSecurityGroup;
|
|
372
|
+
|
|
373
|
+
expect(endpointSg).toBeDefined();
|
|
374
|
+
expect(endpointSg.Properties.SecurityGroupIngress).toEqual([
|
|
375
|
+
{
|
|
376
|
+
IpProtocol: 'tcp',
|
|
377
|
+
FromPort: 443,
|
|
378
|
+
ToPort: 443,
|
|
379
|
+
SourceSecurityGroupId: 'sg-123456',
|
|
380
|
+
Description: 'HTTPS from Lambda security group'
|
|
381
|
+
}
|
|
382
|
+
]);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('should fall back to VPC CIDR when Lambda security group identifier cannot be resolved', async () => {
|
|
386
|
+
AWSDiscovery.mockImplementation(() => ({
|
|
387
|
+
discoverResources: jest
|
|
388
|
+
.fn()
|
|
389
|
+
.mockResolvedValue(createDiscoveryResponse()),
|
|
390
|
+
}));
|
|
391
|
+
|
|
392
|
+
const appDefinition = {
|
|
393
|
+
vpc: {
|
|
394
|
+
enable: true,
|
|
395
|
+
management: 'discover',
|
|
396
|
+
securityGroupIds: [
|
|
397
|
+
{
|
|
398
|
+
'Fn::ImportValue': 'shared-lambda-security-group',
|
|
399
|
+
},
|
|
400
|
+
],
|
|
401
|
+
},
|
|
402
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
403
|
+
integrations: [],
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
407
|
+
const endpointSg = result.resources.Resources.VPCEndpointSecurityGroup;
|
|
408
|
+
|
|
409
|
+
expect(endpointSg).toBeDefined();
|
|
410
|
+
expect(endpointSg.Properties.SecurityGroupIngress).toEqual([
|
|
411
|
+
{
|
|
412
|
+
IpProtocol: 'tcp',
|
|
413
|
+
FromPort: 443,
|
|
414
|
+
ToPort: 443,
|
|
415
|
+
CidrIp: '172.31.0.0/16',
|
|
416
|
+
Description: 'HTTPS from VPC CIDR (fallback)',
|
|
417
|
+
},
|
|
418
|
+
]);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it('should fall back to default private ranges when neither Lambda security group nor VPC CIDR is available', async () => {
|
|
422
|
+
AWSDiscovery.mockImplementation(() => ({
|
|
423
|
+
discoverResources: jest
|
|
424
|
+
.fn()
|
|
425
|
+
.mockResolvedValue(
|
|
426
|
+
createDiscoveryResponse({ vpcCidr: null })
|
|
427
|
+
),
|
|
428
|
+
}));
|
|
429
|
+
|
|
430
|
+
const appDefinition = {
|
|
431
|
+
vpc: {
|
|
432
|
+
enable: true,
|
|
433
|
+
management: 'discover',
|
|
434
|
+
securityGroupIds: [
|
|
435
|
+
{
|
|
436
|
+
'Fn::ImportValue': 'shared-lambda-security-group',
|
|
437
|
+
},
|
|
438
|
+
],
|
|
439
|
+
},
|
|
440
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
441
|
+
integrations: [],
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
445
|
+
const endpointSg = result.resources.Resources.VPCEndpointSecurityGroup;
|
|
446
|
+
|
|
447
|
+
expect(endpointSg).toBeDefined();
|
|
448
|
+
expect(endpointSg.Properties.SecurityGroupIngress).toEqual([
|
|
449
|
+
{
|
|
450
|
+
IpProtocol: 'tcp',
|
|
451
|
+
FromPort: 443,
|
|
452
|
+
ToPort: 443,
|
|
453
|
+
CidrIp: '172.31.0.0/16',
|
|
454
|
+
Description: 'HTTPS from default VPC range',
|
|
455
|
+
},
|
|
456
|
+
]);
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('should reference the Lambda security group when creating a new VPC', async () => {
|
|
460
|
+
const appDefinition = {
|
|
461
|
+
vpc: {
|
|
462
|
+
enable: true,
|
|
463
|
+
management: 'create-new'
|
|
464
|
+
},
|
|
465
|
+
encryption: { fieldLevelEncryptionMethod: 'kms' },
|
|
466
|
+
integrations: []
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
const result = await composeServerlessDefinition(appDefinition);
|
|
470
|
+
const endpointSg = result.resources.Resources.FriggVPCEndpointSecurityGroup;
|
|
471
|
+
|
|
472
|
+
expect(endpointSg).toBeDefined();
|
|
473
|
+
expect(endpointSg.Properties.SecurityGroupIngress).toEqual([
|
|
474
|
+
{
|
|
475
|
+
IpProtocol: 'tcp',
|
|
476
|
+
FromPort: 443,
|
|
477
|
+
ToPort: 443,
|
|
478
|
+
SourceSecurityGroupId: { Ref: 'FriggLambdaSecurityGroup' },
|
|
479
|
+
Description: 'HTTPS from Lambda security group'
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
IpProtocol: 'tcp',
|
|
483
|
+
FromPort: 443,
|
|
484
|
+
ToPort: 443,
|
|
485
|
+
CidrIp: '10.0.0.0/16',
|
|
486
|
+
Description: 'HTTPS from VPC CIDR (fallback)'
|
|
487
|
+
}
|
|
488
|
+
]);
|
|
489
|
+
});
|
|
490
|
+
|
|
347
491
|
it('should not add VPC configuration when vpc.enable is false', async () => {
|
|
348
492
|
const appDefinition = {
|
|
349
493
|
vpc: { enable: false },
|
|
@@ -1326,4 +1470,4 @@ describe('composeServerlessDefinition', () => {
|
|
|
1326
1470
|
await expect(composeServerlessDefinition(appDefinition)).rejects.toThrow('Invalid integration: missing Definition or name');
|
|
1327
1471
|
});
|
|
1328
1472
|
});
|
|
1329
|
-
});
|
|
1473
|
+
});
|
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.428.
|
|
4
|
+
"version": "2.0.0--canary.428.db65660.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.428.
|
|
13
|
-
"@friggframework/test": "2.0.0--canary.428.
|
|
12
|
+
"@friggframework/schemas": "2.0.0--canary.428.db65660.0",
|
|
13
|
+
"@friggframework/test": "2.0.0--canary.428.db65660.0",
|
|
14
14
|
"@hapi/boom": "^10.0.1",
|
|
15
15
|
"@inquirer/prompts": "^5.3.8",
|
|
16
16
|
"axios": "^1.7.2",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"serverless-http": "^2.7.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@friggframework/eslint-config": "2.0.0--canary.428.
|
|
36
|
-
"@friggframework/prettier-config": "2.0.0--canary.428.
|
|
35
|
+
"@friggframework/eslint-config": "2.0.0--canary.428.db65660.0",
|
|
36
|
+
"@friggframework/prettier-config": "2.0.0--canary.428.db65660.0",
|
|
37
37
|
"aws-sdk-client-mock": "^4.1.0",
|
|
38
38
|
"aws-sdk-client-mock-jest": "^4.1.0",
|
|
39
39
|
"jest": "^30.1.3",
|
|
@@ -68,5 +68,5 @@
|
|
|
68
68
|
"publishConfig": {
|
|
69
69
|
"access": "public"
|
|
70
70
|
},
|
|
71
|
-
"gitHead": "
|
|
71
|
+
"gitHead": "db656605e3c91ad9e4e4fc6357c74397caaeed05"
|
|
72
72
|
}
|