@friggframework/devtools 2.0.0--canary.461.9790ea9.0 → 2.0.0--canary.461.aa02ace.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.
|
@@ -625,13 +625,13 @@ describe('AuroraBuilder', () => {
|
|
|
625
625
|
|
|
626
626
|
expect(result.resources.FriggAuroraCluster).toBeDefined();
|
|
627
627
|
expect(result.resources.FriggAuroraCluster.Type).toBe('AWS::RDS::DBCluster');
|
|
628
|
-
|
|
628
|
+
|
|
629
629
|
// PubliclyAccessible is NOT supported on Aurora clusters (only on instances)
|
|
630
630
|
expect(result.resources.FriggAuroraCluster.Properties.PubliclyAccessible).toBeUndefined();
|
|
631
|
-
|
|
631
|
+
|
|
632
632
|
// Port should be explicitly set to PostgreSQL standard (5432)
|
|
633
633
|
expect(result.resources.FriggAuroraCluster.Properties.Port).toBe(5432);
|
|
634
|
-
|
|
634
|
+
|
|
635
635
|
// Should create self-referencing security group ingress rule
|
|
636
636
|
expect(result.resources.FriggAuroraIngressRule).toBeDefined();
|
|
637
637
|
expect(result.resources.FriggAuroraIngressRule.Type).toBe('AWS::EC2::SecurityGroupIngress');
|
|
@@ -141,18 +141,32 @@ class VpcBuilder extends InfrastructureBuilder {
|
|
|
141
141
|
// Build VPC Endpoints if enabled
|
|
142
142
|
const vpcManagement = appDefinition.vpc.management || 'discover';
|
|
143
143
|
const selfHeal = appDefinition.vpc.selfHeal !== false;
|
|
144
|
-
|
|
144
|
+
// Check which VPC endpoints already exist
|
|
145
|
+
const existingEndpoints = {
|
|
146
|
+
s3: discoveredResources.s3VpcEndpointId,
|
|
147
|
+
dynamodb: discoveredResources.dynamodbVpcEndpointId,
|
|
148
|
+
kms: discoveredResources.kmsVpcEndpointId,
|
|
149
|
+
secretsManager: discoveredResources.secretsManagerVpcEndpointId,
|
|
150
|
+
};
|
|
151
|
+
const allEndpointsExist = existingEndpoints.s3 && existingEndpoints.dynamodb &&
|
|
152
|
+
existingEndpoints.kms && existingEndpoints.secretsManager;
|
|
153
|
+
const someEndpointsExist = existingEndpoints.s3 || existingEndpoints.dynamodb ||
|
|
154
|
+
existingEndpoints.kms || existingEndpoints.secretsManager;
|
|
145
155
|
|
|
146
156
|
if (appDefinition.vpc.enableVPCEndpoints !== false) {
|
|
147
157
|
if (vpcManagement === 'create-new') {
|
|
148
158
|
// Always create in create-new mode
|
|
149
|
-
this.buildVpcEndpoints(appDefinition, discoveredResources, result);
|
|
159
|
+
this.buildVpcEndpoints(appDefinition, discoveredResources, result, existingEndpoints);
|
|
150
160
|
} else if (vpcManagement === 'discover') {
|
|
151
|
-
if (
|
|
152
|
-
console.log(' VPC endpoints already exist - skipping creation');
|
|
161
|
+
if (allEndpointsExist) {
|
|
162
|
+
console.log(' All VPC endpoints already exist - skipping creation');
|
|
153
163
|
} else if (selfHeal) {
|
|
154
|
-
|
|
155
|
-
|
|
164
|
+
if (someEndpointsExist) {
|
|
165
|
+
console.log(' Some VPC endpoints found - selfHeal creating missing ones');
|
|
166
|
+
} else {
|
|
167
|
+
console.log(' No VPC endpoints found - selfHeal creating them');
|
|
168
|
+
}
|
|
169
|
+
this.buildVpcEndpoints(appDefinition, discoveredResources, result, existingEndpoints);
|
|
156
170
|
} else {
|
|
157
171
|
console.log(' VPC endpoints not found and selfHeal disabled - skipping');
|
|
158
172
|
}
|
|
@@ -662,8 +676,19 @@ class VpcBuilder extends InfrastructureBuilder {
|
|
|
662
676
|
/**
|
|
663
677
|
* Build VPC Endpoints for AWS services
|
|
664
678
|
*/
|
|
665
|
-
buildVpcEndpoints(appDefinition, discoveredResources, result) {
|
|
666
|
-
|
|
679
|
+
buildVpcEndpoints(appDefinition, discoveredResources, result, existingEndpoints = {}) {
|
|
680
|
+
const missing = [];
|
|
681
|
+
if (!existingEndpoints.s3) missing.push('S3');
|
|
682
|
+
if (!existingEndpoints.dynamodb) missing.push('DynamoDB');
|
|
683
|
+
if (!existingEndpoints.kms && appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') missing.push('KMS');
|
|
684
|
+
if (!existingEndpoints.secretsManager) missing.push('Secrets Manager');
|
|
685
|
+
|
|
686
|
+
if (missing.length > 0) {
|
|
687
|
+
console.log(` Creating missing VPC Endpoints: ${missing.join(', ')}...`);
|
|
688
|
+
} else {
|
|
689
|
+
console.log(' All required VPC Endpoints already exist - skipping creation');
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
667
692
|
|
|
668
693
|
const vpcId = result.vpcId || discoveredResources.defaultVpcId;
|
|
669
694
|
|
|
@@ -682,52 +707,58 @@ class VpcBuilder extends InfrastructureBuilder {
|
|
|
682
707
|
};
|
|
683
708
|
}
|
|
684
709
|
|
|
685
|
-
// S3 Gateway Endpoint
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
710
|
+
// S3 Gateway Endpoint (only if missing)
|
|
711
|
+
if (!existingEndpoints.s3) {
|
|
712
|
+
result.resources.FriggS3VPCEndpoint = {
|
|
713
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
714
|
+
Properties: {
|
|
715
|
+
VpcId: vpcId,
|
|
716
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.s3',
|
|
717
|
+
VpcEndpointType: 'Gateway',
|
|
718
|
+
RouteTableIds: [{ Ref: 'FriggLambdaRouteTable' }],
|
|
719
|
+
},
|
|
720
|
+
};
|
|
721
|
+
}
|
|
695
722
|
|
|
696
|
-
// DynamoDB Gateway Endpoint
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
723
|
+
// DynamoDB Gateway Endpoint (only if missing)
|
|
724
|
+
if (!existingEndpoints.dynamodb) {
|
|
725
|
+
result.resources.FriggDynamoDBVPCEndpoint = {
|
|
726
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
727
|
+
Properties: {
|
|
728
|
+
VpcId: vpcId,
|
|
729
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.dynamodb',
|
|
730
|
+
VpcEndpointType: 'Gateway',
|
|
731
|
+
RouteTableIds: [{ Ref: 'FriggLambdaRouteTable' }],
|
|
732
|
+
},
|
|
733
|
+
};
|
|
734
|
+
}
|
|
706
735
|
|
|
707
|
-
// VPC Endpoint Security Group
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
736
|
+
// VPC Endpoint Security Group (only if KMS or Secrets Manager are missing)
|
|
737
|
+
if (!existingEndpoints.kms || !existingEndpoints.secretsManager) {
|
|
738
|
+
result.resources.FriggVPCEndpointSecurityGroup = {
|
|
739
|
+
Type: 'AWS::EC2::SecurityGroup',
|
|
740
|
+
Properties: {
|
|
741
|
+
GroupDescription: 'Security group for VPC Endpoints',
|
|
742
|
+
VpcId: vpcId,
|
|
743
|
+
SecurityGroupIngress: [
|
|
744
|
+
{
|
|
745
|
+
IpProtocol: 'tcp',
|
|
746
|
+
FromPort: 443,
|
|
747
|
+
ToPort: 443,
|
|
748
|
+
SourceSecurityGroupId: { Ref: 'FriggLambdaSecurityGroup' },
|
|
749
|
+
Description: 'HTTPS from Lambda',
|
|
750
|
+
},
|
|
751
|
+
],
|
|
752
|
+
Tags: [
|
|
753
|
+
{ Key: 'Name', Value: '${self:service}-${self:provider.stage}-vpc-endpoint-sg' },
|
|
754
|
+
{ Key: 'ManagedBy', Value: 'Frigg' },
|
|
755
|
+
],
|
|
756
|
+
},
|
|
757
|
+
};
|
|
758
|
+
}
|
|
728
759
|
|
|
729
|
-
// KMS Interface Endpoint (if KMS encryption enabled)
|
|
730
|
-
if (appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') {
|
|
760
|
+
// KMS Interface Endpoint (only if missing AND KMS encryption is enabled)
|
|
761
|
+
if (!existingEndpoints.kms && appDefinition.encryption?.fieldLevelEncryptionMethod === 'kms') {
|
|
731
762
|
result.resources.FriggKMSVPCEndpoint = {
|
|
732
763
|
Type: 'AWS::EC2::VPCEndpoint',
|
|
733
764
|
Properties: {
|
|
@@ -741,20 +772,22 @@ class VpcBuilder extends InfrastructureBuilder {
|
|
|
741
772
|
};
|
|
742
773
|
}
|
|
743
774
|
|
|
744
|
-
// Secrets Manager Interface Endpoint
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
775
|
+
// Secrets Manager Interface Endpoint (only if missing)
|
|
776
|
+
if (!existingEndpoints.secretsManager) {
|
|
777
|
+
result.resources.FriggSecretsManagerVPCEndpoint = {
|
|
778
|
+
Type: 'AWS::EC2::VPCEndpoint',
|
|
779
|
+
Properties: {
|
|
780
|
+
VpcId: vpcId,
|
|
781
|
+
ServiceName: 'com.amazonaws.${self:provider.region}.secretsmanager',
|
|
782
|
+
VpcEndpointType: 'Interface',
|
|
783
|
+
SubnetIds: result.vpcConfig.subnetIds,
|
|
784
|
+
SecurityGroupIds: [{ Ref: 'FriggVPCEndpointSecurityGroup' }],
|
|
785
|
+
PrivateDnsEnabled: true,
|
|
786
|
+
},
|
|
787
|
+
};
|
|
788
|
+
}
|
|
756
789
|
|
|
757
|
-
console.log(
|
|
790
|
+
console.log(` ✅ Created ${missing.length} VPC endpoint(s): ${missing.join(', ')}`);
|
|
758
791
|
}
|
|
759
792
|
}
|
|
760
793
|
|
|
@@ -114,6 +114,12 @@ class VpcDiscovery {
|
|
|
114
114
|
const dynamodbEndpoint = rawResources.vpcEndpoints.find(
|
|
115
115
|
ep => ep.ServiceName && ep.ServiceName.includes('.dynamodb')
|
|
116
116
|
);
|
|
117
|
+
const kmsEndpoint = rawResources.vpcEndpoints.find(
|
|
118
|
+
ep => ep.ServiceName && ep.ServiceName.includes('.kms')
|
|
119
|
+
);
|
|
120
|
+
const secretsManagerEndpoint = rawResources.vpcEndpoints.find(
|
|
121
|
+
ep => ep.ServiceName && ep.ServiceName.includes('.secretsmanager')
|
|
122
|
+
);
|
|
117
123
|
|
|
118
124
|
if (s3Endpoint) {
|
|
119
125
|
result.s3VpcEndpointId = s3Endpoint.VpcEndpointId;
|
|
@@ -121,6 +127,12 @@ class VpcDiscovery {
|
|
|
121
127
|
if (dynamodbEndpoint) {
|
|
122
128
|
result.dynamodbVpcEndpointId = dynamodbEndpoint.VpcEndpointId;
|
|
123
129
|
}
|
|
130
|
+
if (kmsEndpoint) {
|
|
131
|
+
result.kmsVpcEndpointId = kmsEndpoint.VpcEndpointId;
|
|
132
|
+
}
|
|
133
|
+
if (secretsManagerEndpoint) {
|
|
134
|
+
result.secretsManagerVpcEndpointId = secretsManagerEndpoint.VpcEndpointId;
|
|
135
|
+
}
|
|
124
136
|
}
|
|
125
137
|
|
|
126
138
|
console.log(` ✓ Found VPC: ${result.defaultVpcId}`);
|
|
@@ -133,8 +145,8 @@ class VpcDiscovery {
|
|
|
133
145
|
if (result.existingNatGatewayId) {
|
|
134
146
|
console.log(` ✓ Found NAT Gateway: ${result.existingNatGatewayId}`);
|
|
135
147
|
}
|
|
136
|
-
if (result.s3VpcEndpointId || result.dynamodbVpcEndpointId) {
|
|
137
|
-
console.log(` ✓ Found VPC Endpoints: S3=${result.s3VpcEndpointId ? 'Yes' : 'No'}, DynamoDB=${result.dynamodbVpcEndpointId ? 'Yes' : 'No'}`);
|
|
148
|
+
if (result.s3VpcEndpointId || result.dynamodbVpcEndpointId || result.kmsVpcEndpointId || result.secretsManagerVpcEndpointId) {
|
|
149
|
+
console.log(` ✓ Found VPC Endpoints: S3=${result.s3VpcEndpointId ? 'Yes' : 'No'}, DynamoDB=${result.dynamodbVpcEndpointId ? 'Yes' : 'No'}, KMS=${result.kmsVpcEndpointId ? 'Yes' : 'No'}, SecretsManager=${result.secretsManagerVpcEndpointId ? 'Yes' : 'No'}`);
|
|
138
150
|
}
|
|
139
151
|
|
|
140
152
|
return result;
|
|
@@ -252,6 +252,99 @@ describe('VpcDiscovery', () => {
|
|
|
252
252
|
|
|
253
253
|
expect(mockProvider.discoverVpc).toHaveBeenCalledWith(config);
|
|
254
254
|
});
|
|
255
|
+
|
|
256
|
+
it('should discover all VPC endpoints (S3, DynamoDB, KMS, Secrets Manager)', async () => {
|
|
257
|
+
mockProvider.discoverVpc.mockResolvedValue({
|
|
258
|
+
vpcId: 'vpc-123',
|
|
259
|
+
vpcCidr: '10.0.0.0/16',
|
|
260
|
+
subnets: [],
|
|
261
|
+
securityGroups: [],
|
|
262
|
+
routeTables: [],
|
|
263
|
+
natGateways: [],
|
|
264
|
+
internetGateways: [],
|
|
265
|
+
vpcEndpoints: [
|
|
266
|
+
{
|
|
267
|
+
VpcEndpointId: 'vpce-s3-123',
|
|
268
|
+
ServiceName: 'com.amazonaws.us-east-1.s3',
|
|
269
|
+
State: 'available',
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
VpcEndpointId: 'vpce-ddb-456',
|
|
273
|
+
ServiceName: 'com.amazonaws.us-east-1.dynamodb',
|
|
274
|
+
State: 'available',
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
VpcEndpointId: 'vpce-kms-789',
|
|
278
|
+
ServiceName: 'com.amazonaws.us-east-1.kms',
|
|
279
|
+
State: 'available',
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
VpcEndpointId: 'vpce-sm-abc',
|
|
283
|
+
ServiceName: 'com.amazonaws.us-east-1.secretsmanager',
|
|
284
|
+
State: 'available',
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const result = await vpcDiscovery.discover({});
|
|
290
|
+
|
|
291
|
+
expect(result.s3VpcEndpointId).toBe('vpce-s3-123');
|
|
292
|
+
expect(result.dynamodbVpcEndpointId).toBe('vpce-ddb-456');
|
|
293
|
+
expect(result.kmsVpcEndpointId).toBe('vpce-kms-789');
|
|
294
|
+
expect(result.secretsManagerVpcEndpointId).toBe('vpce-sm-abc');
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should handle partial VPC endpoint discovery', async () => {
|
|
298
|
+
mockProvider.discoverVpc.mockResolvedValue({
|
|
299
|
+
vpcId: 'vpc-123',
|
|
300
|
+
vpcCidr: '10.0.0.0/16',
|
|
301
|
+
subnets: [],
|
|
302
|
+
securityGroups: [],
|
|
303
|
+
routeTables: [],
|
|
304
|
+
natGateways: [],
|
|
305
|
+
internetGateways: [],
|
|
306
|
+
vpcEndpoints: [
|
|
307
|
+
{
|
|
308
|
+
VpcEndpointId: 'vpce-s3-123',
|
|
309
|
+
ServiceName: 'com.amazonaws.us-east-1.s3',
|
|
310
|
+
State: 'available',
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
VpcEndpointId: 'vpce-ddb-456',
|
|
314
|
+
ServiceName: 'com.amazonaws.us-east-1.dynamodb',
|
|
315
|
+
State: 'available',
|
|
316
|
+
},
|
|
317
|
+
// KMS and Secrets Manager are missing
|
|
318
|
+
],
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
const result = await vpcDiscovery.discover({});
|
|
322
|
+
|
|
323
|
+
expect(result.s3VpcEndpointId).toBe('vpce-s3-123');
|
|
324
|
+
expect(result.dynamodbVpcEndpointId).toBe('vpce-ddb-456');
|
|
325
|
+
expect(result.kmsVpcEndpointId).toBeUndefined();
|
|
326
|
+
expect(result.secretsManagerVpcEndpointId).toBeUndefined();
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('should handle no VPC endpoints', async () => {
|
|
330
|
+
mockProvider.discoverVpc.mockResolvedValue({
|
|
331
|
+
vpcId: 'vpc-123',
|
|
332
|
+
vpcCidr: '10.0.0.0/16',
|
|
333
|
+
subnets: [],
|
|
334
|
+
securityGroups: [],
|
|
335
|
+
routeTables: [],
|
|
336
|
+
natGateways: [],
|
|
337
|
+
internetGateways: [],
|
|
338
|
+
vpcEndpoints: [],
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const result = await vpcDiscovery.discover({});
|
|
342
|
+
|
|
343
|
+
expect(result.s3VpcEndpointId).toBeUndefined();
|
|
344
|
+
expect(result.dynamodbVpcEndpointId).toBeUndefined();
|
|
345
|
+
expect(result.kmsVpcEndpointId).toBeUndefined();
|
|
346
|
+
expect(result.secretsManagerVpcEndpointId).toBeUndefined();
|
|
347
|
+
});
|
|
255
348
|
});
|
|
256
349
|
});
|
|
257
350
|
|
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.461.
|
|
4
|
+
"version": "2.0.0--canary.461.aa02ace.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-ec2": "^3.835.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.835.0",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"@babel/eslint-parser": "^7.18.9",
|
|
12
12
|
"@babel/parser": "^7.25.3",
|
|
13
13
|
"@babel/traverse": "^7.25.3",
|
|
14
|
-
"@friggframework/schemas": "2.0.0--canary.461.
|
|
15
|
-
"@friggframework/test": "2.0.0--canary.461.
|
|
14
|
+
"@friggframework/schemas": "2.0.0--canary.461.aa02ace.0",
|
|
15
|
+
"@friggframework/test": "2.0.0--canary.461.aa02ace.0",
|
|
16
16
|
"@hapi/boom": "^10.0.1",
|
|
17
17
|
"@inquirer/prompts": "^5.3.8",
|
|
18
18
|
"axios": "^1.7.2",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"serverless-http": "^2.7.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@friggframework/eslint-config": "2.0.0--canary.461.
|
|
38
|
-
"@friggframework/prettier-config": "2.0.0--canary.461.
|
|
37
|
+
"@friggframework/eslint-config": "2.0.0--canary.461.aa02ace.0",
|
|
38
|
+
"@friggframework/prettier-config": "2.0.0--canary.461.aa02ace.0",
|
|
39
39
|
"aws-sdk-client-mock": "^4.1.0",
|
|
40
40
|
"aws-sdk-client-mock-jest": "^4.1.0",
|
|
41
41
|
"jest": "^30.1.3",
|
|
@@ -70,5 +70,5 @@
|
|
|
70
70
|
"publishConfig": {
|
|
71
71
|
"access": "public"
|
|
72
72
|
},
|
|
73
|
-
"gitHead": "
|
|
73
|
+
"gitHead": "aa02ace634ba3b5a59a207891f7012b5b08d9f5e"
|
|
74
74
|
}
|