This is a series of articles about setting up a complex Serverless backend infrastructure with AWS SAM and CloudFormation.
Here is the index of all the articles in case you want to jump to any of them:
1. Setup of AppSync and API Gateway with Multiple AWS Cognito User Pools
2. Configuring S3 Buckets with Permissions and Access Roles in AWS Cognito AuthRole
3. Intro to DynamoDB Resolvers for AppSync Implementation
4. Intro to Lambda Resolvers for AppSync Implementation
- Configuring an AWS VPC to Include Lambda Resolvers with a Fixed IP
6. Intro to Pipeline Resolvers for AppSync Implementation
7. Handling Lambda Resolver Timeouts with SNS Messages
Your Lambda needs to call a third-party API that whitelists IPs. But Lambda doesn't have a fixed IP — every invocation could come from a different address. The whitelist rejects you, and you're stuck.
The fix is a VPC with a NAT Gateway. Route your Lambda's traffic through a single Elastic IP, and every outbound request looks the same to the outside world.
The Setup
You need five things: a VPC, public subnets with an Internet Gateway, a private subnet, a NAT Gateway with an Elastic IP, and the Lambda sitting in the private subnet. The private subnet routes through the NAT, which gives you a static IP. The public subnets handle Lambdas that don't need internet access.
Resources:
# VPC
MyVPC:
Type: "AWS::EC2::VPC"
Properties:
CidrBlock: "10.0.0.0/16"
EnableDnsSupport: true
EnableDnsHostnames: true
# Subnets
PublicSubnetA:
Type: "AWS::EC2::Subnet"
Properties:
VpcId: !Ref MyVPC
CidrBlock: "10.0.1.0/24"
MapPublicIpOnLaunch: true
PublicSubnetB:
Type: "AWS::EC2::Subnet"
Properties:
VpcId: !Ref MyVPC
CidrBlock: "10.0.2.0/24"
MapPublicIpOnLaunch: true
# Internet Gateway for public internet access
InternetGateway:
Type: "AWS::EC2::InternetGateway"
GatewayAttachment:
Type: "AWS::EC2::VPCGatewayAttachment"
Properties:
VpcId: !Ref MyVPC
InternetGatewayId: !Ref InternetGateway
# Route Table for public subnets
PublicRouteTable:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref MyVPC
PublicRoute:
Type: "AWS::EC2::Route"
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: "0.0.0.0/0"
GatewayId: !Ref InternetGateway
SubnetRouteTableAssociationA:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PublicSubnetA
RouteTableId: !Ref PublicRouteTable
SubnetRouteTableAssociationB:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PublicSubnetB
RouteTableId: !Ref PublicRouteTable
# Elastic IP for NAT Gateway
NatEIP:
Type: "AWS::EC2::EIP"
# NAT Gateway
NatGateway:
Type: "AWS::EC2::NatGateway"
Properties:
AllocationId: !GetAtt NatEIP.AllocationId
SubnetId: !Ref PublicSubnetA
# Private Subnet for Lambda
PrivateSubnet:
Type: "AWS::EC2::Subnet"
Properties:
VpcId: !Ref MyVPC
CidrBlock: "10.0.3.0/24"
# Route Table for private subnet
PrivateRouteTable:
Type: "AWS::EC2::RouteTable"
Properties:
VpcId: !Ref MyVPC
PrivateRoute:
Type: "AWS::EC2::Route"
Properties:
RouteTableId: !Ref PrivateRouteTable
DestinationCidrBlock: "0.0.0.0/0"
NatGatewayId: !Ref NatGateway
PrivateSubnetRouteTableAssociation:
Type: "AWS::EC2::SubnetRouteTableAssociation"
Properties:
SubnetId: !Ref PrivateSubnet
RouteTableId: !Ref PrivateRouteTable
# Security Group to allow Lambda to communicate within the VPC
LambdaSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: "Enable Lambda to communicate within the VPC"
VpcId: !Ref MyVPC
# Lambda Function inside VPC without Internet Access
MyInternalLambdaFunction:
Type: "AWS::Lambda::Function"
Properties:
Handler: "index.handler"
Role: !GetAtt LambdaExecutionRole.Arn
FunctionName: "MyVPCBasedLambda"
Code:
S3Bucket: "myBucket"
S3Key: "code/myLambda.zip"
Runtime: "nodejs14.x"
VpcConfig:
SecurityGroupIds:
- !GetAtt LambdaSecurityGroup.GroupId
SubnetIds:
- !Ref PublicSubnetA
- !Ref PublicSubnetB
# Lambda Function inside VPC with Internet Access
MyInternalLambdaFunction:
Type: "AWS::Lambda::Function"
Properties:
Handler: "index.handler"
Role: !GetAtt LambdaExecutionRole.Arn
FunctionName: "MyVPCBasedLambda"
Code:
S3Bucket: "myBucket"
S3Key: "code/myLambda.zip"
Runtime: "nodejs14.x"
VpcConfig:
SecurityGroupIds:
- !GetAtt LambdaSecurityGroup.GroupId
SubnetIds:
- !Ref PrivateSubnet
# IAM Role for Lambda Execution
LambdaExecutionRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Principal:
Service: "lambda.amazonaws.com"
Action: "sts:AssumeRole"
Policies:
- PolicyName: "LambdaExecutionPolicy"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- "logs:CreateLogGroup"
- "logs:CreateLogStream"
- "logs:PutLogEvents"
Resource: "arn:aws:logs:*:*:*"The key distinction: Lambdas in the public subnets can communicate within the VPC but have no internet access. Lambdas in the private subnet route through the NAT Gateway — they get internet access with a fixed IP.
Code examples are simplified to illustrate the approach. Some adjustments may be needed for production.
Next UP: Part 6. Intro to Pipeline Resolvers for AppSync Implementation
