AWS provide a guide for working out the IP addresses of any service endpoints to allow egress to it. This relies on downloading the IP addresses of all AWS endpoints and then filtering for ones in the AMAZON
list but removing the ones from the EC2
list.
Thankfully we can get those IP addresses directly in Terraform using the aws_ip_ranges
data source and can filter for both the AMAZON
ranges and the EC2
ranges:
data "aws_region" "current" {}
data "aws_ip_ranges" "amazon" {
regions = [data.aws_region.current.name]
services = ["amazon"]
}
data "aws_ip_ranges" "ec2" {
regions = [data.aws_region.current.name]
services = ["ec2"]
}
The above example would get all of the IP addresses for the AMAZON
and the EC2
blocks (which are a subset of the AMAZON
ones) for the region you are running in.
To remove the EC2
blocks from the AMAZON
one we need to use the setsubtract
function:
locals {
aws_control_plane = setsubtract(data.aws_ip_ranges.amazon.cidr_blocks, data.aws_ip_ranges.ec2.cidr_blocks)
}
This should give us just the IP ranges that we want to allow our security group egress to.
Unfortunately this is likely to be over 60 CIDR ranges which would equate to more than 60 rules. And a security group is limited to a max of 60 ingress and 60 egress rules:
You can have 60 inbound and 60 outbound rules per security group
(making a total of 120 rules). This quota is enforced separately for
IPv4 rules and IPv6 rules; for example, a security group can have 60
inbound rules for IPv4 traffic and 60 inbound rules for IPv6 traffic.
A rule that references a security group or prefix list ID counts as
one rule for IPv4 and one rule for IPv6.
A quota change applies to both inbound and outbound rules. This quota
multiplied by the quota for security groups per network interface
cannot exceed 1000. For example, if you increase this quota to 100, we
decrease the quota for your number of security groups per network
interface to 10.
We can, however, have multiple security groups per interface so we can just spread these ranges across multiple security groups and attach multiple security groups to the instance:
To do this we need to split the list of ranges we have into blocks of 60 and then loop over the security group resource that we're going to create. We can do this with the chunklist
function:
locals {
aws_control_plane_chunked = chunklist(local.aws_control_plane, 60)
}
This returns a list of lists with a max of 60 CIDR blocks in each.
We can then create our multiple security groups by iterating over these lists:
resource "aws_security_group" "aws_only_egress" {
count = length(local.aws_control_plane_chunked)
name = "aws-only-egress-example-chunk-${count.index + 1}"
egress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = local.aws_control_plane_chunked[count.index]
}
}
And then finally we need to attach these multiple security groups to our instance:
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}
resource "aws_instance" "example" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
vpc_security_group_ids = aws_security_group.aws_only_egress.*.id
}