From a4ad35f642d33e9acd2e7af7e80e84e2502edaab Mon Sep 17 00:00:00 2001
From: Mike McGirr <mike@fpcomplete.com>
Date: Fri, 20 Mar 2020 20:39:51 -0700
Subject: [PATCH 1/2] Add initial support for IPv6 to the vpc module and add
 the subnet-ipv6 module

---
 modules/single-port-sg/main.tf   |  8 ++++++
 modules/subnet-ipv6/README.md    |  3 ++
 modules/subnet-ipv6/main.tf      | 22 ++++++++++++++
 modules/subnet-ipv6/output.tf    | 25 ++++++++++++++++
 modules/subnet-ipv6/variables.tf | 49 ++++++++++++++++++++++++++++++++
 modules/subnet-ipv6/versions.tf  |  4 +++
 modules/subnets/variables.tf     |  3 +-
 modules/vpc/main.tf              |  2 ++
 modules/vpc/outputs.tf           |  7 +++++
 modules/vpc/variables.tf         |  7 +++++
 10 files changed, 128 insertions(+), 2 deletions(-)
 create mode 100644 modules/subnet-ipv6/README.md
 create mode 100644 modules/subnet-ipv6/main.tf
 create mode 100644 modules/subnet-ipv6/output.tf
 create mode 100644 modules/subnet-ipv6/variables.tf
 create mode 100644 modules/subnet-ipv6/versions.tf

diff --git a/modules/single-port-sg/main.tf b/modules/single-port-sg/main.tf
index 705e3f5f..0da5d12b 100644
--- a/modules/single-port-sg/main.tf
+++ b/modules/single-port-sg/main.tf
@@ -17,6 +17,12 @@ variable "cidr_blocks" {
   type        = list(string)
 }
 
