diff --git a/README.md b/README.md index e7e789d..8bcead3 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ module "label" { } module "example1" { - source = "github.com/obytes/terraform-aws-vpc.git?ref=v1.0.9" + source = "github.com/obytes/terraform-aws-vpc.git?ref=v1.0.10" enabled = true prefix = module.label.id name = "vpc" diff --git a/examples/example.tf b/examples/example.tf index 6197572..c532d3f 100644 --- a/examples/example.tf +++ b/examples/example.tf @@ -17,11 +17,15 @@ module "example1" { additional_tags = module.label.tags cidr_block = "172.16.0.0/18" enable_dns_hostnames = true - enable_nat_gateway = true + enable_nat_gateway = false + nat_instance_enabled = true enable_internet_gateway = true create_public_subnets = true max_subnet_count = 3 single_nat_gateway = true + private_subnets_enabled = true + ipv4_enabled = true + additional_default_route_table_tags = { Managed = "Terraform" Default = "Yes" diff --git a/nat-gw.tf b/nat-gw.tf index fee1fb4..572155f 100644 --- a/nat-gw.tf +++ b/nat-gw.tf @@ -7,7 +7,7 @@ locals { resource "aws_eip" "_" { - count = local.enabled ? local.nat_gateway_eip_count : 0 + count = local.enabled && var.enable_nat_gateway ? 1 : local.enabled && local.nat_instance_enabled ? 1 : 0 vpc = true tags = merge(var.additional_tags, tomap({ "Name" = join(local.delimiter, [local.name, count.index]) })) } diff --git a/nat_instance.tf b/nat_instance.tf new file mode 100644 index 0000000..d26d23b --- /dev/null +++ b/nat_instance.tf @@ -0,0 +1,118 @@ +locals { + private_enabled = local.enabled && var.private_subnets_enabled + private4_enabled = local.private_enabled && local.ipv4_enabled + ipv4_enabled = local.enabled && var.ipv4_enabled + nat_gateway_setting = var.nat_instance_enabled == true ? var.enable_nat_gateway == true : !( + var.enable_nat_gateway == false # not true or null + ) + nat_instance_setting = local.nat_gateway_setting ? false : var.nat_instance_enabled == true # not false or null + nat_instance_useful = local.private4_enabled + nat_instance_enabled = local.nat_instance_useful && local.nat_instance_setting + need_nat_ami_id = local.nat_instance_enabled && length(var.nat_instance_ami_id) == 0 + nat_instance_ami_id = local.need_nat_ami_id ? data.aws_ami.nat_instance[0].id : try(var.nat_instance_ami_id[0], "") +} + +resource "aws_security_group" "nat_instance" { + count = local.nat_instance_enabled ? 1 : 0 + description = "Security Group for NAT Instance" + vpc_id = join("", aws_vpc._.*.id) + tags = merge(var.additional_tags, var.additional_private_subnet_tags, tomap({ "VPC" = join("", aws_vpc._.*.id), + "Availability Zone" = length(var.azs_list_names) > 0 ? element(var.azs_list_names, count.index) : element(data.aws_availability_zones.azs.names, count.index), + "Name" = join(local.delimiter, [local.name, local.az_map_list_short[local.availability_zones[count.index]]]) } + )) +} + +resource "aws_security_group_rule" "nat_instance_egress" { + count = local.nat_instance_enabled ? 1 : 0 + + description = "Allow all egress traffic" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + security_group_id = join("", aws_security_group.nat_instance.*.id) + type = "egress" +} + +resource "aws_security_group_rule" "nat_instance_ingress" { + count = local.nat_instance_enabled ? 1 : 0 + + description = "Allow ingress traffic from the VPC CIDR block" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = [var.cidr_block] + security_group_id = join("", aws_security_group.nat_instance.*.id) + type = "ingress" +} + +# aws --region us-west-2 ec2 describe-images --owners amazon --filters Name="name",Values="amzn-ami-vpc-nat*" Name="virtualization-type",Values="hvm" +data "aws_ami" "nat_instance" { + count = local.need_nat_ami_id ? 1 : 0 + + most_recent = true + + filter { + name = "name" + values = ["amzn-ami-vpc-nat*"] + } + + filter { + name = "virtualization-type" + values = ["hvm"] + } + + owners = ["amazon"] +} + +# https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-comparison.html +# https://docs.aws.amazon.com/vpc/latest/userguide/VPC_NAT_Instance.html +# https://dzone.com/articles/nat-instance-vs-nat-gateway +resource "aws_instance" "nat_instance" { + count = local.nat_instance_enabled ? 1 : 0 + + ami = local.nat_instance_ami_id + instance_type = var.nat_instance_type + subnet_id = aws_subnet.public[count.index].id + vpc_security_group_ids = [aws_security_group.nat_instance[0].id] + + tags = merge( + var.additional_tags, + { + "Name" = join(local.delimiter, [local.name, count.index]), + "VPC" = join("", aws_vpc._.*.id) + } + ) + + # Required by NAT + # https://docs.aws.amazon.com/vpc/latest/userguide/VPC_NAT_Instance.html#EIP_Disable_SrcDestCheck + source_dest_check = false + associate_public_ip_address = true + + ebs_optimized = true + key_name = join("-", [local.name, "key"]) +} + +resource "aws_eip_association" "nat_instance" { + count = local.nat_instance_enabled ? 1 : 0 + + instance_id = aws_instance.nat_instance[count.index].id + allocation_id = aws_eip._[count.index].id +} + +# If private IPv4 subnets and NAT Instance are both enabled, create a +# default route from private subnet to NAT Instance in each subnet + +resource "aws_route" "nat_instance" { + count = local.enabled && var.nat_instance_enabled ? 1 : 0 + + route_table_id = element(aws_route_table.private.*.id, count.index) + network_interface_id = element(aws_instance.nat_instance.*.primary_network_interface_id, count.index) + destination_cidr_block = "0.0.0.0/0" + depends_on = [aws_route_table.private] + + timeouts { + create = var.route_create_timeout + delete = var.route_delete_timeout + } +} diff --git a/private-subnets.tf b/private-subnets.tf index 51eec6b..698b6a9 100644 --- a/private-subnets.tf +++ b/private-subnets.tf @@ -18,7 +18,7 @@ resource "aws_subnet" "private" { # There are as many route_table as local.nat_gateway_count resource "aws_route_table" "private" { - count = local.enabled && local.private_subnet_count > 0 ? local.nat_gateway_count : 0 + count = local.enabled && local.private_subnet_count > 0 ? 1 : 0 vpc_id = aws_vpc._[count.index].id tags = merge(var.additional_tags, tomap({ "Name" = join(local.delimiter, [local.name, "prv-route", count.index]) }), diff --git a/variables.tf b/variables.tf index b82bfb7..d921fb6 100644 --- a/variables.tf +++ b/variables.tf @@ -59,8 +59,54 @@ variable "azs_list_names" { variable "enable_nat_gateway" { type = bool + description = <<-EOT + Set `true` to create NAT Gateways to perform IPv4 NAT and NAT64 as needed. + Defaults to `true` unless `nat_instance_enabled` is `true`. + EOT + default = null +} + +############## NAT instance configuration ################### +variable "nat_instance_type" { + type = string + description = "NAT Instance type" + default = "t3.micro" +} + +variable "nat_instance_enabled" { + type = bool + description = <<-EOT + Set `true` to create NAT Instances to perform IPv4 NAT. + Defaults to `false`. + EOT + default = null +} + +variable "private_subnets_enabled" { + type = bool + description = "If false, do not create private subnets (or NAT gateways or instances)" default = true - description = "Should be true if you want to provision NAT Gateways for each of your private networks" +} + +variable "ipv4_enabled" { + type = bool + description = "Set `true` to enable IPv4 addresses in the subnets" + default = true +} + +variable "nat_instance_ami_id" { + type = list(string) + description = <<-EOT + A list optionally containing the ID of the AMI to use for the NAT instance. + If the list is empty (the default), the latest official AWS NAT instance AMI + will be used. NOTE: The Official NAT instance AMI is being phased out and + does not support NAT64. Use of a NAT gateway is recommended instead. + EOT + default = [] + validation { + condition = length(var.nat_instance_ami_id) < 2 + error_message = "Only 1 NAT Instance AMI ID can be provided." + } } variable "single_nat_gateway" {