Gateway Endpoints vs Internet Routing for S3


Table of Contents
Jump to a section
Introduction
In this blog post, we will explore the differences between using gateway endpoints and internet routing for S3 access. We will discuss why you should use gateway endpoints and how to set them up.
We'll also have a look at some common pitfalls and how to avoid them.
As with most of our articles, we'll also provide you with a complete project that you can run and deploy to your own AWS account.
Feel free to clone the repository and play around with the code!
Make sure to deploy it to your AWS account with SST, not just running the
npx sst dev
command. This is due to the fact that you won't be able to invoke your function locally or via the console.
What are Gateway Endpoints?
Gateway endpoints are a type of VPC endpoint that allows you to access S3 (and other services like DynamoDB) from within your VPC. You may ask: this is already possible from within the VPC using the public internet, so why do we need gateway endpoints?
The answer is: mostly due to better security. You'll also get a small performance benefit.
But back to the question: why is it better?
Well, if you use the public internet, you're exposed to all the security threats that come with it. If you for example want to access S3 from within your private VPC, you'll need to open your VPC to the public internet. At least for the outgoing traffic.
So this is the public internet routing. Even though our S3 bucket resides in the same AWS account, we'll route the traffic through the public internet. For a set up with a private subnet, we'll need to route traffic through a NAT gateway or an NAT instance.
How would this look like with a gateway endpoint?
With a gateway endpoint, we'll route the traffic through the VPC endpoint. This way, we don't need to go through multiple hops and we don't need to expose anything to the public internet.
The best part of this is: you can use gateway endpoints at no additional cost. The downside: gateway endpoints do not allow access from on-premises networks. For this you'll need to use interface endpoints.
Setting up an Gateway Endpoint for Amazon S3
Let's have a look at how to set up a gateway endpoint for Amazon S3.
We'll look at a trivial example: a Lambda function that lists the contents of an S3 bucket.
The Lambda function will reside in a private subnet and will only be able to access the bucket through our gateway endpoint.
We'll make sure that the bucket is not accessible elsewhere by restricting access to the gateway endpoint.
Let's get started!
Prerequisites
Surprise surprise: we'll need to have an AWS account and our CLI needs to be configured.
If you have done that, you can follow the documentation specific to your operating system. If you are using macOS, you can easily install the CLI through Homebrew by executing the command: brew install awscli
.
Your credentials can be configured by running the command: aws configure
.
You can obtain your credentials from the AWS Console.
Creating our VPC and Subnets
Now to the interesting part: creating our VPC and subnets. You can either follow along or clone our repository and run the SST commands yourself.
SST will do a lot of heavy lifting for us:
const { privateSubnets, id: vpcId } = new sst.aws.Vpc('awsf-vpc', { az: 3 });
As you see, it will create a VPC with three private subnets in three different availability zones. We'll also directly get the VPC ID and the private subnets. This way, we can directly use them in our next steps.
Before we create our gateway endpoint, we'll also need to find the route tables of our private subnets:
const routeTableIds = privateSubnets.apply((subnets) =>
subnets.map((subnet) =>
subnet.apply((subnet) =>
aws.ec2
.getRouteTable({
subnetId: subnet,
})
.then((rt) => rt.id),
),
),
);
Now we're ready to create our gateway endpoint!
Creating a Gateway Endpoint
Let's do exactly that:
const endpoint = new aws.ec2.VpcEndpoint('awsf-s3-endpoint', {
vpcId,
serviceName: 'com.amazonaws.us-east-1.s3',
vpcEndpointType: 'Gateway',
routeTableIds,
});
As you can see, we're creating a VPC endpoint with the type Gateway
and the service name com.amazonaws.us-east-1.s3
.
We'll also pass our route table IDs to the endpoint.
Creating an S3 Bucket and Restricting Access to the Gateway Endpoint
Now we'll create an S3 bucket and restrict access to the gateway endpoint:
const bucket = new sst.aws.Bucket('awsf-bucket', {
policy: [
{
effect: 'deny',
actions: ['s3:PutObject', 's3:GetObject', 's3:DeleteObject', 's3:ListBucket'],
principals: [
{
type: 'aws',
identifiers: ['*'],
},
],
conditions: [
{
test: 'StringNotEquals',
variable: 'aws:sourceVpce',
values: [endpoint.id],
},
],
},
],
});
In the bucket policy, we're denying access to the bucket from all principals except the gateway endpoint. This also means that we'll not be able to list the bucket's contents.
You should be able to verify this when accessing your bucket in the web console:
If you see this message, our bucket policy is working as expected.
Creating a Lambda Function that Accesses our Bucket from our private Subnet
Let's continue to the last and final step: creating a Lambda function that accesses our bucket from our private subnet through our new gateway endpoint.
But first, let's create a security group that only allows outbound traffic through the HTTPS port:
const blockAll = new aws.ec2.SecurityGroup('block_all', {
name: 'block_all',
description: 'Security group for private Lambda function',
vpcId,
ingress: [],
egress: [
{
fromPort: 443,
toPort: 443,
protocol: 'tcp',
cidrBlocks: ['0.0.0.0/0'],
},
],
tags: {
Name: 'block_all',
},
});
Now we can create our Lambda function:
new sst.aws.Function('awsf-function', {
timeout: '5 seconds',
handler: 'functions/private.handler',
memory: '1024 MB',
link: [bucket],
vpc: {
privateSubnets,
securityGroups: [blockAll.id],
},
environment: {
BUCKET_NAME: bucket.name,
},
});
The Lambda function resides in the private subnets and will only be able to access the bucket through our gateway endpoint.
Let's find out if this works as expected!
Testing our Setup
For validation purposes, let's adjust our bucket policy manually once so we can upload a single file to the bucket.
You can do this by accessing the permissions
tab in the S3 bucket and then manually removing the Deny
statement.
Alternatively, you can also remove it from the IaC and deploy once.
Afterward you can upload any file to the bucket.
Don't forget to re-adjust the bucket policy after you're done testing!
Now we're good to go.
Let's jump to our Lambda function in the web console and invoke it via the Test
button.
Don't worry about the invocation payload, as we're not using it in our Lambda function.
We'll always only list the bucket's contents. Let's do that:
As we can see, the function is able to list the bucket's contents. This means that our gateway endpoint is working as expected!
Common Pitfalls
As always with networking configurations, there are some common pitfalls that you should be aware of. In this article, we'll include a non-exhaustive list of them.
Too Restrictive Security Group Configuration
If your resource is not able to access the gateway endpoint, it might be due to a too restrictive security group configuration. For example, you've completely blocked egress traffic through the HTTPS port.
If this is the case, your resource can't connect to the gateway endpoint. You'll receive a connect timeout error.
Gateway Endpoint Misconfiguration
If your gateway endpoint is misconfigured, it might be due to a wrong service name or a wrong VPC endpoint type.
For example, you've used the Gateway
type but the service name is incorrect.
Confusion between Gateway and Interface Endpoints
Let's wrap our heads around this one.
Gateway Endpoint:
- Creates a route in your route table.
- Traffic to the service goes through the VPC's internet gateway without using NAT, internet gateway, or VPN.
- Only supports S3 and DynamoDB.
- No ENI (Elastic Network Interface) created.
Interface Endpoint:
- Creates an ENI (Elastic Network Interface) in your subnet.
- Private IP address is assigned to access the service.
- Supports many AWS services (e.g., SSM, SNS, Secrets Manager, etc.).
This means, if you need to access other services than S3 and DynamoDB, you'll need to use interface endpoints.
Final Thoughts
In this article, we've learned about the differences between using gateway endpoints and internet routing for S3 access. We've also seen how to set up a gateway endpoint for Amazon S3 and how to use it to access our bucket from our private subnet.
We've also covered some common pitfalls that you should be aware of.
Generally, it's advisable to keep your traffic inside the AWS network if possible.
Gateway endpoints are one great way to do that.