+variable "ipv6_cidr_blocks" {
+  description = "List of IPv6 CIDR block ranges that the SG allows ingress from"
+  type        = list(string)
+  default     = []
+}
+
 variable "description" {
   description = "Use this string to add a description for the SG rule"
   type        = string
@@ -53,6 +59,7 @@ resource "aws_security_group_rule" "tcp_ingress" {
   to_port           = var.port
   protocol          = "tcp"
   cidr_blocks       = var.cidr_blocks
+  ipv6_cidr_blocks  = var.ipv6_cidr_blocks
   security_group_id = var.security_group_id
 }
 
@@ -65,5 +72,6 @@ resource "aws_security_group_rule" "udp_ingress" {
   to_port           = var.port
   protocol          = "udp"
   cidr_blocks       = var.cidr_blocks
+  ipv6_cidr_blocks  = var.ipv6_cidr_blocks
   security_group_id = var.security_group_id
 }
diff --git a/modules/subnet-ipv6/README.md b/modules/subnet-ipv6/README.md
new file mode 100644
index 00000000..104ba926
--- /dev/null
+++ b/modules/subnet-ipv6/README.md
@@ -0,0 +1,3 @@
+ ## AWS subnet IPv6
+ 
+ Creates a single IPv6 ready subnet
diff --git a/modules/subnet-ipv6/main.tf b/modules/subnet-ipv6/main.tf
new file mode 100644
index 00000000..13d932aa
--- /dev/null
+++ b/modules/subnet-ipv6/main.tf
@@ -0,0 +1,22 @@
+/**
+ * ## AWS Subnet IPv6
+ * Creates a single IPv6 ready subnet
+ *
+ */
+
+resource "aws_subnet" "main" {
+  vpc_id            = var.vpc_id
+  cidr_block        = var.cidr_block
+  ipv6_cidr_block   = cidrsubnet(var.vpc_ipv6_cidr_block, var.ipv6_newbits, var.ipv6_netsum)
+  availability_zone = var.az
+
+  tags = merge(
+    {
+      "Name" = "${var.name_prefix}-${var.az}"
+    },
+    var.extra_tags,
+  )
+
+  map_public_ip_on_launch         = var.public
+  assign_ipv6_address_on_creation = true
+}
diff --git a/modules/subnet-ipv6/output.tf b/modules/subnet-ipv6/output.tf
new file mode 100644
index 00000000..dc742dac
--- /dev/null
+++ b/modules/subnet-ipv6/output.tf
@@ -0,0 +1,25 @@
+output "id" {
+  description = "The subnet id"
+  value       = aws_subnet.main.id
+}
+
+output "cidr_block" {
+  description = "The IPv4 CIDR block"
+  value       = aws_subnet.main.cidr_block
+}
+
+output "ipv6_cidr_block" {
+  description = "The IPv6 CIDR block"
+  value       = aws_subnet.main.ipv6_cidr_block
+}
+
+output "az" {
+  value       = aws_subnet.main.availability_zone
+  description = "The availability zones of the subnet"
+}
+
+output "vpc_id" {
+  description = "ID of the VPC the subnet is in"
+  value       = var.vpc_id
+}
+
diff --git a/modules/subnet-ipv6/variables.tf b/modules/subnet-ipv6/variables.tf
new file mode 100644
index 00000000..430adf97
--- /dev/null
+++ b/modules/subnet-ipv6/variables.tf
@@ -0,0 +1,49 @@
+variable "name_prefix" {
+  description = "Name to prefix subnets with"
+  type        = string
+}
+
+variable "vpc_id" {
+  description = "VPC ID where subnets will be created"
+  type        = string
+}
+
+variable "cidr_block" {
+  description = "The IPv4 CIDR block for the subnet"
+  type        = string
+}
+
+variable "az" {
+  description = "The Availaiblity Zones to create the subnet in"
+  type        = string
+}
+
+variable "extra_tags" {
+  default     = {}
+  description = "Extra tags that will be added to aws_subnet resources"
+  type        = map(string)
+}
+
+# default to creating a public subnet
+variable "public" {
+  default     = true
+  description = "Boolean, maps to the map_public_ip_on_launch variable"
+  type        = bool
+}
+
+variable "vpc_ipv6_cidr_block" {
+  description = "The IPv6 cidr block for the vpc"
+  type        = string
+}
+
+variable "ipv6_newbits" {
+  description = "The number of additional bits with which to extend the prefix"
+  type = number
+  default = 8
+}
+
+variable "ipv6_netsum" {
+  description = "a whole number that can be represented as a binary integer with no more than newbits binary digits"
+  type = number
+  default =  162
+}
diff --git a/modules/subnet-ipv6/versions.tf b/modules/subnet-ipv6/versions.tf
new file mode 100644
index 00000000..ac97c6ac
--- /dev/null
+++ b/modules/subnet-ipv6/versions.tf
@@ -0,0 +1,4 @@
+
+terraform {
+  required_version = ">= 0.12"
+}
diff --git a/modules/subnets/variables.tf b/modules/subnets/variables.tf
index 73130302..4277c55d 100644
--- a/modules/subnets/variables.tf
+++ b/modules/subnets/variables.tf
@@ -28,6 +28,5 @@ variable "extra_tags" {
 variable "public" {
   default     = true
   description = "Boolean, maps to the map_public_ip_on_launch variable"
-  type        = string # no boolean type...
+  type        = bool
 }
-
diff --git a/modules/vpc/main.tf b/modules/vpc/main.tf
index 098b902a..1715ee08 100644
--- a/modules/vpc/main.tf
+++ b/modules/vpc/main.tf
@@ -16,6 +16,8 @@ resource "aws_vpc" "main" {
   enable_dns_hostnames = var.enable_dns_hostnames
   enable_dns_support   = var.enable_dns_support
 
+  assign_generated_ipv6_cidr_block = var.assign_generated_ipv6_cidr_block
+
   tags = merge(
     {
       "Name" = var.name_prefix
diff --git a/modules/vpc/outputs.tf b/modules/vpc/outputs.tf
index 77a216d3..1a877d73 100644
--- a/modules/vpc/outputs.tf
+++ b/modules/vpc/outputs.tf
@@ -13,3 +13,10 @@ output "dhcp_options_id" {
   description = "ID of the DHCP options resource"
 }
 
+# It would be great if Terraform had an Option or Maybe type
+# Otherwise this will output an empty default value if the IPv6 option is not
+# set to true
+output "ipv6_cidr_block" {
+  value       =  (var.assign_generated_ipv6_cidr_block ? aws_vpc.main.ipv6_cidr_block : "")
+  description = "Optional IPv6 CIDR block output for the VPC"
+}
diff --git a/modules/vpc/variables.tf b/modules/vpc/variables.tf
index 240f6c3c..51dcb6e4 100644
--- a/modules/vpc/variables.tf
+++ b/modules/vpc/variables.tf
@@ -35,6 +35,13 @@ variable "dns_servers" {
   default     = ["AmazonProvidedDNS"]
   description = "list of DNS servers for the DHCP options resource"
   type        = list(string)
+
+}
+
+variable "assign_generated_ipv6_cidr_block" {
+  description = "Whether to request an Amazon-provided IPv6 CIDR block with a /56 prefix length for the VPC"
+  type        = bool
+  default     = false
 }
 
 variable "ntp_servers" {

From aedd0c861dbb589ae62c0f89029c21fd82308ef5 Mon Sep 17 00:00:00 2001
From: Mike McGirr <mike@fpcomplete.com>
Date: Sat, 21 Mar 2020 19:00:27 -0700
Subject: [PATCH 2/2] Add initial IPv6 support to the open-egress-sg and
 route-public modules

---
 modules/open-egress-sg/main.tf | 7 +++++++
 modules/route-public/main.tf   | 7 +++++++
 2 files changed, 14 insertions(+)

diff --git a/modules/open-egress-sg/main.tf b/modules/open-egress-sg/main.tf
index 2ad2b81a..9bd926f6 100644
--- a/modules/open-egress-sg/main.tf
+++ b/modules/open-egress-sg/main.tf
@@ -18,6 +18,12 @@ variable "cidr_blocks" {
   type        = list(string)
 }
 
+variable "ipv6_cidr_blocks" {
+  description = "Allow egress to these IPv6 CIDR blocks"
+  type        = list(string)
+  default     = []
+}
+
 variable "description" {
   description = "use this string to generate a description for the SG rules"
   default     = "OPEN egress, all ports, all protocols"
@@ -32,6 +38,7 @@ resource "aws_security_group_rule" "open_egress" {
   to_port           = "0"
   protocol          = "-1"
   cidr_blocks       = var.cidr_blocks
+  ipv6_cidr_blocks  = var.ipv6_cidr_blocks
   security_group_id = var.security_group_id
 }
 
diff --git a/modules/route-public/main.tf b/modules/route-public/main.tf
index cfa2bb3a..e4dddfd1 100644
--- a/modules/route-public/main.tf
+++ b/modules/route-public/main.tf
@@ -41,3 +41,10 @@ resource "aws_route" "public" {
   depends_on             = [aws_route_table.public]
 }
 
+resource "aws_route" "public6" {
+  # TODO make this optional or control with count
+  route_table_id         = aws_route_table.public.id
+  destination_ipv6_cidr_block = "::/0"
+  gateway_id             = aws_internet_gateway.public.id
+  depends_on             = [aws_route_table.public]
+}