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" + } + } +}