How to create private buckets with Lambda access

Sometimes S3 buckets need to be private, which means that access from the public internet (including the AWS Console) should not be allowed. We can interact with private buckets by using Gateway endpoints. The architecture sample of this post contains a private bucket and a Lambda function uploading the content.

1. Why are VPC endpoints useful?

VPC endpoints make it possible to privately connect to AWS services from a VPC.

It means that traffic won’t leave the AWS network and won’t go via the public internet.

When we normally want our application to connect to services, like S3, using SDK, traffic goes through the internet via an encrypted channel (TLS).

In some cases (for compliance or legal reasons), we don’t want resources (a bucket, for example) to be accessible through the internet. Instead, it would be much better, if we routed all traffic to the private bucket via AWS’s private network.

VPC endpoints provide a solution to this problem.

2. Gateway endpoints

AWS provides two types of endpoints: Gateway endpoints and Interface endpoints.

Gateway endpoints enables us to privately connect to S3 and DynamoDB. Interface endpoints provide private connections to other services.

These two endpoint types work differently.

Gateway endpoints use route tables to direct traffic through them. They are highly available and fully managed.

Interface endpoints, on the other hand, utilize elastic network interfaces (ENIs), and they are not highly available by default.

3. Architecture

The following diagram pictures a part of a bigger service, where a Lambda function uploads objects to an S3 bucket.

Lambda access to S3 via Gateway endpoint
Lambda access to S3 via Gateway endpoint

3.1. Lambda in a VPC

To achieve the above, we need to deploy the function in a VPC. In this case, we create two of them in two private subnets.

When we deploy Lambda functions in a VPC, AWS will create a new ENI in each subnet, and as such, each ENI and subnet combination will result in a new security group. To reduce the number of security groups, we can create one and attach it to both ENIs.

3.2. Private bucket

The S3 bucket will become private by adding a bucket policy to it. The bucket policy denies all traffic unless it comes from the VPC endpoint:

{
  "Effect": "Deny",
  "Condition": {
    "StringNotEquals": {
      "aws:SourceVpce": "vpce-0da40659846fa09f5"
    }
  }
}

We can try to upload an object via the Console - it won’t work. We can create another Lambda function, which has s3:PutObject permissions to upload to the bucket - it won’t work if we don’t deploy it inside the VPC.

The bucket policy makes the bucket private, and only resources in the VPC can access it. Private buckets are an excellent way to protect sensitive data.

3.3. Route table entries

By specifying the service name (com.amazonaws.us-east-1.s3) in the Destination field of the route table, where the target is the VPC Gateway endpoint, all traffic destined to S3 will flow through the endpoint and not via the Internet Gateway.

4. Some notes

4.1 Truly private bucket

A real private bucket has the following bucket policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Principal": "*",
      "Action": "s3:*",
      "Effect": "Deny",
      "Resource": [
        "arn:aws:s3:::BUCKET_NAME",
        "arn:aws:s3:::BUCKET_NAME/*"
      ],
      "Condition": {
        "StringNotEquals": {
          "aws:SourceVpce": "VPC_ENDPOINT_ID"
        }
      }
    }
  ]
}

I used s3:PutObject and s3:PutObjectTagging in the Action field instead of all S3 operations to avoid getting locked out of my bucket. In real life, the above policy snippet should replace the one in the template.

4.2. ENI permissions

Because we deploy the Lambda function in a VPC, it will need the ec2:CreateNetworkInterface permission.

The AWSLambdaVPCAccessExecutionRole managed policy comes with this permission along with ec2:DeleteNetworkInterface, which allows us to delete the stack entirely.

4.3. UploadViaInternetFn

This function is only there to prove that it cannot upload anything to the bucket because it’s not in the VPC.

This function is not necessary for real-life systems.

4.4. Inline code

The template contains inline code for the functions. Real-life services are more complicated, and a separate folder in the project will have the code.

5. Conclusion

We can create private buckets using a VPC Gateway endpoint and a bucket policy.

The CloudFormation template can be downloaded from my GitHub repo, where you can find more information about this project.

Thanks for reading and see you next time.