Terraform AWS VPC Endpoint Implementation Patterns and Module Options

Creating private connectivity to AWS services from within a Virtual Private Cloud is a core pattern for secure workloads. Terraform provides declarative control over VPC endpoints, but the implementation details vary significantly by endpoint type, DNS requirements, and cross-region constraints. The reference material covers multiple community modules, DNS handling with Private Hosted Zones, and specific Terraform provider behaviors that affect cross-region service consumption.

Terraform Module for Managing VPC Endpoints

The Terraform AWS VPC Endpoint Module is designed to create VPC endpoints on an existing VPC in AWS infrastructure. The module offers the ability to automatically generate a dedicated security group for all Interface endpoints when the createendpointsg variable is set to true, and this setting is recommended.

The module is described as easy to use with simplified examples, removes the complexity of managing multiple resources manually, and deploys the needed resources faster. Examples are available in the module repository. Charges apply. See here for more details.

A minimum example from the module shows a Gateway endpoint for DynamoDB:

hcl module "minimum_vpc_endpoints" { source = "boldlink/vpc-endpoints/aws/" version = "<latest_version_nr>" vpc_id = local.vpc_id tags = var.tags vpc_endpoints = [ { service_name = "com.amazonaws.${local.region}.dynamodb" vpc_endpoint_type = "Gateway" name = "DynamoDB" route_table_ids = flatten(local.route_table_ids) policy = data.aws_iam_policy_document.ddb_endpoint_policy.json } ] }

The complete example notes a skipped Checkov alert CKV2AWS5. The alert is intentionally skipped as VPC endpoints are configured to utilize security groups only for specific Interface VPC endpoints. The security group attached is specifically designed to allow SSL/TLS inbound traffic on port 443.

Infrablocks Terraform AWS VPC Endpoint Module

A separate Terraform module for managing a VPC endpoint in AWS is provided by infrablocks. The module is compatible with Terraform versions greater than or equal to Terraform 1.3.

The reference material includes a usage skeleton:

