Running Terraform inside a network-isolated AWS environment forces a different connectivity model than the default internet-reliant workflow. When the public internet is unavailable, VPC Endpoints become the backbone for private service access. The same constructs also solve multi-VPC DNS sharing problems when private hosted zones are required. This article covers the full lifecycle of creating, securing, and validating VPC Endpoints with Terraform, from gateway endpoints for state storage to interface endpoints for control plane services and custom DNS handling.
What VPC Endpoints Provide
VPC Endpoints allow AWS resources in a VPC to privately connect to AWS services without traffic traversing the public internet. They use AWS PrivateLink and Elastic Network Interfaces to route traffic securely through the AWS network.
Two endpoint types exist:
- Gateway Endpoints: For S3 and DynamoDB. Free and highly efficient.
- Interface Endpoints: For all other services like EC2, IAM, STS, RDS, etc. Incur costs.
Interface endpoints create an elastic network interface in your subnet, allowing for private connectivity using private IP addresses.
Gateway Endpoints For Terraform State
Terraform in an air-gapped environment relies on private access to S3 for state files and modules, and optionally DynamoDB for state locking. Both are served via Gateway Endpoints.
S3 Gateway Endpoint
The S3 Gateway Endpoint is required for storing Terraform state files in S3 and downloading modules or remote files from an S3 bucket.
Creation via AWS CLI:
bash
aws ec2 create-vpc-endpoint \
--vpc-id <vpc-id> \
--service-name com.amazonaws.<region>.s3 \
--route-table-ids <route-table-id>
Route Table: Add the endpoint to the route table associated with your private subnets.
Permissions: Restrict access to specific buckets using an endpoint policy:
json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::my-terraform-state-bucket",
"arn:aws:s3:::my-terraform-state-bucket/*"
]
}
]
}
Terraform module pattern:
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]
}
hcl
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"
}
}
Gateway endpoints only support S3 and DynamoDB resources.
DynamoDB Gateway Endpoint
The DynamoDB Gateway Endpoint is required if you are using DynamoDB for state locking.
Creation:
bash
aws ec2 create-vpc-endpoint \
--vpc-id <vpc-id> \
--service-name com.amazonaws.<region>.dynamodb \
--route-table-ids <route-table-id>
No additional endpoint policies are needed for DynamoDB.
Module example:
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
}
]
}
Interface Endpoints For Control Plane Services
Terraform needs interface VPC Endpoints for most AWS services when operating without internet access.
Key services:
- com.amazonaws.
.ec2 - com.amazonaws.
.iam - com.amazonaws.
.sts - com.amazonaws.
.secretsmanager
Example CLI creation:
bash
aws ec2 create-vpc-endpoint \
--vpc-id <vpc-id> \
--service-name com.amazonaws.<region>.ec2 \
--subnet-ids <subnet-id> \
--security-group-ids <security-group-id>
bash
aws ec2 create-vpc-endpoint \
--vpc-id <vpc-id> \
--service-name com.amazonaws.<region>.iam \
--subnet-ids <subnet-id> \
--security-group-ids <security-group-id>
bash
aws ec2 create-vpc-endpoint \
--vpc-id <vpc-id> \
--service-name com.amazonaws.<region>.sts \
--subnet-ids <subnet-id> \
--security-group-ids <security-group-id>
bash
aws ec2 create-vpc-endpoint \
--vpc-id <vpc-id> \
--service-name com.amazonaws.<region>.secretsmanager \
--subnet-ids <subnet-id> \
--security-group-ids <security-group-id>
Security Groups For Interface Endpoints
For interface VPC Endpoints, ensure the associated security groups allow incoming traffic from the private subnet CIDRs.
Example Security Group Rules:
- Allow HTTPS port 443 traffic from private subnet CIDRs.
The Terraform AWS VPC Endpoint Module is designed to create VPC endpoints on an existing VPC in your AWS infrastructure. This module offers the ability to automatically generate a dedicated security group for all Interface endpoints when the createendpointsg variable is set to true, we recommend this setting to be set.
This module is easy to use with simplified examples. It removes the complexity of managing multiple resources manually and deploys the needed resources faster.
A skipped Checkov alert CKV2AWS5 is intentionally skipped in complete examples. This 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.
Private DNS Enablement
Enable private DNS names so that Terraform can use the standard AWS service URLs e.g., ec2.amazonaws.com without modification.
bash
aws ec2 modify-vpc-endpoint \
--vpc-endpoint-id <endpoint-id> \
--private-dns-enabled
Validating the setup after creating necessary VPC Endpoints involves running terraform init and ensuring it can access the S3 bucket and DynamoDB table.
Endpoint Comparison And Specs
| Attribute | Gateway Endpoint | Interface Endpoint |
|---|---|---|
| Supported Services | S3, DynamoDB | EC2, IAM, STS, RDS, Secrets Manager, etc. |
| Cost | Free | Incur costs |
| Network Interface | No ENI | ENI created in subnet |
| Routing | Route table association | Subnet selection |
| Private DNS | N/A | Can be enabled |
| Service | Endpoint Type | Required For |
|---|---|---|
| com.amazonaws. |
Gateway | State storage, modules |
| com.amazonaws. |
Gateway | State locking |
| com.amazonaws. |
Interface | EC2 API calls |
| com.amazonaws. |
Interface | IAM API calls |
| com.amazonaws. |
Interface | Assume role, tokens |
| com.amazonaws. |
Interface | Secrets access |
Private Hosted Zone Sharing Across VPCs
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.
Solution using awsvpcendpointservice to create a dynamic configuration that handles all the multitude of DNS entries removes the guess work. This is a relatively new ability, as the data object for this resource was missing the required privatedns_names until recently.
Using Terraform to extract data from the AWS API allows automatic creation of DNS records in a Private Hosted Zone, enabling multiple VPCs to resolve the endpoint without relying on AWS-provided DNS per VPC.
Validation Workflow
After establishing a secure connection to the VPC endpoint, you can now access and list the S3 buckets from within the private VPC.
Conclusion
VPC Endpoint design for Terraform in isolated networks requires a deliberate split between gateway endpoints for data plane storage and interface endpoints for control plane APIs. S3 and DynamoDB gateway endpoints provide free, route-table-based access for state files and locking. Interface endpoints for EC2, IAM, STS, and Secrets Manager require subnet placement, security groups allowing HTTPS from private CIDRs, and private DNS enablement to preserve standard AWS endpoints.
Module automation reduces manual resource sprawl and centralizes security group creation for interface endpoints. The intentional allowance of inbound 443 within those security groups aligns with SSL/TLS requirements and explains suppressed security scanning alerts.
For multi-VPC scenarios, private hosted zone management becomes the critical layer. Disabling private DNS on endpoints forces explicit DNS records, and using awsvpcendpointservice data to enumerate privatedns_names provides a reliable, API-driven method to populate a shared Private Hosted Zone. This approach avoids manual record guesswork and supports cross-VPC endpoint sharing.
Together, gateway endpoints, interface endpoints, proper security groups, and DNS handling form a complete private connectivity stack that lets Terraform run reliably in air-gapped AWS environments and across complex VPC topologies.