Testing AWS PrivateLink
This Terraform recipe creates a full, minimal infrastructure to test the AWS PrivateLink feature. With it, you can connect from internal services to the AWS API without public IP addresses, NAT gateways or similar elements.
You can test it cloning this gist repo:
git clone https://gist.github.com/f4bf0643c166d8c7033037daffb51972.git tf-vpcprivatelinktest
The recipe creates the following elements:
- VPC and related objects, with Internet Gateway (needed for the bastion), but without NAT gateways or similar elements, and DNS features enabled
- A bastion EC2 instance
- An internal EC2 instance in the private subnet, that has no Internet access
- A role attached to the internal instance that allows access to the S3, EC2 and DynamoDB APIs
- An S3 endpoint (gateway type)
- An EC2 endpoint (interface type)
After cloning, create the infrastructure (you must have created a key pair previously, and set its name in the key_pair
variable to be able to access the instances); in this first step, we won’t create the endpoints so we can ensure that we don’t have Internet access:
terraform init
terraform apply -auto-approve -var key_pair=mykeypair -var create_endpoints=false
After that, we can access the internal instance using the bastion as jump host:
ssh-add mykeypair.pem
ssh -J ec2-user@$(terraform output -raw bastion_public_ip) ec2-user@$(terraform output -raw internal_private_ip)
Once logged in the internal instance, we can verify that without endpoints, we are not able to access the AWS APIs, as we don’t have access to the Internet:
aws configure set region eu-west-1 # Set minimal AWS CLI configuration
dig s3.eu-west-1.amazonaws.com # Returns a public IP
dig ec2.eu-west-1.amazonaws.com # Returns a public IP
dig dynamodb.eu-west-1.amazonaws.com # Returns a public IP
aws s3 ls # Doesn't work because we can't reach the service API
aws ec2 describe-instances # Doesn't work because we can't reach the service API
aws dynamodb list-tables # Doesn't work because we can't reach the service API
Now, we re-deploy the infrastructure, but in this case, we will tell Terraform to create the VPC endpoints:
terraform apply -auto-approve -var key_pair=mykeypair -var create_endpoints=true
Now, login again to the instance, and let’s do the same tests as before:
aws configure set region eu-west-1 # Set minimal AWS CLI configuration
dig s3.eu-west-1.amazonaws.com # Returns a public IP (gateway endpoint mode) -> 52.218.109.219
dig ec2.eu-west-1.amazonaws.com # Returns a private IP (interface endpoint mode) -> 10.0.1.248, 10.0.2.54
dig dynamodb.eu-west-1.amazonaws.com # Returns a public IP (no endpoint) -> 52.119.242.40
aws s3 ls # Works because S3 endpoint (gateway)
aws ec2 describe-instances # works because EC2 endpoint (interface)
aws dynamodb list-tables # Doesn't work, because internal instance hasn't got access to Internet, and no endpoint exists for DynamoDB
As you can see, for endpoints in gateway mode, although it seems that we are talking with a public IP address, the routes that have been added to the route table of the private subnets, does the magic, routing the requests (that matches the prefix lists) to those addresses through the endpoint.
We can see the prefix lists:
aws ec2 describe-prefix-lists
{
"PrefixLists": [
{
"Cidrs": [
"52.94.5.0/24",
"52.119.240.0/21",
"52.94.24.0/23"
],
"PrefixListId": "pl-6fa54006",
"PrefixListName": "com.amazonaws.eu-west-1.dynamodb"
},
{
"Cidrs": [
"3.5.64.0/21",
"3.5.72.0/23",
"52.218.0.0/17"
],
"PrefixListId": "pl-6da54004",
"PrefixListName": "com.amazonaws.eu-west-1.s3"
}
]
}
In the previous output, we can see that the IP returned for the S3 API (52.218.109.219) matches the second prefix list, while the IP returned for the DynamoDB endpoint (52.119.242.40) matches the first prefix list (these prefix lists, for S3 and DynamoDB, that are the only services supported in gateway mode, exist by default, although we haven’t requested to create a DynamoDB endpoint).
The following command shows the routes for the private subnets route tables, where we can see the entries related to the prefix lists:
aws ec2 describe-route-tables --route-table-ids $(terraform output -json private_route_tables | jq -r 'join(" ")') --query 'RouteTables[].Routes'
[
[
{
"DestinationCidrBlock": "10.0.0.0/16",
"GatewayId": "local",
"Origin": "CreateRouteTable",
"State": "active"
},
{
"DestinationPrefixListId": "pl-6da54004",
"GatewayId": "vpce-0740fd0d657fb80f9",
"Origin": "CreateRoute",
"State": "active"
}
],
[
{
"DestinationCidrBlock": "10.0.0.0/16",
"GatewayId": "local",
"Origin": "CreateRouteTable",
"State": "active"
},
{
"DestinationPrefixListId": "pl-6da54004",
"GatewayId": "vpce-0740fd0d657fb80f9",
"Origin": "CreateRoute",
"State": "active"
}
]
]
Great! We have verified that we can access the AWS APIs without any element connected to the Internet.
Finally, you can destroy the infrastructure:
terraform destroy -auto-approve