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!