Accessing the Internet from VPC Connected Lambda Functions using a NAT Gateway

Want to learn more about AWS Lambda and .NET? Check out my A Cloud Guru course on ASP.NET Web API and Lambda.

This post is mostly a copy of an earlier one that showed to give a VPC connected Lambda function access to Secrets Manager. The solution here is the same, but describes the more general case of internet connectivity for a Lambda function connected to a VPC.

Once you connect a Lambda function to the VPC, it can no longer access the internet. But you can use a NAT Gateway to handle internet traffic for the Lambda function.

If you don’t know, the Lambda service is function as a service offering for .NET application.

Why connect a Lambda function to the VPC?

My main reason was security. I had SQL Server in my VPC that was not publicly accessible and I wanted to query it from a Lambda function. I had to connect my Lambda function to the VPC, but as soon as I did, the Lambda function lost its ability to access the internet.

Assumptions

  • You have a VPC
  • Your VPC has at least two subnets
  • You have an Internet Gateway on your VPC
  • You have a main route table with a route for 0.0.0.0/0 via the Internet Gateway
  • You have a default security group that allows all egress traffic, and all internal traffic within that security group

Concepts

I am going to simplify some things, but there will be enough to get the technique across.

Subnets

You are going to use two of your subnets, one will be designated as “public”, and the other as “private”.

A public subnet is a subnet that can access the internet via an Internet Gateway. In AWS, this means that the subnet uses a route table that routes internet traffic to the Internet Gateway.

A private subnet is a subnet that can only access the internet via a NAT Gateway. In AWS, this means that the subnet uses a route table that routes internet traffic to a NAT Gateway.

Lambda functions

Lambda functions run in their own VPC, not your VPC. By default, they do not have access to your VPC but they do have access to the internet.

When you connect your Lambda function to the VPC, it will have access to resources in the VPC, but no longer has internet access.

In this example you are going to connect a Lambda function to your VPC, you will choose the private subnet and the default security group. Once you have done this, the Lambda function has connectivity to your VPC. It cannot use the Internet Gateway to access the internet. But it can use a NAT Gateway to access the internet.

NAT Gateway

Your NAT Gateway will be added to the public subnet.

A route table will be created to direct all internet traffic from the private subnet to the NAT Gateway (on the public subnet). From there, the NAT Gateway will send internet traffic to the Internet Gateway.

Your Lambda will now have access to your VPC, and the internet via the NAT Gateway.

Step by step

There are a few steps to get this technique working. But if you follow along it won’t be too difficult.

1. Create a Lambda function that accesses an API on the internet

Follow this post to create a simple net lambda function. Name the function OpenBreweryDBQuery, and use OpenBreweryDBQueryRole for the role name.

Change the FunctionHandler method to the following -

public async Task<Stream> FunctionHandler(ILambdaContext context)
{
    HttpClient client = new HttpClient();
    HttpResponseMessage response =  await client.GetAsync("https://api.openbrewerydb.org/breweries?by_city=boston&per_page=1");
    return await response.Content.ReadAsStreamAsync();
}

Follow the instruction for deploying the function.

Then invoke with -

dotnet lambda invoke-function OpenBreweryDBQuery

You will get a response like -

Payload:
[{"id":"backlash-beer-co-boston","name":"Backlash Beer Co","brewery_type":"contract","street":null,"address_2":null,"address_3":null,"city":"Boston","state":"Massachusetts","county_province":null,"postal_code":"2215","country":"United States","longitude":null,"latitude":null,"phone":null,"website_url":"http://backlashbeer.com/","updated_at":"2021-10-23T02:24:55.243Z","created_at":"2021-10-23T02:24:55.243Z"}]

Now you have a function that can access the internet. When you connect the function to the VPC, it will lose internet access.

2. Get your VPC id and default security group id

Run the following command -

aws ec2 describe-security-groups --query 'SecurityGroups[?GroupName==`default`].[GroupId,VpcId]' --output text

You will see output like -

sg-11111    vpc-22222

sg-11111 is your default security group id, and vpc-22222 is your VPC id.

3. Get the list of subnets in the VPC

