Skip to content

Commit 580e1d6

Browse files
authored
Merge pull request #21 from Martijn02/main
feat: Add AWS lambda support for IPV6 outbound connections in VPC
2 parents 4e6dbe5 + 1c6ef7f commit 580e1d6

File tree

6 files changed

+209
-2
lines changed

6 files changed

+209
-2
lines changed

docs/guides/functions.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,49 @@ The Lambda function execution role must have permissions to create, describe and
529529
By default, when a Lambda function is executed inside a VPC, it loses internet access and some resources inside AWS may become unavailable. In order for S3 resources and DynamoDB resources to be available for your Lambda function running inside the VPC, a VPC end point needs to be created. For more information please check [VPC Endpoint for Amazon S3](https://aws.amazon.com/blogs/aws/new-vpc-endpoint-for-amazon-s3/).
530530
In order for other services such as Kinesis streams to be made available, a NAT Gateway needs to be configured inside the subnets that are being used to run the Lambda, for the VPC used to execute the Lambda. For more information, please check [Enable Outgoing Internet Access within VPC](https://medium.com/@philippholly/aws-lambda-enable-outgoing-internet-access-within-vpc-8dd250e11e12)
531531

532+
**VPC Lambda Internet IPv6 Access**
533+
534+
Alternatively to setting up a NAT Gateway, you can also use an [egress-only internet gateway](https://docs.aws.amazon.com/vpc/latest/userguide/egress-only-internet-gateway.html) and allow your functions in a VPC to access the internet or other AWS services via IPv6. This eliminates the need for a NAT Gateway, reducing costs and simplifying architecture. In this case, VPC-configured Lambda functions can be allowed to access the internet using egress-only internet gateway by adding a `ipv6AllowedForDualStack` option to either the functions VPC specification:
535+
536+
```yml
537+
# serverless.yml
538+
service: service-name
539+
provider: aws
540+
541+
functions:
542+
hello:
543+
handler: handler.hello
544+
vpc:
545+
ipv6AllowedForDualStack: true
546+
securityGroupIds:
547+
- securityGroupId1
548+
- securityGroupId2
549+
subnetIds:
550+
- subnetId1
551+
- subnetId2
552+
```
553+
554+
Or if you want to apply VPC configuration to all functions in your service, you can add the configuration to the higher level `provider` object, and overwrite these service level config at the function level. For example:
555+
556+
```yml
557+
# serverless.yml
558+
service: service-name
559+
provider:
560+
name: aws
561+
vpc:
562+
ipv6AllowedForDualStack: true
563+
securityGroupIds:
564+
- securityGroupId1
565+
- securityGroupId2
566+
subnetIds:
567+
- subnetId1
568+
- subnetId2
569+
570+
functions: ...
571+
```
572+
573+
For more information, please check [Announcing AWS Lambda’s support for Internet Protocol Version 6 (IPv6) for outbound connections in VPC](https://aws.amazon.com/about-aws/whats-new/2023/10/aws-lambda-ipv6-outbound-connections-vpc/)
574+
532575
## Environment Variables
533576

534577
You can add environment variable configuration to a specific function in `serverless.yml` by adding an `environment` object property in the function configuration. This object should contain a key-value pairs of string to string:

docs/guides/serverless.yml.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,8 +490,9 @@ Configure the Lambda functions to run inside a VPC ([complete documentation](./f
490490
```yml
491491
provider:
492492
# Optional VPC settings
493-
# If you use VPC then both securityGroupIds and subnetIds are required
493+
# If you use VPC then both securityGroupIds and subnetIds are required, ipv6AllowedForDualStack is optional
494494
vpc:
495+
ipv6AllowedForDualStack: true
495496
securityGroupIds:
496497
- securityGroupId1
497498
- securityGroupId2
@@ -647,6 +648,7 @@ functions:
647648
# If you use VPC then both subproperties (securityGroupIds and subnetIds) are required
648649
# Can be set to '~' to disable the use of a VPC
649650
vpc:
651+
ipv6AllowedForDualStack: true
650652
securityGroupIds:
651653
- securityGroupId1
652654
- securityGroupId2

lib/plugins/aws/deploy-function.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,10 @@ class AwsDeployFunction {
378378
const vpc = functionObj.vpc || providerObj.vpc;
379379
params.VpcConfig = {};
380380

381+
if (vpc.ipv6AllowedForDualStack) {
382+
params.VpcConfig.Ipv6AllowedForDualStack = vpc.ipv6AllowedForDualStack;
383+
}
384+
381385
if (Array.isArray(vpc.securityGroupIds) && !vpc.securityGroupIds.some(_.isObject)) {
382386
params.VpcConfig.SecurityGroupIds = vpc.securityGroupIds;
383387
}
@@ -387,8 +391,14 @@ class AwsDeployFunction {
387391
}
388392

389393
const didVpcChange = () => {
390-
const remoteConfigToCompare = { SecurityGroupIds: [], SubnetIds: [] };
394+
const remoteConfigToCompare = {
395+
Ipv6AllowedForDualStack: false,
396+
SecurityGroupIds: [],
397+
SubnetIds: [],
398+
};
391399
if (remoteFunctionConfiguration.VpcConfig) {
400+
remoteConfigToCompare.Ipv6AllowedForDualStack =
401+
remoteFunctionConfiguration.VpcConfig.Ipv6AllowedForDualStack || false;
392402
remoteConfigToCompare.SecurityGroupIds = new Set(
393403
remoteFunctionConfiguration.VpcConfig.SecurityGroupIds || []
394404
);
@@ -397,6 +407,7 @@ class AwsDeployFunction {
397407
);
398408
}
399409
const localConfigToCompare = {
410+
Ipv6AllowedForDualStack: params.VpcConfig.Ipv6AllowedForDualStack || false,
400411
SecurityGroupIds: new Set(params.VpcConfig.SecurityGroupIds || []),
401412
SubnetIds: new Set(params.VpcConfig.SubnetIds || []),
402413
};

lib/plugins/aws/package/compile/functions.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,9 @@ class AwsCompileFunctions {
389389
if (!this.serverless.service.provider.vpc) this.serverless.service.provider.vpc = {};
390390

391391
functionResource.Properties.VpcConfig = {
392+
Ipv6AllowedForDualStack:
393+
functionObject.vpc.ipv6AllowedForDualStack ||
394+
this.serverless.service.provider.vpc.ipv6AllowedForDualStack,
392395
SecurityGroupIds:
393396
functionObject.vpc.securityGroupIds ||
394397
this.serverless.service.provider.vpc.securityGroupIds,

lib/plugins/aws/provider.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,7 @@ class AwsProvider {
661661
awsLambdaVpcConfig: {
662662
type: 'object',
663663
properties: {
664+
ipv6AllowedForDualStack: { type: 'boolean' },
664665
securityGroupIds: {
665666
anyOf: [
666667
{

test/unit/lib/plugins/aws/deploy-function.test.js

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,153 @@ describe('test/unit/lib/plugins/aws/deployFunction.test.js', () => {
948948
});
949949
});
950950

951+
it('should update function configuration if ipv6AllowedForDualStack is true', async () => {
952+
await runServerless({
953+
fixture: 'function',
954+
command: 'deploy function',
955+
options: { function: 'basic' },
956+
awsRequestStubMap: {
957+
...awsRequestStubMap,
958+
Lambda: {
959+
...awsRequestStubMap.Lambda,
960+
getFunction: {
961+
Configuration: {
962+
LastModified: '2020-05-20T15:34:16.494+0000',
963+
PackageType: 'Zip',
964+
State: 'Active',
965+
LastUpdateStatus: 'Successful',
966+
},
967+
},
968+
},
969+
},
970+
configExt: {
971+
provider: {
972+
environment: {
973+
ANOTHERVAR: 'anothervalue',
974+
},
975+
},
976+
functions: {
977+
basic: {
978+
kmsKeyArn,
979+
description,
980+
handler,
981+
environment: {
982+
VARIABLE: 'value',
983+
},
984+
name: functionName,
985+
memorySize,
986+
onError: onErrorHandler,
987+
role,
988+
timeout,
989+
vpc: {
990+
ipv6AllowedForDualStack: true,
991+
securityGroupIds: ['sg-111', 'sg-222'],
992+
subnetIds: ['subnet-111', 'subnet-222'],
993+
},
994+
layers: [layerArn, secondLayerArn],
995+
},
996+
},
997+
},
998+
});
999+
expect(updateFunctionConfigurationStub).to.be.calledWithExactly({
1000+
FunctionName: functionName,
1001+
KMSKeyArn: kmsKeyArn,
1002+
Description: description,
1003+
Handler: handler,
1004+
Environment: {
1005+
Variables: {
1006+
ANOTHERVAR: 'anothervalue',
1007+
VARIABLE: 'value',
1008+
},
1009+
},
1010+
MemorySize: memorySize,
1011+
Timeout: timeout,
1012+
DeadLetterConfig: {
1013+
TargetArn: onErrorHandler,
1014+
},
1015+
Role: role,
1016+
VpcConfig: {
1017+
Ipv6AllowedForDualStack: true,
1018+
SecurityGroupIds: ['sg-111', 'sg-222'],
1019+
SubnetIds: ['subnet-111', 'subnet-222'],
1020+
},
1021+
Layers: [layerArn, secondLayerArn],
1022+
});
1023+
});
1024+
1025+
it('should update function configuration if ipv6AllowedForDualStack is false', async () => {
1026+
await runServerless({
1027+
fixture: 'function',
1028+
command: 'deploy function',
1029+
options: { function: 'basic' },
1030+
awsRequestStubMap: {
1031+
...awsRequestStubMap,
1032+
Lambda: {
1033+
...awsRequestStubMap.Lambda,
1034+
getFunction: {
1035+
Configuration: {
1036+
LastModified: '2020-05-20T15:34:16.494+0000',
1037+
PackageType: 'Zip',
1038+
State: 'Active',
1039+
LastUpdateStatus: 'Successful',
1040+
},
1041+
},
1042+
},
1043+
},
1044+
configExt: {
1045+
provider: {
1046+
environment: {
1047+
ANOTHERVAR: 'anothervalue',
1048+
},
1049+
},
1050+
functions: {
1051+
basic: {
1052+
kmsKeyArn,
1053+
description,
1054+
handler,
1055+
environment: {
1056+
VARIABLE: 'value',
1057+
},
1058+
name: functionName,
1059+
memorySize,
1060+
onError: onErrorHandler,
1061+
role,
1062+
timeout,
1063+
vpc: {
1064+
ipv6AllowedForDualStack: false,
1065+
securityGroupIds: ['sg-111', 'sg-222'],
1066+
subnetIds: ['subnet-111', 'subnet-222'],
1067+
},
1068+
layers: [layerArn, secondLayerArn],
1069+
},
1070+
},
1071+
},
1072+
});
1073+
expect(updateFunctionConfigurationStub).to.be.calledWithExactly({
1074+
FunctionName: functionName,
1075+
KMSKeyArn: kmsKeyArn,
1076+
Description: description,
1077+
Handler: handler,
1078+
Environment: {
1079+
Variables: {
1080+
ANOTHERVAR: 'anothervalue',
1081+
VARIABLE: 'value',
1082+
},
1083+
},
1084+
MemorySize: memorySize,
1085+
Timeout: timeout,
1086+
DeadLetterConfig: {
1087+
TargetArn: onErrorHandler,
1088+
},
1089+
Role: role,
1090+
VpcConfig: {
1091+
SecurityGroupIds: ['sg-111', 'sg-222'],
1092+
SubnetIds: ['subnet-111', 'subnet-222'],
1093+
},
1094+
Layers: [layerArn, secondLayerArn],
1095+
});
1096+
});
1097+
9511098
it('should update function configuration with provider-level properties', async () => {
9521099
await runServerless({
9531100
fixture: 'function',

0 commit comments

Comments
 (0)