hcl module "vpc_endpoint" { source = "infrablocks/vpc-endpoint/aws" version = "0.0.0" // TODO }

See the Terraform registry entry for more details.

The module documentation includes a table structure for inputs:

| Name | Description | Default | Required |
|---|---|---|---|

and a second table:

| Name | Description |
|---|---|

In order for the build to run correctly, a few tools need to be installed on the development machine:

  • Ruby 3.1.1
  • Bundler
  • git
  • git-crypt
  • gnupg
  • direnv
  • aws-vault

Installing the required tools is best managed by homebrew. To install homebrew:

bash ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

Then, to install the required tools:

```bash

ruby

brew install rbenv
brew install ruby-build
echo 'eval "$(rbenv init - bash)"' >> ~/.bash_profile
echo 'eval "$(rbenv init - zsh)"' >> ~/.zshrc
eval "$(rbenv init -)"
rbenv install 3.1.1
rbenv rehash
rbenv local 3.1.1
gem install bundler

git, git-crypt, gnupg

brew install git
brew install git-crypt
brew install gnupg

aws-vault

brew cask install

direnv

brew install direnv
echo "$(direnv hook bash)" >> ~/.bash_profile
echo "$(direnv hook zsh)" >> ~/.zshrc
eval "$(direnv hook $SHELL)"
direnv allow
```

Running the build requires an...

The API gateway requires:

  • TODO

The API gateway consists of:

  • TODO

Private Hosted Zone DNS Control for VPC Endpoints

Sometimes instead of a simple AWS VPC Endpoint, you need to control your own DNS through a Private Hosted Zone. This is useful when you have multiple VPCs that need to use the same VPC Endpoints as you cannot share the AWS created DNS across VPCs/Accounts.

The challenge is that AWS VPC Endpoint DNS is typically limited to the originating VPC. This means that if you have multiple VPCs that need to access the same VPC Endpoint, you cannot share the DNS resolution across VPCs. This is a problem if you have a multi-account or multi-VPC setup where you want to share a VPC Endpoint across VPCs.

When you create a VPC Endpoint, and turn off the privatednsenabled flag, you lose the ability to use the AWS provided DNS. You also are not given information about what DNS records you should have created in your Private Hosted Zone. Making assumptions here leads to a lot of misses and misconfigurations.

Fortunately, the data is available in the AWS API, and Terraform can be used to extract it.

Using awsvpcendpointservice to create a dynamic configuration that handles all the multitude of DNS entries is a solution where guess work is removed from this process. This is a relatively new ability, as the data object for this resource was missing the required privatedns_names until recently.

The guide walks through how to set up a VPC Endpoint with a Private Hosted Zone using Terraform step by step.

Gateway Versus Interface Endpoint Types

To connect to DynamoDB, a VPC Endpoint of type Gateway can be used, which is easier to manage and does not come with additional costs if the data transfer is within the VPC.

Gateway endpoints only support S3 and DynamoDB resources.

Interface endpoints create an elastic network interface in your subnet, allowing for private connectivity using private IP addresses.

A step-by-step guide example shows a module structure for a Gateway endpoint to S3:

hcl module "vpce_endpoint" { source = "./vpc-endpoint" vpc_id = module.networking.dev_proj_1_vpc_id service_name = "com.amazonaws.eu-west-1.s3" vpc_endpoint_type = "Gateway" route_table_ids = [module.networking.dev_proj_1_private_route_table_ids] }

The vpc-endpoint module main.tf includes:

hcl variable "vpc_id" {} variable "service_name" {} variable "vpc_endpoint_type" {} variable "route_table_ids" {} resource "aws_vpc_endpoint" "s3" { vpc_id = var.vpc_id service_name = var.service_name vpc_endpoint_type = var.vpc_endpoint_type route_table_ids = var.route_table_ids tags = { Name = "dev-proj-1-vpce-s3" } }

Variable definitions with types and descriptions:

hcl variable "vpc_id" { type = string description = "VPC ID" } variable "service_name" { type = string description = "AWS Service Name for VPC Endpoint" } variable "vpc_endpoint_type" { type = string description = "VPC Endpoint Type (Gateway/Interface)" } variable "route_table_ids" { type = list(string) description = "List of Route Table IDs" }

After establishing a secure connection to the VPC endpoint, access and listing of S3 buckets can be performed.

The guide walks through setting up a VPC, creating subnets, configuring route tables, and finally creating VPC endpoints using Terraform.

Cross-Region VPC Endpoint Service Consumption

When setting up cross-region connection between VPC Endpoint Service in two different regions using terraform, an error can occur similar to:

Error: creating EC2 VPC Endpoint (com.amazonaws.vpce.us-east-2.vpce-svc-xxxxxxxxxxxxx): operation error EC2: CreateVpcEndpoint, https response error StatusCode: 400, RequestID: xxxxxxx, api error InvalidServiceName: The Vpc Endpoint Service 'com.amazonaws.vpce.us-east-2.vpce-svc-xxxxxxxxxxxxx' does not exist

However, cross region connection between VPC Endpoint Service in two different regions through the AWS Management Console works fine.

The cause is demonstrated with a configuration trying to set up a cross-region connection between a VPC Endpoint Service in Ohio us-east-2 region and a VPC Endpoint in Oregon us-west-2 region.

The configuration for awsvpcendpoint looks as below:

hcl resource "aws_vpc_endpoint" "test_vpc_endpoint" { provider = aws.oregon vpc_id = var.vpc_id_oregon vpc_endpoint_type = "Interface" service_name = data.aws_vpc_endpoint_service.rds_endpoint_service.service_name # Ohio service_name security_group_ids = var.glue_security_groups subnet_ids = var.glue_subnets private_dns_enabled = var.glue_private_dns_enabled tags = local.tags depends_on = [aws_vpc_endpoint_service.rds_endpoint_service] }

Since we need to consume the vpcendpoint from the serviceregion = us-east-2 which is Ohio, if we do not provide that service_region in the configuration, terraform will throw that error thinking the VPC Endpoint Service does not exist.

The solution is to add this optional attribute in the resource.

Module Comparison and Selection Considerations

Module Source Endpoint Types Covered Security Group Automation Notable Constraint
boldlink/vpc-endpoints/aws Terraform Registry Gateway, Interface createendpointsg creates dedicated SG for Interface Checkov CKV2AWS5 intentionally skipped for SG-only Interface endpoints
infrablocks/vpc-endpoint/aws Terraform Registry Generic VPC endpoint Not specified in facts Requires Ruby 3.1.1, Bundler, git-crypt, gnupg, direnv, aws-vault for build
Custom PHZ pattern Guide Interface with private DNS disabled Manual DNS via awsvpcendpoint_service data Requires privatednsnames data from awsvpcendpoint_service
Custom module vpce_endpoint Example Gateway None Variables: vpcid, servicename, vpcendpointtype, routetableids

Implementation Patterns

VPC endpoint implementation with Terraform benefits from separating endpoint type concerns. Gateway endpoints require routetableids and are limited to S3 and DynamoDB. Interface endpoints require subnetids, securitygroupids, and often privatedns_enabled control.

For Interface endpoints, the createendpointsg option automates security group creation for inbound SSL/TLS on port 443. When private DNS is disabled, the awsvpcendpoint_service data source can be used to extract the required DNS records for a Private Hosted Zone, avoiding manual assumptions.

Cross-region consumption requires aligning the provider region for the VPC endpoint resource with the consumer region and explicitly specifying the service region for the data source that resolves the service name. The dependson relationship to the vpcendpoint_service data source ensures the service name is resolved before endpoint creation.

Conclusion

Terraform VPC endpoint automation hinges on understanding endpoint type constraints, DNS ownership, and provider region alignment. The boldlink module provides simplified multi-endpoint declarations with optional security group automation for Interface endpoints and intentional Checkov handling for SG-only configurations. The infrablocks module offers a more generic VPC endpoint wrapper with specific build toolchain requirements for development.

Private Hosted Zone control introduces a need to disable privatednsenabled and dynamically generate DNS records via awsvpcendpointservice, a capability that became practical once privatedns_names was exposed in the data object. Gateway endpoints remain the low-cost choice for S3 and DynamoDB with route table associations, while Interface endpoints provide ENI-based private connectivity for a broader service set.

Cross-region VPC Endpoint Service consumption is a common failure point where Terraform defaults to the current provider region for service name resolution, producing InvalidServiceName errors even when the AWS Console succeeds. Explicit service_region configuration and proper provider aliasing resolve the mismatch.

When building reusable modules, variable definitions for vpcid, servicename, vpcendpointtype, and routetableids or subnetids and securitygroup_ids provide the minimal interface for both Gateway and Interface patterns. Tagging conventions and policy attachments for services like DynamoDB complete the secure, repeatable deployment pattern.

Sources

  1. TerraformFoundation terraform-aws-vpc-endpoints
  2. infrablocks terraform-aws-vpc-endpoint
  3. dwood.dev terraformvpcendpointswith_phz
  4. dev.to creating-and-linking-vpc-endpoint-of-type-interface-using-terraform-and-aws-management-console-2oo
  5. support.hashicorp.com Set-up-a-cross-region-connection-between-VPC-Endpoint-Service-in-two-different-regions
  6. linkedin.com creating-aws-vpc-endpoints-terraform-step-by-step-guide

Related Posts