AWS PrivateLink with Terraform
2024-05-03
๐ Overview
AWS PrivateLink enables secure, private connectivity between VPCs and services hosted on AWS without exposing traffic to the public internet. It's essential for secure service-to-service communication across accounts or VPCs.
In this post, we'll implement a working PrivateLink setup with Terraform: one VPC exposing a service (via NLB), and another VPC accessing it (via interface endpoint). We'll use two Terraform providers to simulate cross-account setup.
๐ ๏ธ Architecture
[ Service Provider VPC ] [ Service Consumer VPC ]
[ EC2 (httpd) ] -> [ NLB ] -> [ Endpoint Service ] <- [ VPC Endpoint ] <- [ EC2 ]
The service is hosted on a private EC2 behind a Network Load Balancer. An Endpoint Service is created from the NLB. The consumer VPC accesses it through an Interface VPC Endpoint.
๐งฑ Prerequisites
- Terraform โฅ 1.0
- Two AWS profiles or roles simulating service provider and consumer
- Basic networking knowledge (subnets, NLB, SGs)
๐งฉ Step 1: Service Provider VPC with NLB and Endpoint Service
provider "aws" {
alias = "provider"
region = "us-east-1"
}
resource "aws_vpc" "provider" {
provider = aws.provider
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "provider_subnet" {
provider = aws.provider
vpc_id = aws_vpc.provider.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
}
resource "aws_security_group" "provider_sg" {
provider = aws.provider
vpc_id = aws_vpc.provider.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["10.0.0.0/16"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "provider_instance" {
provider = aws.provider
ami = "ami-0c94855ba95c71c99"
instance_type = "t2.micro"
subnet_id = aws_subnet.provider_subnet.id
security_groups = [aws_security_group.provider_sg.id]
user_data = <<-EOF
#!/bin/bash
echo "Hello from PrivateLink Service Provider" > index.html
nohup python -m SimpleHTTPServer 80 &
EOF
}
resource "aws_lb" "nlb" {
provider = aws.provider
name = "privatelink-nlb"
internal = true
load_balancer_type = "network"
subnets = [aws_subnet.provider_subnet.id]
}
resource "aws_lb_target_group" "tg" {
provider = aws.provider
name = "privatelink-tg"
port = 80
protocol = "TCP"
vpc_id = aws_vpc.provider.id
target_type = "instance"
}
resource "aws_lb_target_group_attachment" "tg_attachment" {
provider = aws.provider
target_group_arn = aws_lb_target_group.tg.arn
target_id = aws_instance.provider_instance.id
port = 80
}
resource "aws_lb_listener" "listener" {
provider = aws.provider
load_balancer_arn = aws_lb.nlb.arn
port = 80
protocol = "TCP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.tg.arn
}
}
resource "aws_vpc_endpoint_service" "service" {
provider = aws.provider
acceptance_required = true
network_load_balancer_arns = [aws_lb.nlb.arn]
}
๐งฉ Step 2: Service Consumer VPC with VPC Endpoint
provider "aws" {
alias = "consumer"
region = "us-east-1"
}
resource "aws_vpc" "consumer" {
provider = aws.consumer
cidr_block = "10.1.0.0/16"
}
resource "aws_subnet" "consumer_subnet" {
provider = aws.consumer
vpc_id = aws_vpc.consumer.id
cidr_block = "10.1.1.0/24"
availability_zone = "us-east-1a"
}
resource "aws_security_group" "consumer_sg" {
provider = aws.consumer
vpc_id = aws_vpc.consumer.id
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["10.1.0.0/16"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_vpc_endpoint" "consumer_endpoint" {
provider = aws.consumer
vpc_id = aws_vpc.consumer.id
service_name = aws_vpc_endpoint_service.service.service_name
vpc_endpoint_type = "Interface"
subnet_ids = [aws_subnet.consumer_subnet.id]
security_group_ids = [aws_security_group.consumer_sg.id]
}
๐งช Testing
- Accept the VPC Endpoint connection from the Provider side.
- SSH into an EC2 in the Consumer VPC and use:
curl http://<interface-endpoint-dns-name>
You should see:
Hello from PrivateLink Service Provider
๐ Security Notes
- Ensure correct SGs between NLB and Endpoint
- Use IAM condition keys or resource policies if needed
- Enable VPC Flow Logs for visibility
๐ Summary
- We implemented AWS PrivateLink with real infra using Terraform
- Service is exposed internally via NLB and Interface Endpoint
- Setup is secure, scalable, and avoids internet exposure
Next steps? Add DNS, IAM auth, or cross-region routing!