Now you need to get a list of the subnets in the VPC, but you are going to use only two.

Run -

aws ec2 describe-subnets --query 'Subnets[?VpcId==`vpc-22222`].SubnetId ' --output text

Your output should look like -

subnet-33333        subnet-44444        subnet-xxxxxxxxx        subnet-xxxxxx        subnet-xxxxxxxxxx        subnet-xxxxxxxx

My VPC has six subnets, but I’m interested in only two of them.

I am going to use subnet-33333 as the public subnet, and subnet-44444 as the private subnet.

4. Attach the Lambda function to VPC

If you followed step 1 you have a Lambda function that can access the internet. By default, Lambda functions do not have access to your VPC.

A simple diagram would look like this -

Lambda function can access the internet directly, but not VPC (this is normal)
Lambda function can access the internet directly, but not VPC (this is normal)

You should be able to run it using the following command -

Now you are going to connect that Lambda function to your VPC via the private subnet and the default security group. But before that, you need to give the Lambda function another permissions policy to allow it to create the VPC connection.

Run -

aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole --role-name OpenBreweryDBQueryRole 

Now that you have the necessary permissions, you can attach the Lambda function to the VPC -

aws lambda update-function-configuration --function-name OpenBreweryDBQuery --vpc-config SubnetIds=subnet-44444,SecurityGroupIds=sg-11111

This will take a while…

Once it is complete, try to invoke the Lambda function, it won’t work!

The Lambda function no longer has access to the internet, but it can access your VPC.

From your VPC, the Internet Gateway can access the internet. Over the next few steps, you will route internet traffic from your Lambda function to the Internet Gateway.

Lambda function attached to VPC. Can't access internet directly and can't access Internet Gateway
Lambda function attached to VPC. Can’t access internet directly and can’t access Internet Gateway

4. Allocate an IP address for the NAT Gateway

Before you can create a NAT Gateway, you need to allocate an IP address for it to use.

Run -

aws ec2 allocate-address --domain vpc

In the output, you will see an allocation id.

"AllocationId": "eipalloc-55555"

If you didn’t see it, run the below to review your allocation addresses

aws ec2 describe-addresses

5. Create the NAT Gateway

Create a NAT Gateway on the public subnet, in my case that is subnet-33333.

Run -

aws ec2 create-nat-gateway --subnet-id subnet-33333 --allocation-id eipalloc-55555 --tag-specifications 'ResourceType=natgateway,Tags=[{Key=Name,Value=my-nat-gateway}]'

The output will include the NAT Gateway Id

"NatGatewayId": "nat-66666",

NAT Gateway on public subnet. Lambda still has no access to internet
NAT Gateway on public subnet. Lambda still has no access to internet

6. Routing tables and routes

6.1 Create a new route table

You already have a “main” route table that directs all traffic to the Internet Gateway. But you need one that will direct traffic from the private subnet to the NAT Gateway.

To see your existing route tables, run -

aws ec2 describe-route-tables --query 'RouteTables[?VpcId==`vpc-22222`]'

Create a new route table by running -

aws ec2 create-route-table --vpc-id vpc-22222 --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=my-nat-route-table}]'

The output will include the route table id, it will look like this -

"RouteTableId": "rtb-77777",

6.2 Associate the private subnet to the new route table

Now that you have a route table, you have to associate the private subnet with it. Remember it’s the private subnet this time.

Run -

aws ec2 associate-route-table --route-table-id rtb-77777 --subnet-id subnet-44444

6.3 Add a route directing all internet traffic to the NAT Gateway

Almost there.

Now you need to add a route to the route table that directs all internet traffic to the NAT Gateway.

Run -

aws ec2 create-route --route-table-id rtb-77777 --destination-cidr-block 0.0.0.0/0 --nat-gateway-id nat-66666

If you have followed the steps correctly, you should now be able to access the internet from your Lambda function.

Route from private subnet to NAT Gateway for internet traffic. Lambda function can access the internet!
Route from private subnet to NAT Gateway for internet traffic. Lambda function can access the internet!

7. Invoke the Lambda function

Invoke the Lambda function, it will work, you now have internet access again!

comments powered by Disqus

Related