Skip to content

feat: Add AWS lambda support for IPV6 outbound connections in VPC #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions docs/guides/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,49 @@ The Lambda function execution role must have permissions to create, describe and
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/).
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)

**VPC Lambda Internet IPv6 Access**

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:

```yml
# serverless.yml
service: service-name
provider: aws

functions:
hello:
handler: handler.hello
vpc:
ipv6AllowedForDualStack: true
securityGroupIds:
- securityGroupId1
- securityGroupId2
subnetIds:
- subnetId1
- subnetId2
```

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:

```yml
# serverless.yml
service: service-name
provider:
name: aws
vpc:
ipv6AllowedForDualStack: true
securityGroupIds:
- securityGroupId1
- securityGroupId2
subnetIds:
- subnetId1
- subnetId2

functions: ...
```

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/)

## Environment Variables

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:
Expand Down
4 changes: 3 additions & 1 deletion docs/guides/serverless.yml.md
Original file line number Diff line number Diff line change
Expand Up @@ -490,8 +490,9 @@ Configure the Lambda functions to run inside a VPC ([complete documentation](./f
```yml
provider:
# Optional VPC settings
# If you use VPC then both securityGroupIds and subnetIds are required
# If you use VPC then both securityGroupIds and subnetIds are required, ipv6AllowedForDualStack is optional
vpc:
ipv6AllowedForDualStack: true
securityGroupIds:
- securityGroupId1
- securityGroupId2
Expand Down Expand Up @@ -647,6 +648,7 @@ functions:
# If you use VPC then both subproperties (securityGroupIds and subnetIds) are required
# Can be set to '~' to disable the use of a VPC
vpc:
ipv6AllowedForDualStack: true
securityGroupIds:
- securityGroupId1
- securityGroupId2
Expand Down
13 changes: 12 additions & 1 deletion lib/plugins/aws/deploy-function.js
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,10 @@ class AwsDeployFunction {
const vpc = functionObj.vpc || providerObj.vpc;
params.VpcConfig = {};

if (vpc.ipv6AllowedForDualStack) {
params.VpcConfig.Ipv6AllowedForDualStack = vpc.ipv6AllowedForDualStack;
}

if (Array.isArray(vpc.securityGroupIds) && !vpc.securityGroupIds.some(_.isObject)) {
params.VpcConfig.SecurityGroupIds = vpc.securityGroupIds;
}
Expand All @@ -387,8 +391,14 @@ class AwsDeployFunction {
}

const didVpcChange = () => {
const remoteConfigToCompare = { SecurityGroupIds: [], SubnetIds: [] };
const remoteConfigToCompare = {
Ipv6AllowedForDualStack: false,
SecurityGroupIds: [],
SubnetIds: [],
};
if (remoteFunctionConfiguration.VpcConfig) {
remoteConfigToCompare.Ipv6AllowedForDualStack =
remoteFunctionConfiguration.VpcConfig.Ipv6AllowedForDualStack || false;
remoteConfigToCompare.SecurityGroupIds = new Set(
remoteFunctionConfiguration.VpcConfig.SecurityGroupIds || []
);
Expand All @@ -397,6 +407,7 @@ class AwsDeployFunction {
);
}
const localConfigToCompare = {
Ipv6AllowedForDualStack: params.VpcConfig.Ipv6AllowedForDualStack || false,
SecurityGroupIds: new Set(params.VpcConfig.SecurityGroupIds || []),
SubnetIds: new Set(params.VpcConfig.SubnetIds || []),
};
Expand Down
3 changes: 3 additions & 0 deletions lib/plugins/aws/package/compile/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,9 @@ class AwsCompileFunctions {
if (!this.serverless.service.provider.vpc) this.serverless.service.provider.vpc = {};

functionResource.Properties.VpcConfig = {
Ipv6AllowedForDualStack:
functionObject.vpc.ipv6AllowedForDualStack ||
this.serverless.service.provider.vpc.ipv6AllowedForDualStack,
SecurityGroupIds:
functionObject.vpc.securityGroupIds ||
this.serverless.service.provider.vpc.securityGroupIds,
Expand Down
1 change: 1 addition & 0 deletions lib/plugins/aws/provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,7 @@ class AwsProvider {
awsLambdaVpcConfig: {
type: 'object',
properties: {
ipv6AllowedForDualStack: { type: 'boolean' },
securityGroupIds: {
anyOf: [
{
Expand Down
147 changes: 147 additions & 0 deletions test/unit/lib/plugins/aws/deploy-function.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,153 @@ describe('test/unit/lib/plugins/aws/deployFunction.test.js', () => {
});
});

it('should update function configuration if ipv6AllowedForDualStack is true', async () => {
await runServerless({
fixture: 'function',
command: 'deploy function',
options: { function: 'basic' },
awsRequestStubMap: {
...awsRequestStubMap,
Lambda: {
...awsRequestStubMap.Lambda,
getFunction: {
Configuration: {
LastModified: '2020-05-20T15:34:16.494+0000',
PackageType: 'Zip',
State: 'Active',
LastUpdateStatus: 'Successful',
},
},
},
},
configExt: {
provider: {
environment: {
ANOTHERVAR: 'anothervalue',
},
},
functions: {
basic: {
kmsKeyArn,
description,
handler,
environment: {
VARIABLE: 'value',
},
name: functionName,
memorySize,
onError: onErrorHandler,
role,
timeout,
vpc: {
ipv6AllowedForDualStack: true,
securityGroupIds: ['sg-111', 'sg-222'],
subnetIds: ['subnet-111', 'subnet-222'],
},
layers: [layerArn, secondLayerArn],
},
},
},
});
expect(updateFunctionConfigurationStub).to.be.calledWithExactly({
FunctionName: functionName,
KMSKeyArn: kmsKeyArn,
Description: description,
Handler: handler,
Environment: {
Variables: {
ANOTHERVAR: 'anothervalue',
VARIABLE: 'value',
},
},
MemorySize: memorySize,
Timeout: timeout,
DeadLetterConfig: {
TargetArn: onErrorHandler,
},
Role: role,
VpcConfig: {
Ipv6AllowedForDualStack: true,
SecurityGroupIds: ['sg-111', 'sg-222'],
SubnetIds: ['subnet-111', 'subnet-222'],
},
Layers: [layerArn, secondLayerArn],
});
});

it('should update function configuration if ipv6AllowedForDualStack is false', async () => {
await runServerless({
fixture: 'function',
command: 'deploy function',
options: { function: 'basic' },
awsRequestStubMap: {
...awsRequestStubMap,
Lambda: {
...awsRequestStubMap.Lambda,
getFunction: {
Configuration: {
LastModified: '2020-05-20T15:34:16.494+0000',
PackageType: 'Zip',
State: 'Active',
LastUpdateStatus: 'Successful',
},
},
},
},
configExt: {
provider: {
environment: {
ANOTHERVAR: 'anothervalue',
},
},
functions: {
basic: {
kmsKeyArn,
description,
handler,
environment: {
VARIABLE: 'value',
},
name: functionName,
memorySize,
onError: onErrorHandler,
role,
timeout,
vpc: {
ipv6AllowedForDualStack: false,
securityGroupIds: ['sg-111', 'sg-222'],
subnetIds: ['subnet-111', 'subnet-222'],
},
layers: [layerArn, secondLayerArn],
},
},
},
});
expect(updateFunctionConfigurationStub).to.be.calledWithExactly({
FunctionName: functionName,
KMSKeyArn: kmsKeyArn,
Description: description,
Handler: handler,
Environment: {
Variables: {
ANOTHERVAR: 'anothervalue',
VARIABLE: 'value',
},
},
MemorySize: memorySize,
Timeout: timeout,
DeadLetterConfig: {
TargetArn: onErrorHandler,
},
Role: role,
VpcConfig: {
SecurityGroupIds: ['sg-111', 'sg-222'],
SubnetIds: ['subnet-111', 'subnet-222'],
},
Layers: [layerArn, secondLayerArn],
});
});

it('should update function configuration with provider-level properties', async () => {
await runServerless({
fixture: 'function',
Expand Down
Loading