├── nat-gateway ├── .gitignore ├── handler.js ├── package.json ├── package-lock.json ├── README.md └── serverless.yml ├── vpc-endpoint ├── .gitignore ├── handler.js ├── package.json ├── serverless.yml ├── README.md └── package-lock.json └── README.md /nat-gateway/.gitignore: -------------------------------------------------------------------------------- 1 | # package directories 2 | node_modules 3 | jspm_packages 4 | 5 | # Serverless directories 6 | .serverless -------------------------------------------------------------------------------- /vpc-endpoint/.gitignore: -------------------------------------------------------------------------------- 1 | # package directories 2 | node_modules 3 | jspm_packages 4 | 5 | # Serverless directories 6 | .serverless -------------------------------------------------------------------------------- /nat-gateway/handler.js: -------------------------------------------------------------------------------- 1 | const AWS = require('aws-sdk') 2 | const SNS = new AWS.SNS() 3 | 4 | module.exports.main = async () => { 5 | const params = { 6 | Message: 'Hello, from your Lambda in a VPC using a NAT Gateway!', 7 | PhoneNumber: process.env.SMS_NUMBER 8 | } 9 | await SNS.publish(params).promise() 10 | }; 11 | -------------------------------------------------------------------------------- /vpc-endpoint/handler.js: -------------------------------------------------------------------------------- 1 | const AWS = require('aws-sdk') 2 | const SNS = new AWS.SNS() 3 | 4 | module.exports.main = async () => { 5 | const params = { 6 | Message: 'Hello, from your Lambda in a VPC using a VPC endpoint!', 7 | PhoneNumber: process.env.SMS_NUMBER 8 | } 9 | await SNS.publish(params).promise() 10 | }; 11 | -------------------------------------------------------------------------------- /nat-gateway/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nat-gateway", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "handler.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "aws-sdk": "^2.606.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /vpc-endpoint/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nat-gateway", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "handler.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "aws-sdk": "^2.606.0" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Serverless VPC Examples 2 | 3 | This repository contains example configuration for using a Lambda function inside a VPC. It also shows two different methods for using AWS services from a Lambda function in a VPC. 4 | 5 | The two methods are: 6 | 7 | - [**Using a NAT Gateway**](./nat-gateway/README.md): Adding a [NAT Gateway](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html) provides full public internet access to your Lambda function. A NAT Gateway costs ~$35 per month per instance plus data processing charges. 8 | - [**Using a VPC Endpoint**](./vpc-endpoint/README.md): A [VPC endpoint](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-endpoints.html) allows you to use a specific AWS service from within a VPC. A VPC endpoint costs ~$7.50 per month plus data processing charges. 9 | 10 | Check out the accompanying blog post on [using AWS services from a Lambda inside a VPC](https://www.alexdebrie.com/posts/aws-lambda-vpc/) for a larger breakdown of the pros and cons of the two approaches. -------------------------------------------------------------------------------- /vpc-endpoint/serverless.yml: -------------------------------------------------------------------------------- 1 | service: vpc-endpoint 2 | 3 | provider: 4 | name: aws 5 | runtime: nodejs12.x 6 | stage: dev 7 | region: us-east-1 8 | iamRoleStatements: 9 | - Effect: Allow 10 | Action: 'sns:Publish' 11 | Resource: '*' 12 | environment: 13 | SMS_NUMBER: '+15555555555' 14 | vpc: 15 | securityGroupIds: 16 | - !GetAtt VpcEndpointLambdaSecurityGroup.GroupId 17 | subnetIds: 18 | - !Ref SubnetAPrivate 19 | - !Ref SubnetBPrivate 20 | 21 | functions: 22 | sendText: 23 | handler: handler.main 24 | 25 | resources: 26 | Resources: 27 | VPC: 28 | Type: 'AWS::EC2::VPC' 29 | Properties: 30 | CidrBlock: !Sub '10.0.0.0/16' 31 | EnableDnsSupport: true 32 | EnableDnsHostnames: true 33 | InstanceTenancy: default 34 | SubnetAPrivate: 35 | Type: 'AWS::EC2::Subnet' 36 | Properties: 37 | AvailabilityZone: !Select [0, !GetAZs ''] 38 | CidrBlock: !Sub '10.0.128.0/20' 39 | VpcId: !Ref VPC 40 | SubnetBPrivate: 41 | Type: 'AWS::EC2::Subnet' 42 | Properties: 43 | AvailabilityZone: !Select [1, !GetAZs ''] 44 | CidrBlock: !Sub '10.0.144.0/20' 45 | VpcId: !Ref VPC 46 | VpcEndpointSecurityGroup: 47 | Type: 'AWS::EC2::SecurityGroup' 48 | Properties: 49 | VpcId: !Ref VPC 50 | GroupDescription: 'Security group for VPC Endpoint' 51 | SecurityGroupIngress: 52 | - IpProtocol: tcp 53 | FromPort: 443 54 | ToPort: 443 55 | SourceSecurityGroupId: !GetAtt VpcEndpointLambdaSecurityGroup.GroupId 56 | SNSVPCEndpoint: 57 | Type: AWS::EC2::VPCEndpoint 58 | Properties: 59 | PrivateDnsEnabled: True 60 | SecurityGroupIds: 61 | - !GetAtt VpcEndpointSecurityGroup.GroupId 62 | ServiceName: 'com.amazonaws.us-east-1.sns' 63 | SubnetIds: 64 | - !Ref SubnetAPrivate 65 | - !Ref SubnetBPrivate 66 | VpcEndpointType: Interface 67 | VpcId: !Ref VPC 68 | VpcEndpointLambdaSecurityGroup: 69 | Type: 'AWS::EC2::SecurityGroup' 70 | Properties: 71 | VpcId: !Ref VPC 72 | GroupDescription: 'Security group for VPC Endpoint Lambda' 73 | 74 | Outputs: 75 | VPC: 76 | Description: 'VPC' 77 | Value: !Ref VPC 78 | SubnetsPrivate: 79 | Description: 'Subnets private' 80 | Value: !Join [',', [!Ref SubnetAPrivate, !Ref SubnetBPrivate]] -------------------------------------------------------------------------------- /vpc-endpoint/README.md: -------------------------------------------------------------------------------- 1 | ## Lambda in VPC -- VPC endpoint example 2 | 3 | This Serverless service shows how to configure a Lambda function in a VPC to use AWS services by using a [VPC endpoint](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-endpoints.html). It will configure all necessary VPC resources and deploy a Lambda function in the VPC. This example uses [Amazon SNS](https://aws.amazon.com/sns), but it will work with any of the [services that use support VPC endpoints](https://docs.aws.amazon.com/vpc/latest/userguide/vpce-interface.html). The VPC will demonstrate that the VPC endpoint works by using SNS to send an SMS message. 4 | 5 | ### Usage 6 | 7 | To use this example, run the following steps. You must have the [Serverless Framework](https://github.com/serverless/serverless) installed. 8 | 9 | 1. Create a new service from this repository and install the dependencies. 10 | 11 | ```bash 12 | serverless create --template-url https://github.com/alexdebrie/serverless-vpc-examples/tree/master/vpc-endpoint --path vpc-endpoint 13 | cd vpc-endpoint 14 | npm i 15 | ``` 16 | 17 | 2. Update the `SMS_NUMBER` environment variable in the `serverless.yml` file to use the SMS number where you want to send a message. 18 | 19 | 3. Deploy your service. 20 | 21 | In your terminal, run: 22 | 23 | ```bash 24 | serverless deploy 25 | ``` 26 | 27 | This will take a few minutes to provision the VPC resources. 28 | 29 | 4. Invoke your function to send the SMS message. 30 | 31 | In your terminal, run: 32 | 33 | ```bash 34 | serverless invoke -f sendText 35 | ``` 36 | 37 | You should receive an SMS message to the number provided. 38 | 39 | ### Explanation 40 | 41 | The `serverless.yml` file is creating the following resources: 42 | 43 | - A [VPC](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc.html); 44 | - Two private [subnets](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnet.html). The Lambda functions will use the private subnets. 45 | - A [Security Group](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group.html) to give to our Lambda function. 46 | - A Security Group for our VPC Endpoint. This security group will allow TCP ingress on port 443 (HTTPS) from our Lambda's security group. 47 | - A [VPC endpoint](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpcendpoint.html) in our private subnets that is routing to SNS. 48 | 49 | The general architecture is as follows: 50 | 51 | ![Lambda in VPC with VPC Endpoint](https://user-images.githubusercontent.com/6509926/72752985-46a50680-3b89-11ea-891d-5a4a2996fb56.png) 52 | 53 | Our Lambda functions are (functionally) in the private subnets of our VPC. The SNS requests are routed through the configured VPC endpoints to the SNS service. 54 | 55 | In our Lambda function configuration, we use the private subnet IDs and the security group ID to configure our Lambda function to be in a VPC. 56 | 57 | Both the VPC resources and the Lambda function are combined in this example stack for ease of demonstration. For production use cases, I would recommend splitting the VPC configuration into a different CloudFormation stack altogether. You could refer to the exported values from that stack in your Serverless service. -------------------------------------------------------------------------------- /nat-gateway/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nat-gateway", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "aws-sdk": { 8 | "version": "2.606.0", 9 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.606.0.tgz", 10 | "integrity": "sha512-PI4vabKfMdvACzefIpm4cL4Fwji+pQQlK1zPXtz9+JzdPCp5FcEhbpjJucfeMaOByrC2ThXXOdCf1rUhjSiYFg==", 11 | "requires": { 12 | "buffer": "4.9.1", 13 | "events": "1.1.1", 14 | "ieee754": "1.1.13", 15 | "jmespath": "0.15.0", 16 | "querystring": "0.2.0", 17 | "sax": "1.2.1", 18 | "url": "0.10.3", 19 | "uuid": "3.3.2", 20 | "xml2js": "0.4.19" 21 | } 22 | }, 23 | "base64-js": { 24 | "version": "1.3.1", 25 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", 26 | "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" 27 | }, 28 | "buffer": { 29 | "version": "4.9.1", 30 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", 31 | "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", 32 | "requires": { 33 | "base64-js": "^1.0.2", 34 | "ieee754": "^1.1.4", 35 | "isarray": "^1.0.0" 36 | } 37 | }, 38 | "events": { 39 | "version": "1.1.1", 40 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 41 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" 42 | }, 43 | "ieee754": { 44 | "version": "1.1.13", 45 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", 46 | "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" 47 | }, 48 | "isarray": { 49 | "version": "1.0.0", 50 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 51 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 52 | }, 53 | "jmespath": { 54 | "version": "0.15.0", 55 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 56 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" 57 | }, 58 | "punycode": { 59 | "version": "1.3.2", 60 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 61 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" 62 | }, 63 | "querystring": { 64 | "version": "0.2.0", 65 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 66 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" 67 | }, 68 | "sax": { 69 | "version": "1.2.1", 70 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 71 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" 72 | }, 73 | "url": { 74 | "version": "0.10.3", 75 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 76 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 77 | "requires": { 78 | "punycode": "1.3.2", 79 | "querystring": "0.2.0" 80 | } 81 | }, 82 | "uuid": { 83 | "version": "3.3.2", 84 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 85 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" 86 | }, 87 | "xml2js": { 88 | "version": "0.4.19", 89 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 90 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 91 | "requires": { 92 | "sax": ">=0.6.0", 93 | "xmlbuilder": "~9.0.1" 94 | } 95 | }, 96 | "xmlbuilder": { 97 | "version": "9.0.7", 98 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 99 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /vpc-endpoint/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nat-gateway", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "aws-sdk": { 8 | "version": "2.606.0", 9 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.606.0.tgz", 10 | "integrity": "sha512-PI4vabKfMdvACzefIpm4cL4Fwji+pQQlK1zPXtz9+JzdPCp5FcEhbpjJucfeMaOByrC2ThXXOdCf1rUhjSiYFg==", 11 | "requires": { 12 | "buffer": "4.9.1", 13 | "events": "1.1.1", 14 | "ieee754": "1.1.13", 15 | "jmespath": "0.15.0", 16 | "querystring": "0.2.0", 17 | "sax": "1.2.1", 18 | "url": "0.10.3", 19 | "uuid": "3.3.2", 20 | "xml2js": "0.4.19" 21 | } 22 | }, 23 | "base64-js": { 24 | "version": "1.3.1", 25 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", 26 | "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" 27 | }, 28 | "buffer": { 29 | "version": "4.9.1", 30 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", 31 | "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", 32 | "requires": { 33 | "base64-js": "^1.0.2", 34 | "ieee754": "^1.1.4", 35 | "isarray": "^1.0.0" 36 | } 37 | }, 38 | "events": { 39 | "version": "1.1.1", 40 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 41 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" 42 | }, 43 | "ieee754": { 44 | "version": "1.1.13", 45 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", 46 | "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" 47 | }, 48 | "isarray": { 49 | "version": "1.0.0", 50 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 51 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 52 | }, 53 | "jmespath": { 54 | "version": "0.15.0", 55 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 56 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" 57 | }, 58 | "punycode": { 59 | "version": "1.3.2", 60 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 61 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" 62 | }, 63 | "querystring": { 64 | "version": "0.2.0", 65 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 66 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" 67 | }, 68 | "sax": { 69 | "version": "1.2.1", 70 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 71 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" 72 | }, 73 | "url": { 74 | "version": "0.10.3", 75 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 76 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 77 | "requires": { 78 | "punycode": "1.3.2", 79 | "querystring": "0.2.0" 80 | } 81 | }, 82 | "uuid": { 83 | "version": "3.3.2", 84 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 85 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" 86 | }, 87 | "xml2js": { 88 | "version": "0.4.19", 89 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 90 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 91 | "requires": { 92 | "sax": ">=0.6.0", 93 | "xmlbuilder": "~9.0.1" 94 | } 95 | }, 96 | "xmlbuilder": { 97 | "version": "9.0.7", 98 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 99 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /nat-gateway/README.md: -------------------------------------------------------------------------------- 1 | ## Lambda in VPC -- NAT Gateway example 2 | 3 | This Serverless service shows how to configure a Lambda function in a VPC with public internet access by using a NAT Gateway. It will configure all necessary VPC resources and deploy a Lambda function in the VPC. The VPC will demonstrate that it has internet access by using [Amazon SNS](https://aws.amazon.com/sns) to send an SMS message. 4 | 5 | ### Usage 6 | 7 | To use this example, run the following steps. You must have the [Serverless Framework](https://github.com/serverless/serverless) installed. 8 | 9 | 1. Create a new service from this repository and install the dependencies. 10 | 11 | ```bash 12 | serverless create --template-url https://github.com/alexdebrie/serverless-vpc-examples/tree/master/nat-gateway --path nat-gateway 13 | cd nat-gateway 14 | npm i 15 | ``` 16 | 17 | 2. Update the `SMS_NUMBER` environment variable in the `serverless.yml` file to use the SMS number where you want to send a message. 18 | 19 | 3. Deploy your service. 20 | 21 | In your terminal, run: 22 | 23 | ```bash 24 | serverless deploy 25 | ``` 26 | 27 | This will take a few minutes to provision the VPC resources. 28 | 29 | 4. Invoke your function to send the SMS message. 30 | 31 | In your terminal, run: 32 | 33 | ```bash 34 | serverless invoke -f sendText 35 | ``` 36 | 37 | You should receive an SMS message to the number provided. 38 | 39 | ### Explanation 40 | 41 | The `serverless.yml` file is creating the following resources: 42 | 43 | - A [VPC](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc.html); 44 | - Two public [subnets](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnet.html) and two private subnets. The Lambda functions will use the private subnets, but the NAT Gateways will be in the public subnets. 45 | - An [Internet Gateway](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-internetgateway.html) and a [VPC Gateway Attachment](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc-gateway-attachment.html) to connect the Gateway to the VPC. The Internet Gateway will allow public internet access for the public subnets. 46 | - [RouteTables](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route-table.html) and [Subnet Route Table Associations](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnet-route-table-assoc.html) to associate each subnet with a route table. 47 | - [Routes](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route.html) in the public subnet route tables that direct traffic through the Internet Gateway. 48 | - Two [Elastic IP Addresses](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-eip.html) that will be assigned to our NAT Gateways. 49 | - Two [NAT Gateways](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-natgateway.html) that will be used to connect our private subnets to the public internet. 50 | - [Routes](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route.html) in the private subnet route tables to route traffic through the NAT Gateways. 51 | - A [Security Group](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group.html) to give to our Lambda function. 52 | 53 | The general architecture is as follows: 54 | 55 | ![Lambda in VPC with NAT Gateway](https://user-images.githubusercontent.com/6509926/72752984-46a50680-3b89-11ea-93ed-3702afb64242.png) 56 | 57 | Our Lambda functions are (functionally) in the private subnets of our VPC. Their web requests are routed through the NAT Gateway into the public subnet where the traffic can go through the internet gateway to the public internet. With public internet access, our Lambda function has access AWS services like SNS. 58 | 59 | In our Lambda function configuration, we use the private subnet IDs and the security group ID to configure our Lambda function to be in a VPC. 60 | 61 | This is a lot of resources and can be quite confusing if you're not a networking wizard. Both the VPC resources and the Lambda function are combined in this example stack for ease of demonstration. For production use cases, I would recommend splitting the VPC configuration into a different CloudFormation stack altogether. You could refer to the exported values from that stack in your Serverless service. -------------------------------------------------------------------------------- /nat-gateway/serverless.yml: -------------------------------------------------------------------------------- 1 | service: nat-gateway 2 | 3 | provider: 4 | name: aws 5 | runtime: nodejs12.x 6 | stage: dev 7 | region: us-east-1 8 | iamRoleStatements: 9 | - Effect: Allow 10 | Action: 'sns:Publish' 11 | Resource: '*' 12 | environment: 13 | SMS_NUMBER: '+15555555555' 14 | vpc: 15 | securityGroupIds: 16 | - !GetAtt NatGatewayLambdaSecurityGroup.GroupId 17 | subnetIds: 18 | - !Ref SubnetAPrivate 19 | - !Ref SubnetBPrivate 20 | 21 | functions: 22 | sendText: 23 | handler: handler.main 24 | 25 | resources: 26 | Resources: 27 | VPC: 28 | Type: 'AWS::EC2::VPC' 29 | Properties: 30 | CidrBlock: !Sub '10.0.0.0/16' 31 | EnableDnsSupport: true 32 | EnableDnsHostnames: true 33 | InstanceTenancy: default 34 | InternetGateway: 35 | Type: 'AWS::EC2::InternetGateway' 36 | Properties: 37 | Tags: 38 | - Key: Name 39 | Value: !Sub '10.0.0.0/16' 40 | VPCGatewayAttachment: 41 | Type: 'AWS::EC2::VPCGatewayAttachment' 42 | Properties: 43 | VpcId: !Ref VPC 44 | InternetGatewayId: !Ref InternetGateway 45 | SubnetAPublic: 46 | Type: 'AWS::EC2::Subnet' 47 | Properties: 48 | AvailabilityZone: !Select [0, !GetAZs ''] 49 | CidrBlock: !Sub '10.0.0.0/20' 50 | MapPublicIpOnLaunch: true 51 | VpcId: !Ref VPC 52 | SubnetAPrivate: 53 | Type: 'AWS::EC2::Subnet' 54 | Properties: 55 | AvailabilityZone: !Select [0, !GetAZs ''] 56 | CidrBlock: !Sub '10.0.128.0/20' 57 | VpcId: !Ref VPC 58 | SubnetBPublic: 59 | Type: 'AWS::EC2::Subnet' 60 | Properties: 61 | AvailabilityZone: !Select [1, !GetAZs ''] 62 | CidrBlock: !Sub '10.0.16.0/20' 63 | MapPublicIpOnLaunch: true 64 | VpcId: !Ref VPC 65 | SubnetBPrivate: 66 | Type: 'AWS::EC2::Subnet' 67 | Properties: 68 | AvailabilityZone: !Select [1, !GetAZs ''] 69 | CidrBlock: !Sub '10.0.144.0/20' 70 | VpcId: !Ref VPC 71 | RouteTableAPublic: 72 | Type: 'AWS::EC2::RouteTable' 73 | Properties: 74 | VpcId: !Ref VPC 75 | RouteTableAPrivate: 76 | Type: 'AWS::EC2::RouteTable' 77 | Properties: 78 | VpcId: !Ref VPC 79 | RouteTableBPublic: 80 | Type: 'AWS::EC2::RouteTable' 81 | Properties: 82 | VpcId: !Ref VPC 83 | RouteTableBPrivate: 84 | Type: 'AWS::EC2::RouteTable' 85 | Properties: 86 | VpcId: !Ref VPC 87 | RouteTableAssociationAPublic: 88 | Type: 'AWS::EC2::SubnetRouteTableAssociation' 89 | Properties: 90 | SubnetId: !Ref SubnetAPublic 91 | RouteTableId: !Ref RouteTableAPublic 92 | RouteTableAssociationAPrivate: 93 | Type: 'AWS::EC2::SubnetRouteTableAssociation' 94 | Properties: 95 | SubnetId: !Ref SubnetAPrivate 96 | RouteTableId: !Ref RouteTableAPrivate 97 | RouteTableAssociationBPublic: 98 | Type: 'AWS::EC2::SubnetRouteTableAssociation' 99 | Properties: 100 | SubnetId: !Ref SubnetBPublic 101 | RouteTableId: !Ref RouteTableBPublic 102 | RouteTableAssociationBPrivate: 103 | Type: 'AWS::EC2::SubnetRouteTableAssociation' 104 | Properties: 105 | SubnetId: !Ref SubnetBPrivate 106 | RouteTableId: !Ref RouteTableBPrivate 107 | RouteTablePublicAInternetRoute: 108 | Type: 'AWS::EC2::Route' 109 | DependsOn: VPCGatewayAttachment 110 | Properties: 111 | RouteTableId: !Ref RouteTableAPublic 112 | DestinationCidrBlock: '0.0.0.0/0' 113 | GatewayId: !Ref InternetGateway 114 | RouteTablePublicBInternetRoute: 115 | Type: 'AWS::EC2::Route' 116 | DependsOn: VPCGatewayAttachment 117 | Properties: 118 | RouteTableId: !Ref RouteTableBPublic 119 | DestinationCidrBlock: '0.0.0.0/0' 120 | GatewayId: !Ref InternetGateway 121 | EIPA: 122 | Type: 'AWS::EC2::EIP' 123 | Properties: 124 | Domain: vpc 125 | EIPB: 126 | Type: 'AWS::EC2::EIP' 127 | Properties: 128 | Domain: vpc 129 | NatGatewayA: 130 | Type: 'AWS::EC2::NatGateway' 131 | Properties: 132 | AllocationId: !GetAtt 'EIPA.AllocationId' 133 | SubnetId: !Ref SubnetAPublic 134 | RouteA: 135 | Type: 'AWS::EC2::Route' 136 | Properties: 137 | RouteTableId: !Ref RouteTableAPrivate 138 | DestinationCidrBlock: '0.0.0.0/0' 139 | NatGatewayId: !Ref NatGatewayA 140 | NatGatewayB: 141 | Type: 'AWS::EC2::NatGateway' 142 | Properties: 143 | AllocationId: !GetAtt 'EIPB.AllocationId' 144 | SubnetId: !Ref SubnetBPublic 145 | RouteB: 146 | Type: 'AWS::EC2::Route' 147 | Properties: 148 | RouteTableId: !Ref RouteTableBPrivate 149 | DestinationCidrBlock: '0.0.0.0/0' 150 | NatGatewayId: !Ref NatGatewayB 151 | NatGatewayLambdaSecurityGroup: 152 | Type: 'AWS::EC2::SecurityGroup' 153 | Properties: 154 | VpcId: !Ref VPC 155 | GroupDescription: 'Security group for NAT Gateway Lambda' 156 | 157 | Outputs: 158 | VPC: 159 | Description: 'VPC' 160 | Value: !Ref VPC 161 | SubnetsPublic: 162 | Description: 'Subnets public' 163 | Value: !Join [',', [!Ref SubnetAPublic, !Ref SubnetBPublic]] 164 | SubnetsPrivate: 165 | Description: 'Subnets private' 166 | Value: !Join [',', [!Ref SubnetAPrivate, !Ref SubnetBPrivate]] 167 | --------------------------------------------------------------------------------