# Interface VPC endpoints: private connectivity to AWS APIs without NAT. # One endpoint per service, one ENI per private subnet, routes in the private RT. locals { services = var.services # e.g. ["s3", "secretsmanager", "kms", "sqs"] } resource "aws_vpc_endpoint" "interface" { for_each = toset(local.services) vpc_id = var.vpc_id service_name = "com.amazonaws.${var.region}.${each.key}" subnet_ids = var.private_subnet_ids security_group_ids = [aws_security_group.endpoint.id] private_dns_enabled = true tags = merge(var.tags, { Name = "${var.name}-vpce-${each.key}" }) lifecycle { create_before_destroy = true } } resource "aws_security_group" "endpoint" { name = "${var.name}-vpce-sg" vpc_id = var.vpc_id tags = merge(var.tags, { Name = "${var.name}-vpce-sg" }) ingress { description = "AWS API traffic from within the VPC" protocol = "-1" cidr_blocks = [var.vpc_cidr] } egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } } # Interface endpoints must be routed via their prefix list; routing the whole # VPC CIDR here would shadow local intra-VPC traffic (RDS, Fargate <-> Fargate). resource "aws_route" "endpoint" { for_each = toset(local.services) route_table_id = var.private_route_table_id destination_prefix_list_id = aws_vpc_endpoint.interface[each.key].prefix_list_id vpc_endpoint_id = aws_vpc_endpoint.interface[each.key].id depends_on = [aws_vpc_endpoint.interface] }