diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..b0b23f1
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,12 @@
+---
+name: Release
+
+on:
+ push:
+ tags:
+ - "v*"
+
+jobs:
+ release:
+ uses: appvia/appvia-cicd-workflows/.github/workflows/terraform-module-release.yml@main
+ name: GitHub Release
diff --git a/.github/workflows/terraform.yml b/.github/workflows/terraform.yml
new file mode 100644
index 0000000..c3a581e
--- /dev/null
+++ b/.github/workflows/terraform.yml
@@ -0,0 +1,16 @@
+---
+name: Terraform
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+
+jobs:
+ module-validation:
+ uses: appvia/appvia-cicd-workflows/.github/workflows/terraform-module-validation.yml@main
+ name: Module Validation
+ with:
+ working-directory: .
diff --git a/.gitignore b/.gitignore
new file mode 100755
index 0000000..5f401a8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,32 @@
+# Local .terraform directories
+**/.terraform/*
+
+# .tfstate files
+*.tfstate
+*.tfstate.*
+
+# Crash log files
+crash.log
+crash.*.log
+
+# Ignore override files as they are usually used to override resources locally and so
+# are not checked in
+override.tf
+override.tf.json
+*_override.tf
+*_override.tf.json
+
+# Include override files you do wish to add to version control using negated pattern
+# !example_override.tf
+
+# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
+# example: *tfplan*
+
+# Ignore CLI configuration files
+.terraformrc
+terraform.rc
+
+# Other
+.DS_Store
+todo.md
+
diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl
new file mode 100644
index 0000000..66a5929
--- /dev/null
+++ b/.terraform.lock.hcl
@@ -0,0 +1,48 @@
+# This file is maintained automatically by "terraform init".
+# Manual edits may be lost in future updates.
+
+provider "registry.terraform.io/hashicorp/aws" {
+ version = "5.40.0"
+ constraints = ">= 3.68.0, >= 3.72.0, >= 4.0.0, >= 4.27.0, ~> 5.0"
+ hashes = [
+ "h1:KEqMoJwLw6Z9bTO4K8nPVvQQa6YiM+bvz89Sw7tNFJw=",
+ "zh:11f177a2385703740bd26d0652d3dba08575101d7639f386ce5637bdb0e29a13",
+ "zh:203fc43e69634f1bd487a9dc24b01944dfd568beac78e491f26677d103d343ed",
+ "zh:3697ebad4929da30ea98276a85d4ce5ebfc48508f4dd149e17e1dcdc7f306c6e",
+ "zh:421e0799756587e728f75a9024b8d4e38707cd6d65cf0710cb8d189062c85a58",
+ "zh:4be2adcd4c32a66159c532908f0d425d793c814b3686832e9af549b1515ae032",
+ "zh:55778b32470212ce6bbfd402529c88e7ea6ba34b0882f85d6ea001ff5c6255a5",
+ "zh:689a4c1fd1e1d5dab7b169759389c76f25e366f19a470971674321d6fca09791",
+ "zh:68a23eda608573a053e8738894457bd0c11766bc243e68826c78ab6b5a144710",
+ "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425",
+ "zh:a1580115c22564e5752e569dc40482503de6cced44da3e9431885cd9d4bf18ea",
+ "zh:b127756d7ee513691e76c211570580c10eaa2f7a7e4fd27c3566a48ec214991c",
+ "zh:b7ccea7a759940c8dcf8726272eed6653eed0b31f7223f71e829a344627afd39",
+ "zh:bb130fc50494fd45406e04b44d242da9a8f138a4a43feb65cf9e86d13aa13629",
+ "zh:cf1c972c90d5f22c9705274a33792275e284a0a3fcac12ce4083b5a4480463f4",
+ "zh:ebe60d3887b23703ca6a4c65b15c6d7b8d93ba27a028d996d17882fe6e98d5c0",
+ ]
+}
+
+provider "registry.terraform.io/hashicorp/awscc" {
+ version = "0.71.0"
+ constraints = "~> 0.9, >= 0.15.0"
+ hashes = [
+ "h1:TrwsdnCSd6M4/akTqzzfOnd/d8p0Wo+PovC4DGj2HzM=",
+ "zh:02a5de5b2dc1fb67d461a53ec25fa543e4f5899b2222a775b87e98ee27e306a2",
+ "zh:0a32866e8386754642f8372a63612ead43089b8084b7069b63ca2096468970a6",
+ "zh:1559685aa3abf171d3cb02b82ae4d39d7bbc0291aca8e38fe45dd2c8718f24e2",
+ "zh:652414aaf2fa3f4a9ebc735d989c48b0930b1bb6a4d386111b428e572c8c9a54",
+ "zh:714969d15bda72154634c1ebe27ce9794a6a3714b86f50bfadb597d3dd5cebdb",
+ "zh:96f386267b5f74f94b4ad7e7369e0e10bf3958be44810b4250deae21f1193eb6",
+ "zh:983500993dd9c04a156e8e05cc04a8b6d9eabd4f360b36227258081527d38ebd",
+ "zh:b0fca02f10f1fc5d4ca31ad57c6ca5b5145a53f827f45a3ade7fca1387acc6b0",
+ "zh:d2107c07d6e5cd00aeea726a29749eabb8981fa1cb46f8a0bb787ebea9c45380",
+ "zh:dfe476d9175bec1bf41c7d9f7c1b40cf8704fe1ecf12ef40fef8dcbb677061d7",
+ "zh:e081719464d4b922afe14938832476c77274eaf11010a0dca48f6f83f322238e",
+ "zh:e5e9f9925059ad826593aaaf8cd58b36883ba299570798e190224b2f42c6df23",
+ "zh:ea918a3cc1a0762300f876fdb4064c46df82898ee1da8b7f38a4c04194b3e633",
+ "zh:edbeb0dc4b1f7e6919f8947e6e9edcda0bc070c311c7dac834b5c9de7e5e4564",
+ "zh:f809ab383cca0a5f83072981c64208cbd7fa67e986a86ee02dd2c82333221e32",
+ ]
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..7412022
--- /dev/null
+++ b/README.md
@@ -0,0 +1,58 @@
+
+## Requirements
+
+| Name | Version |
+|------|---------|
+| [terraform](#requirement\_terraform) | >= 1.0 |
+| [aws](#requirement\_aws) | ~> 5.0 |
+
+## Providers
+
+| Name | Version |
+|------|---------|
+| [aws](#provider\_aws) | 5.40.0 |
+
+## Modules
+
+| Name | Source | Version |
+|------|--------|---------|
+| [vpc](#module\_vpc) | aws-ia/vpc/aws | = 4.4.2 |
+
+## Resources
+
+| Name | Type |
+|------|------|
+| [aws_vpc_ipam_pool.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc_ipam_pool) | data source |
+
+## Inputs
+
+| Name | Description | Type | Default | Required |
+|------|-------------|------|---------|:--------:|
+| [availability\_zones](#input\_availability\_zones) | The number of availability zone the network should be deployed into | `number` | `2` | no |
+| [enable\_ipam](#input\_enable\_ipam) | Indicates the cidr block for the network should be assigned from IPAM | `bool` | `true` | no |
+| [enable\_nat\_gateway](#input\_enable\_nat\_gateway) | Indicates the network should provison nat gateways | `bool` | `false` | no |
+| [enable\_transit\_gateway](#input\_enable\_transit\_gateway) | Indicates the network should provison nat gateways | `bool` | `false` | no |
+| [enable\_transit\_gateway\_appliance\_mode](#input\_enable\_transit\_gateway\_appliance\_mode) | Indicates the network should be connected to a transit gateway in appliance mode | `bool` | `false` | no |
+| [name](#input\_name) | Is the name of the network to provision | `string` | n/a | yes |
+| [nat\_gateway\_mode](#input\_nat\_gateway\_mode) | The configuration mode of the NAT gateways | `string` | `"none"` | no |
+| [private\_subnet\_netmask](#input\_private\_subnet\_netmask) | The netmask for the private subnets | `number` | `17` | no |
+| [public\_subnet\_netmask](#input\_public\_subnet\_netmask) | The netmask for the public subnets | `string` | `""` | no |
+| [tags](#input\_tags) | Tags to apply to all resources | `map(string)` | n/a | yes |
+| [transit\_gateway\_id](#input\_transit\_gateway\_id) | If enabled, and not lookup is disabled, the transit gateway id to connect to | `string` | `""` | no |
+| [transit\_gateway\_route](#input\_transit\_gateway\_route) | If enabled, and not lookup is disabled, the transit gateway default routes to add | `list(string)` | `[]` | no |
+| [vpc\_netmask](#input\_vpc\_netmask) | An optional range assigned to the VPC | `number` | `0` | no |
+
+## Outputs
+
+| Name | Description |
+|------|-------------|
+| [nat\_public\_ips](#output\_nat\_public\_ips) | The public IPs of the NAT Gateways |
+| [private\_route\_table\_ids](#output\_private\_route\_table\_ids) | The IDs of the private route tables |
+| [private\_subnet\_ids](#output\_private\_subnet\_ids) | The IDs of the private subnets |
+| [private\_subnet\_netmask](#output\_private\_subnet\_netmask) | The netmask for the private subnets |
+| [public\_subnet\_ids](#output\_public\_subnet\_ids) | The IDs of the public subnets |
+| [public\_subnet\_netmask](#output\_public\_subnet\_netmask) | The netmask for the public subnets |
+| [transit\_gateway\_attachment\_id](#output\_transit\_gateway\_attachment\_id) | The ID of the transit gateway attachment |
+| [transit\_subnet\_ids](#output\_transit\_subnet\_ids) | The IDs of the transit gateway subnets |
+| [vpc\_id](#output\_vpc\_id) | The ID of the VPC |
+
\ No newline at end of file
diff --git a/main.tf b/main.tf
new file mode 100644
index 0000000..260a288
--- /dev/null
+++ b/main.tf
@@ -0,0 +1,93 @@
+#
+## Provisions a network within an account
+#
+
+locals {
+ # Is the IPAM pool id
+ ipam_pool_id = var.enable_ipam ? data.aws_vpc_ipam_pool.current[0].id : null
+
+ # The id for the transit_gateway_id passed into the module
+ transit_gateway_id = var.enable_transit_gateway ? var.transit_gateway_id : null
+
+ # Is the routes to propagate down the transit gateway
+ transit_routes = var.enable_transit_gateway && length(var.transit_gateway_route) > 0 ? {
+ private = var.transit_gateway_route
+ } : null
+
+ # The configuration for the private subnets
+ private_subnet = {
+ private = {
+ connect_to_public_natgw = var.enable_nat_gateway ? true : null
+ netmask = var.private_subnet_netmask
+ tags = var.tags
+ }
+ }
+
+ # Public subnets are optional
+ public_subnet = length(var.public_subnet_netmask) > 0 ? {
+ public = {
+ connect_to_public_natgw = var.enable_nat_gateway ? true : null
+ nat_gateway_configuration = var.nat_gateway_mode
+ netmask = var.public_subnet_netmask
+ tags = var.tags
+ }
+ } : null
+
+
+ # Configuration for the transit subnets
+ transit_subnet = var.enable_transit_gateway ? {
+ transit_gateway = {
+ connect_to_public_natgw = false
+ netmask = 28
+ tags = var.tags
+ transit_gateway_appliance_mode_support = var.enable_transit_gateway_appliance_mode ? "enable" : "disable"
+ transit_gateway_default_route_table_association = true
+ transit_gateway_default_route_table_propagation = true
+ transit_gateway_dns_support = "enable"
+ }
+ } : null
+
+ # The subnet id for the private subnets
+ private_subnet_ids = [for k, x in module.vpc.private_subnet_attributes_by_az : x.id if startswith(k, "private/")]
+ # The subnet id for the public subnets
+ public_subnet_ids = [for k, x in module.vpc.public_subnet_attributes_by_az : x.id]
+ # The subnet id for the transit subnets
+ transit_subnet_ids = [for k, x in module.vpc.tgw_subnet_attributes_by_az : x.id]
+ # The routing tables for the private subnets
+ private_route_table_ids = [for k, x in module.vpc.rt_attributes_by_type_by_az.private : x.id]
+
+ subnets = merge(
+ local.private_subnet,
+ local.public_subnet,
+ local.transit_subnet,
+ )
+}
+
+#
+## Lookup the IPAM by protocol
+#
+data "aws_vpc_ipam_pool" "current" {
+ count = var.enable_ipam ? 1 : 0
+
+ filter {
+ name = "address-family"
+ values = ["ipv4"]
+ }
+}
+
+#
+## Provision the VPC for VPN
+#
+module "vpc" {
+ source = "aws-ia/vpc/aws"
+ version = "= 4.4.2"
+
+ name = var.name
+ az_count = var.availability_zones
+ tags = var.tags
+ vpc_ipv4_ipam_pool_id = local.ipam_pool_id
+ vpc_ipv4_netmask_length = var.vpc_netmask
+ transit_gateway_id = local.transit_gateway_id
+ transit_gateway_routes = local.transit_routes
+ subnets = local.subnets
+}
diff --git a/outputs.tf b/outputs.tf
new file mode 100644
index 0000000..e31a619
--- /dev/null
+++ b/outputs.tf
@@ -0,0 +1,49 @@
+#
+## Related to the outputs of the module
+#
+
+output "vpc_id" {
+ description = "The ID of the VPC"
+ value = module.vpc.vpc_attributes.id
+}
+
+output "private_subnet_netmask" {
+ description = "The netmask for the private subnets"
+ value = var.private_subnet_netmask
+}
+
+output "public_subnet_netmask" {
+ description = "The netmask for the public subnets"
+ value = var.public_subnet_netmask
+}
+
+output "private_subnet_ids" {
+ description = "The IDs of the private subnets"
+ value = local.private_subnet_ids
+}
+
+output "public_subnet_ids" {
+ description = "The IDs of the public subnets"
+ value = local.public_subnet_ids
+}
+
+output "private_route_table_ids" {
+ description = "The IDs of the private route tables"
+ value = local.private_route_table_ids
+}
+
+output "transit_gateway_attachment_id" {
+ description = "The ID of the transit gateway attachment"
+ value = var.enable_transit_gateway ? module.vpc.transit_gateway_attachment_id : null
+}
+
+output "transit_subnet_ids" {
+ description = "The IDs of the transit gateway subnets"
+ value = var.enable_transit_gateway ? local.transit_subnet_ids : null
+}
+
+output "nat_public_ips" {
+ description = "The public IPs of the NAT Gateways"
+ value = var.enable_nat_gateway ? [] : [for x in module.vpc.nat_gateway_attributes_by_az : x.public_ip]
+}
+
diff --git a/variables.tf b/variables.tf
new file mode 100644
index 0000000..41b5d98
--- /dev/null
+++ b/variables.tf
@@ -0,0 +1,96 @@
+variable "availability_zones" {
+ description = "The number of availability zone the network should be deployed into"
+ type = number
+ default = 2
+}
+
+variable "enable_ipam" {
+ description = "Indicates the cidr block for the network should be assigned from IPAM"
+ type = bool
+ default = true
+}
+
+variable "enable_nat_gateway" {
+ description = "Indicates the network should provison nat gateways"
+ type = bool
+ default = false
+}
+
+variable "enable_transit_gateway" {
+ description = "Indicates the network should provison nat gateways"
+ type = bool
+ default = false
+}
+
+variable "enable_transit_gateway_appliance_mode" {
+ description = "Indicates the network should be connected to a transit gateway in appliance mode"
+ type = bool
+ default = false
+}
+
+variable "name" {
+ description = "Is the name of the network to provision"
+ type = string
+
+ validation {
+ condition = length(var.name) > 0
+ error_message = "name must be a non-empty string"
+ }
+
+ validation {
+ condition = length(var.name) <= 10
+ error_message = "name must not be longer than 10 characters"
+ }
+}
+
+variable "nat_gateway_mode" {
+ description = "The configuration mode of the NAT gateways"
+ type = string
+ default = "none"
+
+ # Must be none, all_az, or single_az
+ validation {
+ condition = can(regex("^(none|all_az|single_az)$", var.nat_gateway_mode))
+ error_message = "nat_gateway_mode must be non, all_az, or single_az"
+ }
+}
+
+variable "private_subnet_netmask" {
+ description = "The netmask for the private subnets"
+ type = number
+ default = 17
+
+ validation {
+ condition = var.private_subnet_netmask > 0 && var.private_subnet_netmask <= 28
+ error_message = "private_subnet_netmask must be between 1 and 28"
+ }
+}
+
+variable "public_subnet_netmask" {
+ description = "The netmask for the public subnets"
+ type = string
+ default = ""
+}
+
+variable "tags" {
+ description = "Tags to apply to all resources"
+ type = map(string)
+}
+
+variable "transit_gateway_id" {
+ description = "If enabled, and not lookup is disabled, the transit gateway id to connect to"
+ type = string
+ default = ""
+}
+
+variable "transit_gateway_route" {
+ description = "If enabled, and not lookup is disabled, the transit gateway default routes to add"
+ type = list(string)
+ default = []
+}
+
+variable "vpc_netmask" {
+ description = "An optional range assigned to the VPC"
+ type = number
+ default = 0
+}
diff --git a/versions.tf b/versions.tf
new file mode 100644
index 0000000..a8de733
--- /dev/null
+++ b/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+ required_version = ">= 1.0"
+
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = "~> 5.0"
+ }
+ }
+}