├── examples ├── advanced │ ├── outputs.tf │ ├── variables.tf │ ├── providers.tf │ ├── .header.md │ ├── main.tf │ └── README.md ├── basic │ ├── outputs.tf │ ├── variables.tf │ ├── providers.tf │ ├── .header.md │ ├── main.tf │ └── README.md ├── cloud_wan │ ├── outputs.tf │ ├── variables.tf │ ├── providers.tf │ ├── .header.md │ ├── cwan_policy.tf │ ├── README.md │ └── main.tf ├── ipam │ ├── outputs.tf │ ├── variables.tf │ ├── .header.md │ ├── providers.tf │ ├── main.tf │ └── README.md ├── vpc_lattice │ ├── outputs.tf │ ├── variables.tf │ ├── providers.tf │ ├── .header.md │ ├── main.tf │ └── README.md └── transit_gateway │ ├── outputs.tf │ ├── providers.tf │ ├── variables.tf │ ├── .header.md │ ├── main.tf │ └── README.md ├── CODEOWNERS ├── tests ├── examples_ipam.tftest.hcl ├── examples_basic.tftest.hcl ├── examples_advanced.tftest.hcl ├── examples_cloud_wan.tftest.hcl ├── examples_vpc_lattice.tftest.hcl └── examples_transit_gateway.tftest.hcl ├── modules ├── flow_logs │ ├── outputs.tf │ ├── providers.tf │ ├── modules │ │ └── s3_log_bucket │ │ │ ├── outputs.tf │ │ │ ├── providers.tf │ │ │ ├── variables.tf │ │ │ └── main.tf │ ├── variables.tf │ └── main.tf ├── calculate_subnets │ ├── providers.tf │ ├── outputs.tf │ ├── variables.tf │ └── main.tf └── calculate_subnets_ipv6 │ ├── outputs.tf │ ├── providers.tf │ ├── variables.tf │ └── main.tf ├── providers.tf ├── .pre-commit-config.yaml ├── .terraform-docs.yaml ├── NOTICE.txt ├── moved_block_rendering ├── moved.tf.j2 ├── main.py └── README.md ├── .gitignore ├── .tflint.hcl ├── contributing.md ├── docs ├── UPGRADE-GUIDE-4.0.md ├── UPGRADE-GUIDE-3.0.md └── How-to-use-module-outputs.md ├── outputs.tf ├── data.tf ├── .header.md ├── LICENSE ├── variables.tf ├── main.tf └── README.md /examples/advanced/outputs.tf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/basic/outputs.tf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/cloud_wan/outputs.tf: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/ipam/outputs.tf: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/vpc_lattice/outputs.tf: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/transit_gateway/outputs.tf: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @aws-ia/aws-ia-terraform-core 2 | -------------------------------------------------------------------------------- /tests/examples_ipam.tftest.hcl: -------------------------------------------------------------------------------- 1 | run "validate" { 2 | command = apply 3 | module { 4 | source = "./examples/ipam" 5 | } 6 | } -------------------------------------------------------------------------------- /tests/examples_basic.tftest.hcl: -------------------------------------------------------------------------------- 1 | run "validate" { 2 | command = apply 3 | module { 4 | source = "./examples/basic" 5 | } 6 | } -------------------------------------------------------------------------------- /modules/flow_logs/outputs.tf: -------------------------------------------------------------------------------- 1 | output "flow_log" { 2 | description = "Flow Log information." 3 | value = aws_flow_log.main 4 | } 5 | -------------------------------------------------------------------------------- /tests/examples_advanced.tftest.hcl: -------------------------------------------------------------------------------- 1 | run "validate" { 2 | command = apply 3 | module { 4 | source = "./examples/advanced" 5 | } 6 | } -------------------------------------------------------------------------------- /tests/examples_cloud_wan.tftest.hcl: -------------------------------------------------------------------------------- 1 | run "validate" { 2 | command = apply 3 | module { 4 | source = "./examples/cloud_wan" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tests/examples_vpc_lattice.tftest.hcl: -------------------------------------------------------------------------------- 1 | run "validate" { 2 | command = apply 3 | module { 4 | source = "./examples/vpc_lattice" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/advanced/variables.tf: -------------------------------------------------------------------------------- 1 | 2 | variable "aws_region" { 3 | type = string 4 | description = "AWS Region." 5 | 6 | default = "eu-west-1" 7 | } -------------------------------------------------------------------------------- /examples/basic/variables.tf: -------------------------------------------------------------------------------- 1 | 2 | variable "aws_region" { 3 | type = string 4 | description = "AWS Region." 5 | 6 | default = "eu-west-1" 7 | } -------------------------------------------------------------------------------- /examples/ipam/variables.tf: -------------------------------------------------------------------------------- 1 | 2 | variable "aws_region" { 3 | type = string 4 | description = "AWS Region." 5 | 6 | default = "eu-west-1" 7 | } -------------------------------------------------------------------------------- /examples/vpc_lattice/variables.tf: -------------------------------------------------------------------------------- 1 | 2 | variable "aws_region" { 3 | description = "AWS Region." 4 | type = string 5 | 6 | default = "eu-west-1" 7 | } -------------------------------------------------------------------------------- /tests/examples_transit_gateway.tftest.hcl: -------------------------------------------------------------------------------- 1 | run "validate" { 2 | command = apply 3 | module { 4 | source = "./examples/transit_gateway" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = ">= 5.0.0" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /modules/flow_logs/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = ">= 3.72.0" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /modules/calculate_subnets/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = ">= 3.72.0" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /modules/flow_logs/modules/s3_log_bucket/outputs.tf: -------------------------------------------------------------------------------- 1 | output "bucket_flow_logs_attributes" { 2 | value = aws_s3_bucket.flow_logs 3 | description = "Flow Logs S3 Bucket resource attributes. Full output of aws_s3_bucket." 4 | } 5 | -------------------------------------------------------------------------------- /modules/flow_logs/modules/s3_log_bucket/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 0.15.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = ">= 4.0.0" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/ipam/.header.md: -------------------------------------------------------------------------------- 1 | # Create VPC with a CIDR from AWS IPAM 2 | 3 | This example builds a VPC with a CIDR block from AWS IPAM. It builds public and private subnets in 3 availability zones, creates a nat gateway in each AZ and appropriately routes from each private to the nat gateway. 4 | -------------------------------------------------------------------------------- /examples/basic/providers.tf: -------------------------------------------------------------------------------- 1 | 2 | terraform { 3 | required_version = ">= 1.3.0" 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 5.0.0" 8 | } 9 | } 10 | } 11 | 12 | # Provider definition 13 | provider "aws" { 14 | region = var.aws_region 15 | } -------------------------------------------------------------------------------- /examples/ipam/providers.tf: -------------------------------------------------------------------------------- 1 | 2 | terraform { 3 | required_version = ">= 1.3.0" 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 5.0.0" 8 | } 9 | } 10 | } 11 | 12 | # Provider definition 13 | provider "aws" { 14 | region = var.aws_region 15 | } -------------------------------------------------------------------------------- /modules/calculate_subnets_ipv6/outputs.tf: -------------------------------------------------------------------------------- 1 | output "subnets_ipv6" { 2 | description = "Outputs subnets prefixes by type (private, public). Derived from split(var.separator, )." 3 | value = merge(try(local.explicit_cidrs_grouped, {}), try(module.subnet_calculator[0].grouped_by_separator, {})) 4 | } -------------------------------------------------------------------------------- /examples/advanced/providers.tf: -------------------------------------------------------------------------------- 1 | 2 | terraform { 3 | required_version = ">= 1.3.0" 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 5.0.0" 8 | } 9 | } 10 | } 11 | 12 | # Provider definition 13 | provider "aws" { 14 | region = var.aws_region 15 | } -------------------------------------------------------------------------------- /examples/cloud_wan/variables.tf: -------------------------------------------------------------------------------- 1 | 2 | variable "aws_regions" { 3 | description = "AWS Regions to create in Cloud WAN's core network." 4 | type = object({ 5 | nvirginia = string 6 | ireland = string 7 | }) 8 | 9 | default = { 10 | nvirginia = "us-east-1" 11 | ireland = "eu-west-1" 12 | } 13 | } -------------------------------------------------------------------------------- /examples/transit_gateway/providers.tf: -------------------------------------------------------------------------------- 1 | 2 | terraform { 3 | required_version = ">= 1.3.0" 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 5.0.0" 8 | } 9 | } 10 | } 11 | 12 | # Provider definition 13 | provider "aws" { 14 | region = var.aws_region 15 | } 16 | 17 | -------------------------------------------------------------------------------- /examples/vpc_lattice/providers.tf: -------------------------------------------------------------------------------- 1 | 2 | terraform { 3 | required_version = ">= 1.3.0" 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 5.0.0" 8 | } 9 | } 10 | } 11 | 12 | # Provider definition 13 | provider "aws" { 14 | region = var.aws_region 15 | } 16 | 17 | -------------------------------------------------------------------------------- /modules/calculate_subnets_ipv6/providers.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = ">= 1.3.0" 3 | required_providers { 4 | aws = { 5 | source = "hashicorp/aws" 6 | version = ">= 3.72.0" 7 | } 8 | awscc = { 9 | source = "hashicorp/awscc" 10 | version = ">= 0.15.0" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /modules/flow_logs/modules/s3_log_bucket/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | type = string 3 | description = "(optional) describe your variable" 4 | } 5 | 6 | variable "lifecycle_filter_prefix" { 7 | description = "Prefix to use for the lifecycle transition rule" 8 | type = string 9 | default = "" 10 | } 11 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | fail_fast: false 4 | minimum_pre_commit_version: "2.6.0" 5 | 6 | repos: 7 | - repo: https://github.com/aws-ia/pre-commit-configs 8 | # To update run: 9 | # pre-commit autoupdate --freeze 10 | rev: b3e647e360f04623c6c582c12245fc92e20cc2e8 # frozen: v1.6.3 11 | hooks: 12 | - id: aws-ia-meta-hook 13 | -------------------------------------------------------------------------------- /examples/transit_gateway/variables.tf: -------------------------------------------------------------------------------- 1 | 2 | variable "aws_region" { 3 | description = "AWS Region." 4 | type = string 5 | 6 | default = "eu-west-1" 7 | } 8 | 9 | variable "prefixes" { 10 | type = map(string) 11 | description = "IPv4 prefixes." 12 | 13 | default = { 14 | primary = "10.0.0.0/8", 15 | internal = "192.168.0.0/16" 16 | } 17 | } -------------------------------------------------------------------------------- /.terraform-docs.yaml: -------------------------------------------------------------------------------- 1 | formatter: markdown 2 | header-from: .header.md 3 | settings: 4 | anchor: true 5 | color: true 6 | default: true 7 | escape: true 8 | html: true 9 | indent: 2 10 | required: true 11 | sensitive: true 12 | type: true 13 | lockfile: false 14 | 15 | sort: 16 | enabled: true 17 | by: required 18 | 19 | output: 20 | file: README.md 21 | mode: replace 22 | -------------------------------------------------------------------------------- /modules/calculate_subnets/outputs.tf: -------------------------------------------------------------------------------- 1 | output "subnets_by_type" { 2 | description = "Outputs subnets prefixes by type (private, public). Derived from split(var.separator, )." 3 | value = merge(try(local.explicit_cidrs_grouped, {}), try(module.subnet_calculator[0].grouped_by_separator, {})) 4 | } 5 | 6 | output "subnets_with_ipv6_native" { 7 | description = "Outputs types of subnets that are ipv6_native." 8 | value = local.types_ipv6_native 9 | } -------------------------------------------------------------------------------- /examples/cloud_wan/providers.tf: -------------------------------------------------------------------------------- 1 | 2 | terraform { 3 | required_version = ">= 1.3.0" 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = ">= 5.22.0" 8 | } 9 | } 10 | } 11 | 12 | # Provider definition for N. Virginia Region 13 | provider "aws" { 14 | region = var.aws_regions.nvirginia 15 | alias = "awsnvirginia" 16 | } 17 | 18 | # Provider definition for Ireland Region 19 | provider "aws" { 20 | region = var.aws_regions.ireland 21 | alias = "awsireland" 22 | } 23 | 24 | -------------------------------------------------------------------------------- /examples/vpc_lattice/.header.md: -------------------------------------------------------------------------------- 1 | # VPC module - Example: Amazon VPC Lattice 2 | 3 | This example shows how you can use this module to create an [Amazon VPC Lattice](https://aws.amazon.com/vpc/lattice/) Service Network VPC association. The example creates: 4 | 5 | * VPC Lattice Service Network. 6 | * Security Group (allowing HTTP and HTTPS traffic from the local CIDR). Used to attach it to the VPC Lattice association 7 | * The VPC module creates the following: 8 | * One set of subnets (*workload*) 9 | * VPC Lattice Service Network VPC association. -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2016-2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance with the License. A copy of the License is located at 4 | 5 | http://aws.amazon.com/apache2.0/ 6 | 7 | or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 8 | -------------------------------------------------------------------------------- /modules/calculate_subnets_ipv6/variables.tf: -------------------------------------------------------------------------------- 1 | variable "subnets" { 2 | description = "Definition of subnets to be built. If `netmask` is passed will calculate CIDR. Else `cidrs` list is ziped to var.azs and merged into final output to be built into aws_subnet(s)." 3 | type = any 4 | # validation happening on root module 5 | } 6 | variable "azs" { 7 | description = "List of AZs to build. AZ is appened to each IP address prefix name." 8 | type = list(string) 9 | } 10 | 11 | variable "cidr_ipv6" { 12 | description = "CIDR value to use as base for calculating IP address prefixes." 13 | type = string 14 | } -------------------------------------------------------------------------------- /examples/basic/.header.md: -------------------------------------------------------------------------------- 1 | # VPC module - Example: Basic VPC 2 | 3 | This example builds an Amazon VPC with basic functionality: 4 | 5 | * Dual-stack VPC (IPv4 & IPv6) 6 | * Egress-only Internet gateway configured. 7 | * 4 VPC subnets - 1 public (dual-stack), 3 private (IPv4-only, dual-stack, and IPv6-only) 8 | * NAT gateways placed in all the public subnets. 9 | * Flow logs enabled (destination Amazon CloudWatch) 10 | * Routing: 11 | * IPv4 egress enabled in public subnets (through Internet gateway) and private subnets (through NAT gateways) 12 | * IPv6 egress enabled in private subnets (through Egress-only Internet gateway) -------------------------------------------------------------------------------- /examples/transit_gateway/.header.md: -------------------------------------------------------------------------------- 1 | # VPC module - Example: AWS Transit Gateway connectivity 2 | 3 | This example shows how you can use this module to crete a AWS Transit Gateway VPC attachment. This examples creates the following: 4 | 5 | * AWS Transit Gateway. 6 | * IPv4 managed prefix list with two entries. 7 | * The VPC module creates the following: 8 | * Three sets of subnets (*private_ipv4*, *private_dualstack*, and *transit_gateway*) 9 | * Transit Gateway VPC attachment. 10 | * Routing to Transit Gateway attachment: 11 | * IPv4 routes from *private_ipv4*, and *private_dualstack*. 12 | * IPv6 routes from *private_dualstack*. -------------------------------------------------------------------------------- /examples/advanced/.header.md: -------------------------------------------------------------------------------- 1 | # VPC module - Example: Advanced VPC 2 | 3 | This example builds an Amazon VPC with advanced functionality: 4 | 5 | * IPv4-only VPC. 6 | * NAT gateway configured only in 1 AZ. 7 | * 4 VPC subnets - 1 public (dual-stack), 3 private (IPv4-only, dual-stack, and IPv6-only) 8 | * Subnet CIDRs calculated to optimize IPv4 adress space (variable `optimize_subnet_cidr_ranges`) 9 | * Flow logs enabled (destination Amazon S3) 10 | * Secondary IPv4 CIDR block. 11 | * Routing - egress traffic through NAT gateways in private subnets (check also the configuration on NAT gateway routing when building the subnets with secondary CIDR block). -------------------------------------------------------------------------------- /examples/ipam/main.tf: -------------------------------------------------------------------------------- 1 | 2 | # ---------- AMAZON VPC IPAM ---------- 3 | module "ipam" { 4 | source = "aws-ia/ipam/aws" 5 | version = ">= 2.0.0" 6 | 7 | top_cidr = ["172.0.0.0/8"] 8 | 9 | pool_configurations = { 10 | (var.aws_region) = { 11 | description = "${var.aws_region} top level pool" 12 | cidr = ["172.2.0.0/16"] 13 | locale = var.aws_region 14 | } 15 | } 16 | } 17 | 18 | # ---------- AMAZON VPC ---------- 19 | module "vpc" { 20 | source = "../.." 21 | 22 | name = "ipam-example-vpc" 23 | az_count = 3 24 | 25 | vpc_ipv4_ipam_pool_id = module.ipam.pools_level_1.eu-west-1.id 26 | vpc_ipv4_netmask_length = 26 27 | 28 | subnets = { 29 | private = { netmask = 28 } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/cloud_wan/.header.md: -------------------------------------------------------------------------------- 1 | # VPC module - Example: AWS Cloud WAN connectivity 2 | 3 | This example shows how you can use this module with `core_network` subnets, and AWS Cloud WAN's VPC attachment. This examples creates the following: 4 | 5 | * Global Network and Core Network. 6 | * Core Network's policy (in `cwan_policy.tf`), creating two segments (prod and nonprod) in two AWS Regions (*us-east-1* and *eu-west-1*). The *prod* segments needs acceptance for the attachments. 7 | * The VPC module creates the following (in two AWS Regions): 8 | * Two sets of subnets (workloads and core_network) 9 | * Cloud WAN's VPC attachment - with attachment acceptance for the VPC to associate to the *prod* segment. 10 | * Routing to Core Network (0.0.0.0/0 & ::/0) in workload subnets. -------------------------------------------------------------------------------- /modules/flow_logs/variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | description = "Name to give the VPC Flow Logs and optional resources." 3 | type = string 4 | } 5 | 6 | variable "flow_log_definition" { 7 | description = "Definition of the Flow Logs (FL) to create. Can define pre-existing log_destination / iam_role_arn or theyll be created, default is Cloud Watch." 8 | type = any 9 | } 10 | 11 | variable "vpc_id" { 12 | description = "VPC ID to create flow logs for." 13 | type = string 14 | } 15 | 16 | variable "tags" { 17 | description = "Tags." 18 | type = map(string) 19 | default = null 20 | } 21 | 22 | variable "log_bucket_lifecycle_filter_prefix" { 23 | description = "Prefix to use for the lifecycle transition rule in the flowlogs bucket" 24 | default = null 25 | } 26 | -------------------------------------------------------------------------------- /modules/calculate_subnets/variables.tf: -------------------------------------------------------------------------------- 1 | variable "subnets" { 2 | description = "Defition of subnets to be built. If `netmask` is passed will calculate CIDR. Else `cidrs` list is ziped to var.azs and merged into final output to be built into aws_subnet(s)." 3 | type = any 4 | # validation happening on root module 5 | } 6 | variable "azs" { 7 | description = "List of AZs to build. AZ is appened to each IP address prefix name." 8 | type = list(string) 9 | } 10 | 11 | variable "cidr" { 12 | description = "CIDR value to use as base for calculating IP address prefixes." 13 | type = string 14 | } 15 | 16 | variable "optimize_subnet_cidr_ranges" { 17 | description = "Sort subnets to calculate by their netmask to efficiently use IP space." 18 | type = bool 19 | default = false 20 | } 21 | -------------------------------------------------------------------------------- /moved_block_rendering/moved.tf.j2: -------------------------------------------------------------------------------- 1 | {%- for az in azs %} 2 | 3 | # moved blocks for private subnet in az {{ az }} 4 | 5 | moved { 6 | from = aws_subnet.private["{{az}}"] 7 | to = aws_subnet.private["private/{{az}}"] 8 | } 9 | 10 | moved { 11 | from = awscc_ec2_subnet_route_table_association.private["{{az}}"] 12 | to = awscc_ec2_subnet_route_table_association.private["private/{{az}}"] 13 | } 14 | 15 | moved { 16 | from = awscc_ec2_route_table.private["{{az}}"] 17 | to = awscc_ec2_route_table.private["private/{{az}}"] 18 | } 19 | 20 | moved { 21 | from = aws_route.private_to_nat["{{az}}"] 22 | to = aws_route.private_to_nat["private/{{az}}"] 23 | } 24 | 25 | moved { 26 | from = aws_route.private_to_tgw["{{az}}"] 27 | to = aws_route.private_to_tgw["private/{{az}}"] 28 | } 29 | 30 | 31 | {%- endfor %} 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | plan.out 3 | plan.out.json 4 | 5 | # Local .terraform directories 6 | .terraform/ 7 | 8 | # .tfstate files 9 | *.tfstate 10 | *.tfstate.* 11 | 12 | # Crash log files 13 | crash.log 14 | 15 | # Exclude all .tfvars files, which are likely to contain sentitive data, such as 16 | # password, private keys, and other secrets. These should not be part of version 17 | # control as they are data points which are potentially sensitive and subject 18 | # to change depending on the environment. 19 | # 20 | *.tfvars 21 | 22 | # Ignore override files as they are usually used to override resources locally and so 23 | # are not checked in 24 | override.tf 25 | override.tf.json 26 | *_override.tf 27 | *_override.tf.json 28 | 29 | # Include override files you do wish to add to version control using negated pattern 30 | # 31 | # !example_override.tf 32 | 33 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 34 | # example: *tfplan* 35 | 36 | # Ignore CLI configuration files 37 | .terraformrc 38 | terraform.rc 39 | .terraform.lock.hcl 40 | 41 | go.mod 42 | go.sum 43 | 44 | tftest 45 | -------------------------------------------------------------------------------- /examples/basic/main.tf: -------------------------------------------------------------------------------- 1 | 2 | # ---------- AMAZON VPC ---------- 3 | module "vpc" { 4 | source = "../.." 5 | 6 | name = "basic-example-vpc" 7 | cidr_block = "10.0.0.0/16" 8 | az_count = 2 9 | 10 | vpc_assign_generated_ipv6_cidr_block = true 11 | vpc_egress_only_internet_gateway = true 12 | 13 | subnets = { 14 | public = { 15 | netmask = 24 16 | nat_gateway_configuration = "all_azs" 17 | assign_ipv6_cidr = true 18 | } 19 | private_ipv4_only = { 20 | netmask = 24 21 | connect_to_public_natgw = true 22 | } 23 | private_dualstack = { 24 | netmask = 24 25 | connect_to_public_natgw = true 26 | assign_ipv6_cidr = true 27 | connect_to_eigw = true 28 | } 29 | private_ipv6_only = { 30 | assign_ipv6_cidr = true 31 | ipv6_native = true 32 | connect_to_eigw = true 33 | } 34 | } 35 | 36 | vpc_flow_logs = { 37 | name_override = "basic-vpc-flowlogs" 38 | log_destination_type = "cloud-watch-logs" 39 | retention_in_days = 7 40 | } 41 | } -------------------------------------------------------------------------------- /examples/cloud_wan/cwan_policy.tf: -------------------------------------------------------------------------------- 1 | 2 | data "aws_networkmanager_core_network_policy_document" "policy" { 3 | core_network_configuration { 4 | vpn_ecmp_support = true 5 | asn_ranges = ["64515-64520"] 6 | 7 | edge_locations { 8 | location = var.aws_regions.nvirginia 9 | asn = 64515 10 | } 11 | 12 | edge_locations { 13 | location = var.aws_regions.ireland 14 | asn = 64516 15 | } 16 | } 17 | 18 | segments { 19 | name = "prod" 20 | description = "Segment for production traffic" 21 | require_attachment_acceptance = false 22 | } 23 | 24 | segments { 25 | name = "nonprod" 26 | description = "Segment for non-production traffic" 27 | require_attachment_acceptance = false 28 | } 29 | 30 | attachment_policies { 31 | rule_number = 100 32 | condition_logic = "or" 33 | 34 | conditions { 35 | type = "tag-exists" 36 | key = "env" 37 | } 38 | action { 39 | association_method = "tag" 40 | tag_value_of_key = "env" 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /moved_block_rendering/main.py: -------------------------------------------------------------------------------- 1 | # Generates a moved block to remap from singular private subnets to allow for multiple private subnets 2 | 3 | import os 4 | import boto3 5 | import argparse 6 | from jinja2 import Template 7 | from ruamel.yaml import YAML 8 | yaml=YAML() 9 | 10 | default_client = boto3.client('ec2') 11 | 12 | def get_azs(client): 13 | return [az['ZoneName'] for az in client.describe_availability_zones()['AvailabilityZones']] 14 | 15 | def generate_tf(azs): 16 | with open('moved.tf.j2', 'r') as f: 17 | try: 18 | j2_modules_template = Template(f.read()) 19 | except: 20 | print('Could not load template.') 21 | 22 | moved_file = j2_modules_template.render({'azs':azs}) 23 | 24 | with open('../moved.tf', 'w') as o: 25 | o.write(moved_file) 26 | 27 | 28 | if __name__ == "__main__": 29 | regions = [ r['RegionName'] for r in default_client.describe_regions()['Regions'] ] 30 | regional_clients = { region : boto3.client('ec2', region_name=region) for region in regions } 31 | all_azs = [] 32 | for _, client in regional_clients.items(): 33 | all_azs.extend(get_azs(client)) 34 | 35 | generate_tf(all_azs) 36 | -------------------------------------------------------------------------------- /examples/ipam/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Create VPC with a CIDR from AWS IPAM 3 | 4 | This example builds a VPC with a CIDR block from AWS IPAM. It builds public and private subnets in 3 availability zones, creates a nat gateway in each AZ and appropriately routes from each private to the nat gateway. 5 | 6 | ## Requirements 7 | 8 | | Name | Version | 9 | |------|---------| 10 | | [terraform](#requirement\_terraform) | >= 1.3.0 | 11 | | [aws](#requirement\_aws) | >= 5.0.0 | 12 | 13 | ## Providers 14 | 15 | No providers. 16 | 17 | ## Modules 18 | 19 | | Name | Source | Version | 20 | |------|--------|---------| 21 | | [ipam](#module\_ipam) | aws-ia/ipam/aws | >= 2.0.0 | 22 | | [vpc](#module\_vpc) | ../.. | n/a | 23 | 24 | ## Resources 25 | 26 | No resources. 27 | 28 | ## Inputs 29 | 30 | | Name | Description | Type | Default | Required | 31 | |------|-------------|------|---------|:--------:| 32 | | [aws\_region](#input\_aws\_region) | AWS Region. | `string` | `"eu-west-1"` | no | 33 | 34 | ## Outputs 35 | 36 | No outputs. 37 | -------------------------------------------------------------------------------- /modules/calculate_subnets_ipv6/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | # group subnets by type and create names for each type 3 | type_grouped_named_subnets_to_build = { for name, subnet_definition in var.subnets : name => [for _, az in var.azs : "${name}/${az}"] } 4 | # which network groups require calculating subnet 5 | types_to_calculate = [for type, subnet_definition in var.subnets : type if can(subnet_definition.assign_ipv6_cidr)] 6 | # network groups that are set explicitly 7 | types_with_explicit = [for type, subnet_definition in var.subnets : type if can(subnet_definition.ipv6_cidrs)] 8 | 9 | # network object to pass to calculating module 10 | calculated_subnet_objects = flatten([for _, type in local.types_to_calculate : [for _, v in local.type_grouped_named_subnets_to_build[type] : { 11 | "name" = v 12 | "netmask" = 64 13 | } 14 | ]]) 15 | 16 | # map of explicit cidrs to az 17 | explicit_cidrs_grouped = { for _, type in local.types_with_explicit : type => zipmap(var.azs, var.subnets[type].ipv6_cidrs[*]) } 18 | } 19 | 20 | module "subnet_calculator" { 21 | count = length(local.types_to_calculate) == 0 ? 0 : 1 22 | 23 | source = "drewmullen/subnets/cidr" 24 | version = "1.0.2" 25 | 26 | base_cidr_block = var.cidr_ipv6 27 | networks = local.calculated_subnet_objects 28 | } 29 | -------------------------------------------------------------------------------- /moved_block_rendering/README.md: -------------------------------------------------------------------------------- 1 | # Notice 2 | 3 | These files were used for the v2->v3 upgrade. Please disregard 4 | 5 | # Moved Resources 6 | 7 | While upgrading this module to allow for multiple private subnets, we had to adjust how we name private subnet related resources. To ease user pain we created the moved.tf file which creates corresponding entries for each private resource. However, we were unable to update `aws_route.private_to_tgw` because we used the user provided CIDR in the key. If you see a `CREATE/DELETE` for these resources you can either allow TF to force create the new routes (momentary blip in traffic) or you can manully update the name, example below: 8 | 9 | If you see a message in your plan like this: 10 | 11 | > module.vpc.aws_route.private_to_tgw["us-east-1a:10.0.0.0/8"] -> ["private/us-east-1a"] 12 | 13 | Resolve with this command per AZ 14 | ```shell 15 | terraform state mv 'module.vpc.aws_route.private_to_tgw["us-east-1a:10.0.0.0/8"]' 'module.vpc.aws_route.private_to_tgw["private/us-east-1a"]' 16 | ``` 17 | 18 | If for some reason, you want to adusted the moved.tf file to accomplish the state mv commands for you, or move other resources, the python used to generate moved.tf can be found in [moved_block_rendering/](https://github.com/aws-ia/terraform-aws-vpc/tree/main/moved_block_rendering) 19 | -------------------------------------------------------------------------------- /modules/flow_logs/modules/s3_log_bucket/main.tf: -------------------------------------------------------------------------------- 1 | #tfsec:ignore:aws-s3-encryption-customer-key tfsec:ignore:aws-s3-enable-bucket-logging tfsec:ignore:aws-s3-enable-versioning 2 | resource "aws_s3_bucket" "flow_logs" { 3 | bucket_prefix = "vpc-flow-logs-${var.name}" 4 | force_destroy = true 5 | } 6 | 7 | resource "aws_s3_bucket_public_access_block" "flow_logs" { 8 | bucket = aws_s3_bucket.flow_logs.bucket 9 | 10 | block_public_acls = true 11 | block_public_policy = true 12 | ignore_public_acls = true 13 | restrict_public_buckets = true 14 | } 15 | 16 | #tfsec:ignore:aws-s3-encryption-customer-key 17 | resource "aws_s3_bucket_server_side_encryption_configuration" "flow_logs" { 18 | bucket = aws_s3_bucket.flow_logs.bucket 19 | 20 | rule { 21 | apply_server_side_encryption_by_default { 22 | sse_algorithm = "AES256" 23 | } 24 | } 25 | } 26 | 27 | resource "aws_s3_bucket_lifecycle_configuration" "flow_logs" { 28 | bucket = aws_s3_bucket.flow_logs.bucket 29 | 30 | rule { 31 | id = "transition" 32 | 33 | filter { 34 | prefix = var.lifecycle_filter_prefix 35 | } 36 | 37 | transition { 38 | days = 30 39 | storage_class = "STANDARD_IA" 40 | } 41 | 42 | expiration { 43 | days = 60 44 | } 45 | 46 | status = "Enabled" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /examples/advanced/main.tf: -------------------------------------------------------------------------------- 1 | 2 | locals { 3 | azs = ["eu-west-1a", "eu-west-1c"] 4 | } 5 | 6 | # ---------- AMAZON VPC ---------- 7 | module "vpc" { 8 | source = "../.." 9 | 10 | name = "advanced-example-vpc" 11 | cidr_block = "10.0.0.0/16" 12 | azs = local.azs 13 | 14 | optimize_subnet_cidr_ranges = true 15 | 16 | subnets = { 17 | public = { 18 | netmask = 28 19 | nat_gateway_configuration = "single_az" 20 | } 21 | private = { netmask = 24 } 22 | database = { netmask = 27 } 23 | infrastructure = { netmask = 28 } 24 | } 25 | 26 | vpc_flow_logs = { 27 | log_destination_type = "s3" 28 | destination_options = { 29 | file_format = "parquet" 30 | } 31 | } 32 | } 33 | 34 | # ---------- VPC SECONDARY CIDR ---------- 35 | module "secondary_cidr_block" { 36 | source = "../.." 37 | 38 | name = "advanced-example-secondary-cidr" 39 | vpc_id = module.vpc.vpc_attributes.id 40 | create_vpc = false 41 | vpc_secondary_cidr = true 42 | 43 | cidr_block = "10.100.0.0/16" 44 | azs = [local.azs[0]] 45 | 46 | vpc_secondary_cidr_natgw = { for k, v in module.vpc.nat_gateway_attributes_by_az : k => { id = v.id } } 47 | 48 | subnets = { 49 | private_secondary_cidr = { 50 | netmask = 28 51 | connect_to_public_natgw = true 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /examples/basic/README.md: -------------------------------------------------------------------------------- 1 | 2 | # VPC module - Example: Basic VPC 3 | 4 | This example builds an Amazon VPC with basic functionality: 5 | 6 | * Dual-stack VPC (IPv4 & IPv6) 7 | * Egress-only Internet gateway configured. 8 | * 4 VPC subnets - 1 public (dual-stack), 3 private (IPv4-only, dual-stack, and IPv6-only) 9 | * NAT gateways placed in all the public subnets. 10 | * Flow logs enabled (destination Amazon CloudWatch) 11 | * Routing: 12 | * IPv4 egress enabled in public subnets (through Internet gateway) and private subnets (through NAT gateways) 13 | * IPv6 egress enabled in private subnets (through Egress-only Internet gateway) 14 | 15 | ## Requirements 16 | 17 | | Name | Version | 18 | |------|---------| 19 | | [terraform](#requirement\_terraform) | >= 1.3.0 | 20 | | [aws](#requirement\_aws) | >= 5.0.0 | 21 | 22 | ## Providers 23 | 24 | No providers. 25 | 26 | ## Modules 27 | 28 | | Name | Source | Version | 29 | |------|--------|---------| 30 | | [vpc](#module\_vpc) | ../.. | n/a | 31 | 32 | ## Resources 33 | 34 | No resources. 35 | 36 | ## Inputs 37 | 38 | | Name | Description | Type | Default | Required | 39 | |------|-------------|------|---------|:--------:| 40 | | [aws\_region](#input\_aws\_region) | AWS Region. | `string` | `"eu-west-1"` | no | 41 | 42 | ## Outputs 43 | 44 | No outputs. 45 | -------------------------------------------------------------------------------- /examples/advanced/README.md: -------------------------------------------------------------------------------- 1 | 2 | # VPC module - Example: Advanced VPC 3 | 4 | This example builds an Amazon VPC with advanced functionality: 5 | 6 | * IPv4-only VPC. 7 | * NAT gateway configured only in 1 AZ. 8 | * 4 VPC subnets - 1 public (dual-stack), 3 private (IPv4-only, dual-stack, and IPv6-only) 9 | * Subnet CIDRs calculated to optimize IPv4 adress space (variable `optimize_subnet_cidr_ranges`) 10 | * Flow logs enabled (destination Amazon S3) 11 | * Secondary IPv4 CIDR block. 12 | * Routing - egress traffic through NAT gateways in private subnets (check also the configuration on NAT gateway routing when building the subnets with secondary CIDR block). 13 | 14 | ## Requirements 15 | 16 | | Name | Version | 17 | |------|---------| 18 | | [terraform](#requirement\_terraform) | >= 1.3.0 | 19 | | [aws](#requirement\_aws) | >= 5.0.0 | 20 | 21 | ## Providers 22 | 23 | No providers. 24 | 25 | ## Modules 26 | 27 | | Name | Source | Version | 28 | |------|--------|---------| 29 | | [secondary\_cidr\_block](#module\_secondary\_cidr\_block) | ../.. | n/a | 30 | | [vpc](#module\_vpc) | ../.. | n/a | 31 | 32 | ## Resources 33 | 34 | No resources. 35 | 36 | ## Inputs 37 | 38 | | Name | Description | Type | Default | Required | 39 | |------|-------------|------|---------|:--------:| 40 | | [aws\_region](#input\_aws\_region) | AWS Region. | `string` | `"eu-west-1"` | no | 41 | 42 | ## Outputs 43 | 44 | No outputs. 45 | -------------------------------------------------------------------------------- /.tflint.hcl: -------------------------------------------------------------------------------- 1 | # https://github.com/terraform-linters/tflint/blob/master/docs/user-guide/module-inspection.md 2 | # borrowed & modified indefinitely from https://github.com/ksatirli/building-infrastructure-you-can-mostly-trust/blob/main/.tflint.hcl 3 | 4 | plugin "aws" { 5 | enabled = true 6 | version = "0.21.1" 7 | source = "github.com/terraform-linters/tflint-ruleset-aws" 8 | } 9 | 10 | config { 11 | module = false 12 | force = false 13 | } 14 | 15 | rule "terraform_required_providers" { 16 | enabled = true 17 | } 18 | 19 | rule "terraform_required_version" { 20 | enabled = true 21 | } 22 | 23 | rule "terraform_naming_convention" { 24 | enabled = true 25 | format = "snake_case" 26 | } 27 | 28 | rule "terraform_typed_variables" { 29 | enabled = true 30 | } 31 | 32 | rule "terraform_unused_declarations" { 33 | enabled = true 34 | } 35 | 36 | rule "terraform_comment_syntax" { 37 | enabled = true 38 | } 39 | 40 | rule "terraform_deprecated_index" { 41 | enabled = true 42 | } 43 | 44 | rule "terraform_deprecated_interpolation" { 45 | enabled = true 46 | } 47 | 48 | rule "terraform_documented_outputs" { 49 | enabled = true 50 | } 51 | 52 | rule "terraform_documented_variables" { 53 | enabled = true 54 | } 55 | 56 | rule "terraform_module_pinned_source" { 57 | enabled = true 58 | } 59 | 60 | rule "terraform_standard_module_structure" { 61 | enabled = true 62 | } 63 | 64 | rule "terraform_workspace_remote" { 65 | enabled = true 66 | } 67 | 68 | # seems to be a bug when a resource is not created 69 | rule "aws_route_not_specified_target" { 70 | enabled = false 71 | } 72 | 73 | rule "aws_route_specified_multiple_targets" { 74 | enabled = false 75 | } 76 | -------------------------------------------------------------------------------- /examples/vpc_lattice/main.tf: -------------------------------------------------------------------------------- 1 | 2 | # ---------- AMAZON VPC ---------- 3 | module "vpc" { 4 | source = "../.." 5 | 6 | name = "vpclattice-example-vpc" 7 | cidr_block = "10.0.0.0/24" 8 | az_count = 2 9 | 10 | vpc_assign_generated_ipv6_cidr_block = true 11 | 12 | vpc_lattice = { 13 | service_network_identifier = aws_vpclattice_service_network.service_network.id 14 | security_group_ids = [aws_security_group.security_group.id] 15 | } 16 | 17 | subnets = { 18 | workload = { 19 | netmask = 28 20 | assign_ipv6_cidr = true 21 | } 22 | } 23 | } 24 | 25 | # Security Group 26 | resource "aws_security_group" "security_group" { 27 | name = "lattice-sg" 28 | description = "Lattice Securigy Group." 29 | vpc_id = module.vpc.vpc_attributes.id 30 | 31 | ingress { 32 | description = "Allow HTTP" 33 | from_port = 80 34 | to_port = 80 35 | protocol = "tcp" 36 | cidr_blocks = ["10.0.0.0/24"] 37 | } 38 | 39 | ingress { 40 | description = "Allow HTTPS" 41 | from_port = 443 42 | to_port = 443 43 | protocol = "tcp" 44 | cidr_blocks = ["10.0.0.0/24"] 45 | } 46 | 47 | egress { 48 | description = "Allowing inter-VPC traffic." 49 | from_port = 0 50 | to_port = 0 51 | protocol = "-1" 52 | cidr_blocks = ["10.0.0.0/24"] 53 | } 54 | 55 | egress { 56 | description = "Allowing VPC Lattice traffic." 57 | from_port = 0 58 | to_port = 0 59 | protocol = "-1" 60 | cidr_blocks = ["169.254.171.0/24"] 61 | } 62 | } 63 | 64 | # ---------- VPC LATTICE SERVICE NETWORK ---------- 65 | resource "aws_vpclattice_service_network" "service_network" { 66 | name = "example-service-network" 67 | auth_type = "NONE" 68 | } -------------------------------------------------------------------------------- /examples/vpc_lattice/README.md: -------------------------------------------------------------------------------- 1 | 2 | # VPC module - Example: Amazon VPC Lattice 3 | 4 | This example shows how you can use this module to create an [Amazon VPC Lattice](https://aws.amazon.com/vpc/lattice/) Service Network VPC association. The example creates: 5 | 6 | * VPC Lattice Service Network. 7 | * Security Group (allowing HTTP and HTTPS traffic from the local CIDR). Used to attach it to the VPC Lattice association 8 | * The VPC module creates the following: 9 | * One set of subnets (*workload*) 10 | * VPC Lattice Service Network VPC association. 11 | 12 | ## Requirements 13 | 14 | | Name | Version | 15 | |------|---------| 16 | | [terraform](#requirement\_terraform) | >= 1.3.0 | 17 | | [aws](#requirement\_aws) | >= 5.0.0 | 18 | 19 | ## Providers 20 | 21 | | Name | Version | 22 | |------|---------| 23 | | [aws](#provider\_aws) | >= 5.0.0 | 24 | 25 | ## Modules 26 | 27 | | Name | Source | Version | 28 | |------|--------|---------| 29 | | [vpc](#module\_vpc) | ../.. | n/a | 30 | 31 | ## Resources 32 | 33 | | Name | Type | 34 | |------|------| 35 | | [aws_security_group.security_group](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group) | resource | 36 | | [aws_vpclattice_service_network.service_network](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpclattice_service_network) | resource | 37 | 38 | ## Inputs 39 | 40 | | Name | Description | Type | Default | Required | 41 | |------|-------------|------|---------|:--------:| 42 | | [aws\_region](#input\_aws\_region) | AWS Region. | `string` | `"eu-west-1"` | no | 43 | 44 | ## Outputs 45 | 46 | No outputs. 47 | -------------------------------------------------------------------------------- /examples/transit_gateway/main.tf: -------------------------------------------------------------------------------- 1 | 2 | data "aws_availability_zones" "current" {} 3 | 4 | # ---------- AWS TRANSIT GATEWAY ---------- 5 | resource "aws_ec2_transit_gateway" "tgw" { 6 | description = "example" 7 | } 8 | 9 | # ---------- MANAGED PREFIX LIST ---------- 10 | resource "aws_ec2_managed_prefix_list" "prefix_list" { 11 | name = "All VPC CIDR-s" 12 | address_family = "IPv4" 13 | max_entries = length(var.prefixes) 14 | 15 | dynamic "entry" { 16 | for_each = var.prefixes 17 | 18 | content { 19 | cidr = entry.value 20 | description = entry.key 21 | } 22 | } 23 | } 24 | 25 | # ---------- AMAZON VPC ---------- 26 | module "vpc" { 27 | source = "../.." 28 | 29 | name = "tgw-example-vpc" 30 | cidr_block = "10.0.0.0/16" 31 | vpc_assign_generated_ipv6_cidr_block = true 32 | az_count = 2 33 | 34 | transit_gateway_id = aws_ec2_transit_gateway.tgw.id 35 | transit_gateway_routes = { 36 | private_ipv4 = "192.168.0.0/16" 37 | private_dualstack = aws_ec2_managed_prefix_list.prefix_list.id 38 | } 39 | transit_gateway_ipv6_routes = { 40 | private_dualstack = "::/0" 41 | } 42 | 43 | subnets = { 44 | private_ipv4 = { netmask = 28 } 45 | private_dualstack = { 46 | netmask = 24 47 | assign_ipv6_cidr = true 48 | } 49 | transit_gateway = { 50 | netmask = 28 51 | assign_ipv6_cidr = true 52 | transit_gateway_default_route_table_association = false 53 | transit_gateway_default_route_table_propagation = false 54 | transit_gateway_appliance_mode_support = "enable" 55 | transit_gateway_dns_support = "disable" 56 | transit_gateway_security_group_referencing_support = "enable" 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /examples/transit_gateway/README.md: -------------------------------------------------------------------------------- 1 | 2 | # VPC module - Example: AWS Transit Gateway connectivity 3 | 4 | This example shows how you can use this module to crete a AWS Transit Gateway VPC attachment. This examples creates the following: 5 | 6 | * AWS Transit Gateway. 7 | * IPv4 managed prefix list with two entries. 8 | * The VPC module creates the following: 9 | * Three sets of subnets (*private\_ipv4*, *private\_dualstack*, and *transit\_gateway*) 10 | * Transit Gateway VPC attachment. 11 | * Routing to Transit Gateway attachment: 12 | * IPv4 routes from *private\_ipv4*, and *private\_dualstack*. 13 | * IPv6 routes from *private\_dualstack*. 14 | 15 | ## Requirements 16 | 17 | | Name | Version | 18 | |------|---------| 19 | | [terraform](#requirement\_terraform) | >= 1.3.0 | 20 | | [aws](#requirement\_aws) | >= 5.0.0 | 21 | 22 | ## Providers 23 | 24 | | Name | Version | 25 | |------|---------| 26 | | [aws](#provider\_aws) | >= 5.0.0 | 27 | 28 | ## Modules 29 | 30 | | Name | Source | Version | 31 | |------|--------|---------| 32 | | [vpc](#module\_vpc) | ../.. | n/a | 33 | 34 | ## Resources 35 | 36 | | Name | Type | 37 | |------|------| 38 | | [aws_ec2_managed_prefix_list.prefix_list](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_managed_prefix_list) | resource | 39 | | [aws_ec2_transit_gateway.tgw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway) | resource | 40 | | [aws_availability_zones.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | 41 | 42 | ## Inputs 43 | 44 | | Name | Description | Type | Default | Required | 45 | |------|-------------|------|---------|:--------:| 46 | | [aws\_region](#input\_aws\_region) | AWS Region. | `string` | `"eu-west-1"` | no | 47 | | [prefixes](#input\_prefixes) | IPv4 prefixes. | `map(string)` |
{
"internal": "192.168.0.0/16",
"primary": "10.0.0.0/8"
}
| no | 48 | 49 | ## Outputs 50 | 51 | No outputs. 52 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). -------------------------------------------------------------------------------- /modules/calculate_subnets/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | # group subnets by type and create names for each type 3 | type_grouped_named_subnets_to_build = { for name, subnet_definition in var.subnets : name => [for _, az in var.azs : "${name}/${az}"] } 4 | # which network groups require calculating subnet 5 | types_to_calculate = [for type, subnet_definition in var.subnets : type if can(subnet_definition.netmask)] 6 | types_ipv6_native = [for type, subnet_definition in var.subnets : type if can(subnet_definition.ipv6_native)] 7 | # network groups that are set explicitly 8 | types_with_explicit_and_ipv6 = setsubtract(keys(var.subnets), local.types_to_calculate) 9 | types_with_explicit = setsubtract(local.types_with_explicit_and_ipv6, local.types_ipv6_native) 10 | 11 | 12 | # network object to pass to calculating module 13 | calculated_subnet_objects = flatten([for _, type in local.types_to_calculate : [for _, v in local.type_grouped_named_subnets_to_build[type] : { 14 | "name" = v 15 | "netmask" = var.subnets[type].netmask 16 | } 17 | ]]) 18 | 19 | # map of subnet names to netmask values for looking up netmask by name 20 | netmasks_for_subnets = { for subnet in local.calculated_subnet_objects : subnet.name => subnet.netmask } 21 | 22 | # sorted list of netmasks from largest to smallest so we can efficiently use the ip address space 23 | sorted_subnet_netmasks = reverse(distinct(sort([ 24 | for subnet in local.calculated_subnet_objects : format("%05d", subnet.netmask) 25 | ]))) 26 | 27 | # list of subnet names sorted based on their netmask value (large to small) 28 | sorted_subnet_names = compact(flatten([ 29 | for netmask in local.sorted_subnet_netmasks : [ 30 | for subnet in local.calculated_subnet_objects : 31 | subnet.name if subnet.netmask == tonumber(netmask) 32 | ] 33 | ])) 34 | 35 | # list of subnet the original calculated subnet objects, but sorted based on their netmask value (large to small) 36 | sorted_subnet_objects = [ 37 | for name in local.sorted_subnet_names : { 38 | name = name 39 | netmask = local.netmasks_for_subnets[name] 40 | } 41 | ] 42 | 43 | # map of explicit cidrs to az 44 | explicit_cidrs_grouped = { for _, type in local.types_with_explicit : type => zipmap(var.azs, var.subnets[type].cidrs[*]) } 45 | } 46 | 47 | module "subnet_calculator" { 48 | count = length(local.types_to_calculate) == 0 ? 0 : 1 49 | 50 | source = "drewmullen/subnets/cidr" 51 | version = "1.0.2" 52 | 53 | base_cidr_block = var.cidr 54 | networks = var.optimize_subnet_cidr_ranges ? local.sorted_subnet_objects : local.calculated_subnet_objects 55 | } 56 | -------------------------------------------------------------------------------- /examples/cloud_wan/README.md: -------------------------------------------------------------------------------- 1 | 2 | # VPC module - Example: AWS Cloud WAN connectivity 3 | 4 | This example shows how you can use this module with `core_network` subnets, and AWS Cloud WAN's VPC attachment. This examples creates the following: 5 | 6 | * Global Network and Core Network. 7 | * Core Network's policy (in `cwan_policy.tf`), creating two segments (prod and nonprod) in two AWS Regions (*us-east-1* and *eu-west-1*). The *prod* segments needs acceptance for the attachments. 8 | * The VPC module creates the following (in two AWS Regions): 9 | * Two sets of subnets (workloads and core\_network) 10 | * Cloud WAN's VPC attachment - with attachment acceptance for the VPC to associate to the *prod* segment. 11 | * Routing to Core Network (0.0.0.0/0 & ::/0) in workload subnets. 12 | 13 | ## Requirements 14 | 15 | | Name | Version | 16 | |------|---------| 17 | | [terraform](#requirement\_terraform) | >= 1.3.0 | 18 | | [aws](#requirement\_aws) | >= 5.22.0 | 19 | 20 | ## Providers 21 | 22 | | Name | Version | 23 | |------|---------| 24 | | [aws](#provider\_aws) | >= 5.22.0 | 25 | | [aws.awsnvirginia](#provider\_aws.awsnvirginia) | >= 5.22.0 | 26 | 27 | ## Modules 28 | 29 | | Name | Source | Version | 30 | |------|--------|---------| 31 | | [ireland\_vpc](#module\_ireland\_vpc) | ../.. | n/a | 32 | | [nvirginia\_vpc](#module\_nvirginia\_vpc) | ../.. | n/a | 33 | 34 | ## Resources 35 | 36 | | Name | Type | 37 | |------|------| 38 | | [aws_networkmanager_core_network.core_network](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/networkmanager_core_network) | resource | 39 | | [aws_networkmanager_global_network.global_network](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/networkmanager_global_network) | resource | 40 | | [aws_networkmanager_core_network_policy_document.policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/networkmanager_core_network_policy_document) | data source | 41 | 42 | ## Inputs 43 | 44 | | Name | Description | Type | Default | Required | 45 | |------|-------------|------|---------|:--------:| 46 | | [aws\_regions](#input\_aws\_regions) | AWS Regions to create in Cloud WAN's core network. |
object({
nvirginia = string
ireland = string
})
|
{
"ireland": "eu-west-1",
"nvirginia": "us-east-1"
}
| no | 47 | 48 | ## Outputs 49 | 50 | No outputs. 51 | -------------------------------------------------------------------------------- /modules/flow_logs/main.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | # does log destination need to be created? 3 | create_flow_log_destination = (var.flow_log_definition.log_destination == null && var.flow_log_definition.log_destination_type != "none") ? true : false 4 | 5 | # which log destination to use 6 | log_destination = local.create_flow_log_destination ? ( 7 | var.flow_log_definition.log_destination_type == "cloud-watch-logs" ? module.cloudwatch_log_group[0].log_group.arn : module.s3_log_bucket[0].bucket_flow_logs_attributes.arn # change to s3 when implemented 8 | ) : var.flow_log_definition.log_destination 9 | 10 | # Use IAM from submodule if if not passed 11 | iam_role_arn = local.create_flow_log_destination ? ( 12 | var.flow_log_definition.log_destination_type == "cloud-watch-logs" ? module.cloudwatch_log_group[0].iam_role.arn : null # s3: unnecessary, svc creates its own bucket policy 13 | ) : var.flow_log_definition.iam_role_arn 14 | } 15 | 16 | module "cloudwatch_log_group" { 17 | # if create destination and type = cloud-watch-logs 18 | count = (local.create_flow_log_destination && var.flow_log_definition.log_destination_type == "cloud-watch-logs") ? 1 : 0 19 | source = "aws-ia/cloudwatch-log-group/aws" 20 | version = "1.0.0" 21 | 22 | name = var.name 23 | retention_in_days = var.flow_log_definition.retention_in_days == null ? 180 : var.flow_log_definition.retention_in_days 24 | kms_key_id = var.flow_log_definition.kms_key_id 25 | aws_service_principal = "vpc-flow-logs.amazonaws.com" 26 | tags = var.tags 27 | } 28 | 29 | module "s3_log_bucket" { 30 | # if create destination and type = s3 31 | count = (local.create_flow_log_destination && var.flow_log_definition.log_destination_type == "s3") ? 1 : 0 32 | source = "./modules/s3_log_bucket" 33 | 34 | name = var.name 35 | lifecycle_filter_prefix = var.log_bucket_lifecycle_filter_prefix 36 | } 37 | 38 | resource "aws_flow_log" "main" { 39 | log_destination = local.log_destination 40 | iam_role_arn = local.iam_role_arn 41 | log_destination_type = var.flow_log_definition.log_destination_type 42 | traffic_type = var.flow_log_definition.traffic_type 43 | vpc_id = var.vpc_id 44 | log_format = var.flow_log_definition.log_format 45 | dynamic "destination_options" { 46 | for_each = var.flow_log_definition.log_destination_type == "s3" ? [true] : [] 47 | 48 | content { 49 | file_format = var.flow_log_definition.destination_options.file_format 50 | per_hour_partition = var.flow_log_definition.destination_options.per_hour_partition 51 | hive_compatible_partitions = var.flow_log_definition.destination_options.hive_compatible_partitions 52 | } 53 | } 54 | 55 | tags = merge( 56 | { Name = var.name }, 57 | var.tags 58 | ) 59 | } 60 | -------------------------------------------------------------------------------- /examples/cloud_wan/main.tf: -------------------------------------------------------------------------------- 1 | 2 | # ---------- AMAZON VPC (North Virginia) ---------- 3 | module "nvirginia_vpc" { 4 | source = "../.." 5 | providers = { aws = aws.awsnvirginia } 6 | 7 | name = "cwan-nvirginia-vpc" 8 | cidr_block = "10.0.0.0/24" 9 | vpc_assign_generated_ipv6_cidr_block = true 10 | az_count = 2 11 | 12 | core_network = { 13 | id = aws_networkmanager_core_network.core_network.id 14 | arn = aws_networkmanager_core_network.core_network.arn 15 | } 16 | core_network_routes = { 17 | workload = "0.0.0.0/0" 18 | } 19 | core_network_ipv6_routes = { 20 | workload = "::/0" 21 | } 22 | 23 | subnets = { 24 | workload = { 25 | netmask = 28 26 | assign_ipv6_cidr = true 27 | } 28 | core_network = { 29 | netmask = 28 30 | assign_ipv6_cidr = true 31 | appliance_mode_support = true 32 | require_acceptance = false 33 | 34 | tags = { 35 | env = "prod" 36 | } 37 | } 38 | } 39 | } 40 | 41 | # ---------- AMAZON VPC (Ireland) ---------- 42 | module "ireland_vpc" { 43 | source = "../.." 44 | providers = { aws = aws.awsireland } 45 | 46 | name = "cwan-ireland-vpc" 47 | cidr_block = "10.0.1.0/24" 48 | vpc_assign_generated_ipv6_cidr_block = true 49 | az_count = 2 50 | 51 | core_network = { 52 | id = aws_networkmanager_core_network.core_network.id 53 | arn = aws_networkmanager_core_network.core_network.arn 54 | } 55 | core_network_routes = { 56 | workload = "0.0.0.0/0" 57 | } 58 | core_network_ipv6_routes = { 59 | workload = "::/0" 60 | } 61 | subnets = { 62 | workload = { 63 | netmask = 28 64 | assign_ipv6_cidr = true 65 | } 66 | core_network = { 67 | netmask = 28 68 | assign_ipv6_cidr = true 69 | require_acceptance = false 70 | 71 | tags = { 72 | env = "nonprod" 73 | } 74 | } 75 | } 76 | } 77 | 78 | # ---------- AWS CLOUD WAN RESOURCES ---------- 79 | # Global network 80 | resource "aws_networkmanager_global_network" "global_network" { 81 | provider = aws.awsnvirginia 82 | 83 | description = "Global Network - VPC module" 84 | } 85 | 86 | # Core network 87 | resource "aws_networkmanager_core_network" "core_network" { 88 | provider = aws.awsnvirginia 89 | 90 | description = "Core Network - VPC module" 91 | global_network_id = aws_networkmanager_global_network.global_network.id 92 | 93 | create_base_policy = true 94 | base_policy_document = data.aws_networkmanager_core_network_policy_document.policy.json 95 | 96 | tags = { 97 | Name = "Core Network - VPC module" 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /docs/UPGRADE-GUIDE-4.0.md: -------------------------------------------------------------------------------- 1 | # Upgrade from version 3 to version 4 2 | 3 | This VPC module is being upgraded to center all its resources on a single provider. Previously we used the awscc provider for various exploration reasons. However, as the module's usage grows, we wish to place more emphasis on customer experience and using a single provider is more seamless. Unfortunately, replacing the awscc resources requires state manipulation which is detailed below. 4 | 5 | ## Preparation for upgrade 6 | 7 | 1. create a backup of your `tfstate` file. You will have to adjust your backup mechanism to your specific situation. 1 example of backup: `tf state pull | tee tfstateV3.bak` 8 | 1. create a file of resources that require modification: `terraform state list | grep -e awscc | tee resources_to_replace.txt` 9 | 10 | ## Upgrade procedure 11 | 12 | Switching resource types is not possible via the native `moved {}` block. For new resources types we must remove and import back the statefile. 13 | 14 | ### Overview 15 | 16 | 1. relocate any `var.tags` entries to [default_tags](https://www.hashicorp.com/blog/default-tags-in-the-terraform-aws-provider) 17 | 1. swap `awscc_ec2_route_table` for `aws_route_table` resource via `terraform state` commands 18 | 1. swap `awscc_ec2_subnet_route_table_association` for `aws_route_table_association` resource via `terraform state` commands 19 | 1. Verify no unintended changes via `terraform plan` 20 | 21 | You can always fallback to prior state using the backup you created. 22 | 23 | ### route_table 24 | 25 | For each `awscc_ec2_route_table` type, run the following 3 commands, replacing the relevant parts for command 3 26 | 27 | 1. Show state values: `terraform state show 'module.vpc.awscc_ec2_route_table.private["private/us-east-1a"]'` 28 | 1. Remove from state: `terraform state rm 'module.vpc.awscc_ec2_route_table.private["private/us-east-1a"]'` 29 | 1. Import as `aws` resource: `terraform import 'module.vpc.aws_route_table.private["private/us-east-1a"]' rtb-0b9b71f291529d9fe` 30 | 31 | For command 3 you need to use the ID outputted from command 1 and you need to change `awscc_ec2_route_table` to `aws_route_table`. 32 | 33 | 34 | ### route_table_association 35 | 36 | For each `awscc_ec2_subnet_route_table_association` type, run the following 3 commands, replacing the relevant parts for command 3 37 | 38 | 1. Show state values: 39 | ``` 40 | terraform state show 'module.vpc.awscc_ec2_subnet_route_table_association.private["private/us-east-1a"]' 41 | resource "awscc_ec2_subnet_route_table_association" "private" { 42 | id = "rtbassoc-0c65299161472413c" 43 | route_table_id = "rtb-0b9b71f291529d9fe" 44 | subnet_id = "subnet-0e1c7e5f9d727fdc1" 45 | } 46 | ``` 47 | 2. Remove from state: `terraform state rm 'module.vpc.awscc_ec2_subnet_route_table_association.private["private/us-east-1a"]'` 48 | 49 | 3. Import as `aws` resource: `terraform import 'module.vpc.aws_route_table_association.private["private/us-east-1a"]' subnet-0e1c7e5f9d727fdc1/rtb-0b9b71f291529d9fe` 50 | 51 | For command 3 you need to use the IDs outputted (format is `subnet_id`/`route_table_id`) from command 1 and you need to change `awscc_ec2_route_table` to `aws_route_table`. 52 | -------------------------------------------------------------------------------- /docs/UPGRADE-GUIDE-3.0.md: -------------------------------------------------------------------------------- 1 | # Changes from 2.x to 3.x 2 | 3 | - IPAM vpcs no longer rely on the `aws_vpc_ipam_preview_next_cidr` resource. This is a breaking change for VPCs were built with this resource dependency, with a workaround available. Removing this dependency was the last major [known] sore thumb of this module. With this removed we can now build vpcs in the same module as IPAMs, or, more importantly, prefix lists / transit gateways in the same `apply` as the vpc. 4 | - transit gateway id is now passed as a root level variable. Previously it was passed in var.subnets.transit_gateway.transit_gateway_id. While this was logically a nice way to organize variable references it lead to race conditions if you were trying to create a transit gateway in the same root module as the VPC call. 5 | - route to transit gateway is now passed at a root level via variable `transit_gateway_routes`. Previously it was passed in var.subnets..route_to_transit_gateway. While this was logically a nice way to organize variable references it lead to race conditions if you were trying to create the prefix list in the same root module as the VPC call. 6 | 7 | # Required Changes to Make 8 | 9 | ## Regarding changes to Transit Gateway users 10 | 11 | Before: 12 | 13 | ```hcl 14 | module "vpc" { 15 | ... 16 | subnets = { 17 | transit_gateway = { 18 | transit_gateway_id = <> 19 | ... 20 | } 21 | } 22 | } 23 | ``` 24 | 25 | After: 26 | 27 | ```hcl 28 | module "vpc" { 29 | ... 30 | transit_gateway_id = <> 31 | subnets = {...} 32 | } 33 | ``` 34 | 35 | ## Regarding changes to Transit Gateway Routes using prefix list 36 | 37 | Before: 38 | 39 | ```hcl 40 | module "vpc" { 41 | ... 42 | subnets = { 43 | public = { 44 | route_to_transit_gateway = 45 | } 46 | private = { 47 | route_to_transit_gateway = 48 | } 49 | } 50 | } 51 | ``` 52 | 53 | After: 54 | 55 | ```hcl 56 | module "vpc" { 57 | ... 58 | transit_gateway_id = <> 59 | transit_gateway_routes = { 60 | public = 61 | private = 62 | } 63 | subnets = {...} 64 | } 65 | ``` 66 | 67 | ## Regarding changes to IPAM users 68 | 69 | Using you were using the [IPAM Example](https://github.com/aws-ia/terraform-aws-vpc/blob/main/examples/ipam/main.tf) and you upgrde to v3, you must make the following changes to avoid a re-build. 70 | 71 | 1. Upgrade the module `terraform init -upgrade` 72 | 1. Remove the preview resource from the state file: `terraform state rm 'module.vpc.aws_vpc_ipam_preview_next_cidr.main[0]'` 73 | 1. Make necessary HCL changes 74 | 75 | ## HCL changes 76 | 77 | Before: 78 | 79 | ```hcl 80 | module "vpc" { 81 | name = "ipam-vpc" 82 | az_count = 3 83 | 84 | vpc_ipv4_ipam_pool_id = "ipam-pool-079f76df39be519c9" 85 | vpc_ipv4_netmask_length = 26 86 | 87 | subnets = { 88 | private = { 89 | netmask = 28 90 | connect_to_public_natgw = false 91 | } 92 | } 93 | } 94 | ``` 95 | 96 | After : 97 | 98 | Commenting out lines that can be removed for clarity. Feel free to remove from your module reference 99 | 100 | ```hcl 101 | module "vpc" { 102 | name = "ipam-vpc" 103 | az_count = 3 104 | 105 | vpc_ipv4_ipam_pool_id = "ipam-pool-079f76df39be519c9" 106 | # vpc_ipv4_netmask_length = 26 107 | cidr_block = "172.2.0.192/26" 108 | 109 | subnets = { 110 | private = { 111 | cidrs = ["172.2.0.192/28", "172.2.0.208/28", "172.2.0.224/28"] 112 | # netmask = 28 113 | connect_to_public_natgw = false 114 | } 115 | } 116 | } 117 | ``` 118 | 119 | # Previous upgrade guides 120 | 121 | - [v2 Upgrade](https://github.com/aws-ia/terraform-aws-vpc/blob/c60216fed89e9617f9357d894462b1282c63682f/UPGRADE-GUIDE-2.0.md) 122 | -------------------------------------------------------------------------------- /docs/How-to-use-module-outputs.md: -------------------------------------------------------------------------------- 1 | # Why our Outputs are so different and how you can use them 2 | 3 | The Outputs from this module are designed differently than many other popular modules. Most have 100+ outputs that export individual values for specific uses. This is great because it is simple, straight-forward approach to creating and using output values from a module. This module takes a different design approach, an opinionated one that may cause you to turn your head on first glance but, we believe, is more useful and maintainable in the long run. 4 | 5 | ## TL;DR How do I use these things? 6 | 7 | [See here](#using-the-console-to-explore) 8 | 9 | ## Implementation details: 10 | 11 | Outputs are provided at the _resource_ level and grouped in the most logical way we have considered (please suggest others!). Let us look at a few examples: 12 | 13 | - [nat_gateway_attributes_by_az](https://github.com/aws-ia/terraform-aws-vpc/#output_nat_gateway_attributes_by_az): 14 | * description: Map of nat gateway resource attributes by AZ. 15 | 16 | The name of the output explains exactly what you'll get. The attributes of a nat gateway and they're grouped by the name of the availability zone theyre in. You will see this design in most of the outputs. This is because when users make multi-az vpcs the attributes of those resources are most relevant with regard to the AZ theyre in. 17 | 18 | ### Using the Console to explore 19 | 20 | After building the [public_private_flow_logs example](https://registry.terraform.io/modules/aws-ia/vpc/aws/latest/examples/public_private_flow_logs) drop into the [console](https://developer.hashicorp.com/terraform/cli/commands/console). 21 | 22 | ```shell 23 | terraform console 24 | > module.vpc.nat_gateway_attributes_by_az 25 | { 26 | "us-east-2a" = { 27 | "allocation_id" = "eipalloc-0ae0e24ffe193f2a1" 28 | "association_id" = "eipassoc-0d714a2e4a1f43c46" 29 | "connectivity_type" = "public" 30 | "id" = "nat-076e272ecaff6fce0" 31 | "network_interface_id" = "eni-07d0d8f11fc3380b5" 32 | "private_ip" = "10.0.2.19" 33 | "public_ip" = "<>" 34 | "subnet_id" = "subnet-005519f1eb2ca5e9d" 35 | "tags" = tomap({ 36 | "Name" = "nat-my-public-us-east-2a" 37 | "subnet_type" = "public" 38 | }) 39 | "tags_all" = tomap({ 40 | "Name" = "nat-my-public-us-east-2a" 41 | "subnet_type" = "public" 42 | }) 43 | } 44 | "us-east-2b" = { 45 | "allocation_id" = "eipalloc-0a0e69ff06847b9cc" 46 | "association_id" = "eipassoc-00ae0cc78566f5ad9" 47 | "connectivity_type" = "public" 48 | "id" = "nat-063181ad924968f92" 49 | "network_interface_id" = "eni-04bc3f3eb3ac6ce77" 50 | "private_ip" = "10.0.3.102" 51 | "public_ip" = "<>" 52 | "subnet_id" = "subnet-04c0b573d8937853d" 53 | "tags" = tomap({ 54 | "Name" = "nat-my-public-us-east-2b" 55 | "subnet_type" = "public" 56 | }) 57 | "tags_all" = tomap({ 58 | "Name" = "nat-my-public-us-east-2b" 59 | "subnet_type" = "public" 60 | }) 61 | } 62 | } 63 | ``` 64 | 65 | You can see that the vpc is in 2 AZs and since `nat_gateway_configuration = "all_azs"` nat gateways were built per AZ. The output object is a nested map and can be referenced with `.` notiation. 66 | 67 | ```hcl 68 | > module.vpc.nat_gateway_attributes_by_az.us-east-2a.id 69 | "nat-076e272ecaff6fce0" 70 | > module.vpc.nat_gateway_attributes_by_az.us-east-2a.public_ip 71 | "3.21.81.83" 72 | ``` 73 | 74 | Since it is a map you can also use expressions to grab values from each nat gateway and even construct them into another useful map: 75 | 76 | ```hcl 77 | > { for az, attrs in module.vpc.nat_gateway_attributes_by_az: az => { id : attrs.id, private_ip : attrs.private_ip } } 78 | { 79 | "us-east-2a" = { 80 | "id" = "nat-076e272ecaff6fce0" 81 | "private_ip" = "10.0.2.19" 82 | } 83 | "us-east-2b" = { 84 | "id" = "nat-063181ad924968f92" 85 | "private_ip" = "10.0.3.102" 86 | } 87 | } 88 | ``` 89 | 90 | Finally, they can be passed to another resource as the parameter to `for_each`. The result would be 2 new resources who's [block label](https://developer.hashicorp.com/terraform/docs/glossary#block) corresponds to the AZ those resources are in 91 | 92 | ```hcl 93 | resource "aws_route" "new_route_to_nat_gateway" { 94 | for_each = module.vpc.nat_gateway_attributes_by_az 95 | 96 | route_table_id = aws_route_table.new_route_table[each.key].id 97 | destination_cidr_block = "0.0.0.0/0" 98 | nat_gateway_id = each.value.id 99 | } 100 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "vpc_attributes" { 2 | description = "VPC resource attributes. Full output of aws_vpc." 3 | value = local.vpc 4 | } 5 | 6 | output "azs" { 7 | description = "List of AZs where subnets are created." 8 | value = local.azs 9 | } 10 | 11 | output "transit_gateway_attachment_id" { 12 | description = "Transit gateway attachment id." 13 | value = try(aws_ec2_transit_gateway_vpc_attachment.tgw[0].id, null) 14 | } 15 | 16 | output "core_network_attachment" { 17 | description = "AWS Cloud WAN's core network attachment. Full output of aws_networkmanager_vpc_attachment." 18 | value = try(aws_networkmanager_vpc_attachment.cwan[0], null) 19 | } 20 | 21 | output "private_subnet_attributes_by_az" { 22 | value = try(aws_subnet.private, null) 23 | description = <<-EOF 24 | Map of all private subnets containing their attributes. 25 | 26 | Example: 27 | ``` 28 | private_subnet_attributes_by_az = { 29 | "private/us-east-1a" = { 30 | "arn" = "arn:aws:ec2:us-east-1:<>:subnet/subnet-04a86315c4839b519" 31 | "assign_ipv6_address_on_creation" = false 32 | ... 33 | 34 | } 35 | "us-east-1b" = {...) 36 | } 37 | ``` 38 | EOF 39 | } 40 | 41 | output "public_subnet_attributes_by_az" { 42 | value = try(aws_subnet.public, null) 43 | description = <<-EOF 44 | Map of all public subnets containing their attributes. 45 | 46 | Example: 47 | ``` 48 | public_subnet_attributes_by_az = { 49 | "us-east-1a" = { 50 | "arn" = "arn:aws:ec2:us-east-1:<>:subnet/subnet-04a86315c4839b519" 51 | "assign_ipv6_address_on_creation" = false 52 | ... 53 | 54 | } 55 | "us-east-1b" = {...) 56 | } 57 | ``` 58 | EOF 59 | } 60 | 61 | output "tgw_subnet_attributes_by_az" { 62 | value = try(aws_subnet.tgw, null) 63 | description = <<-EOF 64 | Map of all tgw subnets containing their attributes. 65 | 66 | Example: 67 | ``` 68 | tgw_subnet_attributes_by_az = { 69 | "us-east-1a" = { 70 | "arn" = "arn:aws:ec2:us-east-1:<>:subnet/subnet-04a86315c4839b519" 71 | "assign_ipv6_address_on_creation" = false 72 | ... 73 | 74 | } 75 | "us-east-1b" = {...) 76 | } 77 | ``` 78 | EOF 79 | } 80 | 81 | output "core_network_subnet_attributes_by_az" { 82 | value = try(aws_subnet.cwan, null) 83 | description = <<-EOF 84 | Map of all core_network subnets containing their attributes. 85 | 86 | Example: 87 | ``` 88 | core_network_subnet_attributes_by_az = { 89 | "us-east-1a" = { 90 | "arn" = "arn:aws:ec2:us-east-1:<>:subnet/subnet-04a86315c4839b519" 91 | "assign_ipv6_address_on_creation" = false 92 | ... 93 | 94 | } 95 | "us-east-1b" = {...) 96 | } 97 | ``` 98 | EOF 99 | } 100 | 101 | output "rt_attributes_by_type_by_az" { 102 | value = { 103 | # TODO: omit keys if value is null 104 | "private" = aws_route_table.private, 105 | "public" = aws_route_table.public 106 | "transit_gateway" = aws_route_table.tgw 107 | "core_network" = aws_route_table.cwan 108 | } 109 | description = <<-EOF 110 | Map of route tables by type => az => route table attributes. Example usage: module.vpc.rt_attributes_by_type_by_az.private.id 111 | 112 | Example: 113 | ``` 114 | rt_attributes_by_type_by_az = { 115 | "private" = { 116 | "us-east-1a" = { 117 | "id" = "rtb-0e77040c0598df003" 118 | "tags" = tolist([ 119 | { 120 | "key" = "Name" 121 | "value" = "private-us-east-1a" 122 | }, 123 | ]) 124 | "vpc_id" = "vpc-033e054f49409592a" 125 | ... 126 | 127 | } 128 | "us-east-1b" = { ... } 129 | "public" = { ... } 130 | ``` 131 | EOF 132 | } 133 | 134 | output "nat_gateway_attributes_by_az" { 135 | value = try(aws_nat_gateway.main, null) 136 | description = <<-EOF 137 | Map of nat gateway resource attributes by AZ. 138 | 139 | Example: 140 | ``` 141 | nat_gateway_attributes_by_az = { 142 | "us-east-1a" = { 143 | "allocation_id" = "eipalloc-0e8b20303eea88b13" 144 | "connectivity_type" = "public" 145 | "id" = "nat-0fde39f9550f4abb5" 146 | "network_interface_id" = "eni-0d422727088bf9a86" 147 | "private_ip" = "10.0.3.40" 148 | "public_ip" = <> 149 | "subnet_id" = "subnet-0f11c92e439c8ab4a" 150 | "tags" = tomap({ 151 | "Name" = "nat-my-public-us-east-1a" 152 | }) 153 | "tags_all" = tomap({ 154 | "Name" = "nat-my-public-us-east-1a" 155 | }) 156 | } 157 | "us-east-1b" = { ... } 158 | } 159 | ``` 160 | EOF 161 | } 162 | 163 | output "natgw_id_per_az" { 164 | value = try(local.nat_per_az, null) 165 | description = <<-EOF 166 | Map of nat gateway IDs for each resource. Will be duplicate ids if your var.subnets.public.nat_gateway_configuration = "single_az". 167 | 168 | Example: 169 | ``` 170 | natgw_id_per_az = { 171 | "us-east-1a" = { 172 | "id" = "nat-0fde39f9550f4abb5" 173 | } 174 | "us-east-1b" = { 175 | "id" = "nat-0fde39f9550f4abb5" 176 | } 177 | } 178 | ``` 179 | EOF 180 | } 181 | 182 | output "internet_gateway" { 183 | value = try(aws_internet_gateway.main[0], null) 184 | description = "Internet gateway attributes. Full output of aws_internet_gateway." 185 | } 186 | 187 | output "egress_only_internet_gateway" { 188 | value = try(aws_egress_only_internet_gateway.eigw[0], null) 189 | description = "Egress-only Internet gateway attributes. Full output of aws_egress_only_internet_gateway." 190 | } 191 | 192 | output "vpc_lattice_service_network_association" { 193 | value = try(aws_vpclattice_service_network_vpc_association.vpc_lattice_service_network_association[0], null) 194 | description = "VPC Lattice Service Network VPC association. Full output of aws_vpclattice_service_network_vpc_association" 195 | } 196 | 197 | output "flow_log_attributes" { 198 | description = "Flow Log information." 199 | value = try(module.flow_logs[0].flow_log, null) 200 | } 201 | -------------------------------------------------------------------------------- /data.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | # az_count has been provided slice based on az_count otherwise return those provided 3 | azs = var.az_count != null ? slice(data.aws_availability_zones.current.names, 0, var.az_count) : data.aws_availability_zones.current.names 4 | 5 | # references to module.calculate_subnets output 6 | calculated_subnets = module.calculate_subnets.subnets_by_type 7 | subnets_with_ipv6_native = module.calculate_subnets.subnets_with_ipv6_native 8 | 9 | # references to module.calculate_subnets_ipv6 10 | calculated_subnets_ipv6 = module.calculate_subnets_ipv6.subnets_ipv6 11 | 12 | ################################################################## 13 | # Subnet names 14 | # A subnet's name is the subnet key by default but can be overrided by `name_prefix`. 15 | # Subnet names are used for Name tags. 16 | # resource name labels always use subnet key 17 | subnet_keys = keys(var.subnets) 18 | subnet_names = { for type, v in var.subnets : type => try(v.name_prefix, type) } 19 | subnet_keys_with_tags = { for type, v in var.subnets : type => v.tags if can(v.tags) } 20 | 21 | ################################################################## 22 | # Internal variables for mapping user input from var.subnets to HCL useful values 23 | # Notes: 24 | # - subnets map contains arbitrary amount of subnet "keys" which are both defined as the subnets type and default name (unless name_prefix is provided). 25 | # - resource name labels for subnet use the key as private subnet keys are constructed 26 | singleton_subnet_types = ["public", "transit_gateway", "core_network"] 27 | private_subnet_names = setsubtract(local.subnet_keys, local.singleton_subnet_types) 28 | 29 | # constructed list of /az 30 | private_per_az = flatten([for az in local.azs : [for subnet in local.private_subnet_names : "${subnet}/${az}"]]) 31 | # list of private subnet keys with connect_to_public_natgw = true 32 | private_subnets_nat_routed = [for type in local.private_subnet_names : type if try(var.subnets[type].connect_to_public_natgw == true, false)] 33 | # private subnets with cidrs per az if connect_to_public_natgw = true ... "privatetwo/us-east-1a" 34 | private_subnet_names_nat_routed = [for subnet in local.private_per_az : subnet if contains(local.private_subnets_nat_routed, split("/", subnet)[0])] 35 | 36 | # support variables for transit_gateway_routes 37 | subnets_tgw_routed = keys(var.transit_gateway_routes) 38 | private_subnet_key_names_tgw_routed = [for subnet in local.private_per_az : subnet if contains(local.subnets_tgw_routed, split("/", subnet)[0])] 39 | 40 | # support variables for transit_gateway_ipv6_routes 41 | ipv6_subnets_tgw_routed = keys(var.transit_gateway_ipv6_routes) 42 | ipv6_private_subnet_key_names_tgw_routed = [for subnet in local.private_per_az : subnet if contains(local.ipv6_subnets_tgw_routed, split("/", subnet)[0])] 43 | 44 | # support variables for core_network_routes 45 | subnets_cwan_routed = keys(var.core_network_routes) 46 | private_subnet_key_names_cwan_routes = [for subnet in local.private_per_az : subnet if contains(local.subnets_cwan_routed, split("/", subnet)[0])] 47 | 48 | # support variables for core_network_ipv6_routes 49 | ipv6_subnets_cwan_routed = keys(var.core_network_ipv6_routes) 50 | ipv6_private_subnet_keys_names_cwan_routes = [for subnet in local.private_per_az : subnet if contains(local.ipv6_subnets_cwan_routed, split("/", subnet)[0])] 51 | 52 | # support variables for core_network subnets 53 | require_acceptance = try(var.subnets.core_network.require_acceptance, false) # value to default 54 | accept_attachment = try(var.subnets.core_network.accept_attachment, true) # value to default 55 | create_acceptance = (local.require_acceptance == true && local.accept_attachment == true) 56 | create_cwan_routes = (local.require_acceptance == false) || local.create_acceptance 57 | 58 | # default value for var.subnets.public.connect_to_igw (default to true) 59 | connect_to_igw = try(var.subnets.public.connect_to_igw, true) 60 | 61 | ################################################################## 62 | # NAT configurations options, maps user string input to HCL usable values. selected based on nat_gateway_configuration 63 | # null = none 64 | # all = local.azs 65 | # single = local.azs[0] 66 | nat_options = { 67 | "all_azs" = local.azs 68 | "single_az" = [local.azs[0]] 69 | "none" = [] # explicit "none" or omitted 70 | } 71 | nat_gateway_configuration = try(length(var.subnets.public.nat_gateway_configuration), 0) != 0 ? var.subnets.public.nat_gateway_configuration : "none" 72 | 73 | # if public subnets being built, check how many nats to create 74 | # options defined by `local.nat_options` 75 | # nat_configuration is a list of az names where a nat should be created 76 | nat_configuration = contains(local.subnet_keys, "public") ? local.nat_options[local.nat_gateway_configuration] : local.nat_options["none"] 77 | 78 | # used to reference which nat gateway id should be used in route 79 | nat_per_az = (contains(local.subnet_keys, "public") && !var.vpc_secondary_cidr) ? ( 80 | # map of az : { id = }, ex: { "us-east-1a" : { "id": "nat-123" }} 81 | { for az in local.azs : az => { 82 | id : try(aws_nat_gateway.main[az].id, aws_nat_gateway.main[local.nat_configuration[0]].id) } if local.nat_gateway_configuration != "none" 83 | }) : ( 84 | var.vpc_secondary_cidr ? var.vpc_secondary_cidr_natgw : {} 85 | ) 86 | 87 | ################################################################## 88 | # Feature toggles for whether: 89 | # - create or reference a VPC 90 | # - get cidr block value from AWS IPAM 91 | # - create flow logs 92 | 93 | vpc = var.create_vpc ? aws_vpc.main[0] : data.aws_vpc.main[0] 94 | cidr_block = var.cidr_block == null ? local.vpc.cidr_block : var.cidr_block 95 | 96 | create_flow_logs = (var.vpc_flow_logs == null || var.vpc_flow_logs.log_destination_type == "none") ? false : true 97 | 98 | # IPv6 ############################################################ 99 | # Ipv6 cidr block (To change when multiple Ipv6 CIDR blocks) 100 | vpc_ipv6_cidr_block = var.vpc_ipv6_cidr_block == null ? local.vpc.ipv6_cidr_block : var.vpc_ipv6_cidr_block 101 | # Checking if public subnets are dual-stack or IPv6-only 102 | public_ipv6only = can(var.subnets.public.ipv6_native) 103 | public_dualstack = !local.public_ipv6only && (can(var.subnets.public.assign_ipv6_cidr) || can(var.subnets.public.ipv6_cidrs)) 104 | # Checking if transit_gateway subnets are dual-stack 105 | tgw_dualstack = (can(var.subnets.transit_gateway.assign_ipv6_cidr) || can(var.subnets.transit_gateway.ipv6_cidrs)) 106 | # Checking if core_network subnets are dual-stack 107 | cwan_dualstack = (can(var.subnets.core_network.assign_ipv6_cidr) || can(var.subnets.core_network.ipv6_cidrs)) 108 | 109 | # Egress Only Internet Gateway for IPv6 110 | # list of private subnet keys with connect_to_public_eigw = true 111 | private_subnets_egress_routed = [for type in local.private_subnet_names : type if try(var.subnets[type].connect_to_eigw == true, false)] 112 | # private subnets with cidrs per az if connect_to_public_eigw = true ... "privatetwo/us-east-1a" 113 | private_subnet_names_egress_routed = [for subnet in local.private_per_az : subnet if contains(local.private_subnets_egress_routed, split("/", subnet)[0])] 114 | 115 | # VPC LATTICE ############################################################ 116 | # If var.vpc_lattice is defined (default = {}), the VPC association is created. 117 | lattice_association = length(keys(var.vpc_lattice)) > 0 118 | 119 | log_name = var.vpc_flow_logs.name_override == "" ? var.name : var.vpc_flow_logs.name_override 120 | } 121 | 122 | data "aws_availability_zones" "current" { 123 | filter { 124 | name = "opt-in-status" 125 | values = ["opt-in-not-required"] 126 | } 127 | 128 | dynamic "filter" { 129 | for_each = var.azs != null ? [1] : [] 130 | content { 131 | name = "zone-name" 132 | values = var.azs 133 | } 134 | } 135 | 136 | lifecycle { 137 | # Check at least one variable `var.azs` or `var.az_count` has been provided 138 | precondition { 139 | condition = (var.azs != null && var.az_count != null) ? false : true 140 | error_message = "Both `var.azs` and `var.az_count` provided. You must provide a value for `var.azs` or `var.az_count` but not both." 141 | } 142 | 143 | # Check that only `var.azs` or `var.az_count` was provided (and not both) 144 | precondition { 145 | condition = (var.azs == null && var.az_count == null) ? false : true 146 | error_message = "You must provide a value for the variable `var.az_count` or `var.azs`." 147 | } 148 | 149 | # Check if `var.azs` was provided that the filter returns the correct number of AZs (i.e. all exist in the current partition/region) 150 | postcondition { 151 | condition = (var.azs == null) ? true : (length(self.names) == length(var.azs) ? true : false) 152 | error_message = "One or more of the Availability Zones provided in `var.azs` does not exist. Please check the availability zones are available in the current region." 153 | } 154 | } 155 | } 156 | 157 | # search for existing vpc with var.vpc_id if not creating 158 | data "aws_vpc" "main" { 159 | count = var.create_vpc ? 0 : 1 160 | id = var.vpc_id 161 | } 162 | 163 | # santizes tags for both aws / awscc providers 164 | # aws tags = module.tags.tags_aws 165 | # awscc tags = module.tags.tags 166 | module "tags" { 167 | source = "aws-ia/label/aws" 168 | version = "0.0.6" 169 | 170 | tags = var.tags 171 | } 172 | 173 | module "subnet_tags" { 174 | source = "aws-ia/label/aws" 175 | version = "0.0.6" 176 | 177 | for_each = local.subnet_keys_with_tags 178 | 179 | tags = each.value 180 | } 181 | 182 | module "vpc_lattice_tags" { 183 | source = "aws-ia/label/aws" 184 | version = "0.0.6" 185 | 186 | tags = try(var.vpc_lattice.tags, {}) 187 | } 188 | -------------------------------------------------------------------------------- /.header.md: -------------------------------------------------------------------------------- 1 | # AWS VPC Module 2 | 3 | This module can be used to deploy a pragmatic VPC with various subnets types in # AZs. Common deployment examples can be found in [examples/](https://github.com/aws-ia/terraform-aws-vpc/tree/main/examples). 4 | 5 | Note: For information regarding the 4.0 upgrade see our [upgrade guide](https://github.com/aws-ia/terraform-aws-vpc/blob/main/docs/UPGRADE-GUIDE-4.0.md). 6 | 7 | ## Usage 8 | 9 | The example below builds a dual-stack VPC with public and private subnets in 3 AZs. Each subnet calculates an IPv4 CIDR based on the `netmask` argument passed, and an IPv6 CIDR with a /64 prefix length. The public subnets build NAT gateways in each AZ but optionally can be switched to `single_az`. An Egress-only Internet gateway is created by using the variable `vpc_egress_only_internet_gateway`. 10 | 11 | ```hcl 12 | module "vpc" { 13 | source = "aws-ia/vpc/aws" 14 | version = ">= 4.2.0" 15 | 16 | name = "multi-az-vpc" 17 | cidr_block = "10.0.0.0/16" 18 | vpc_assign_generated_ipv6_cidr_block = true 19 | vpc_egress_only_internet_gateway = true 20 | az_count = 3 21 | 22 | subnets = { 23 | # Dual-stack subnet 24 | public = { 25 | name_prefix = "my_public" # omit to prefix with "public" 26 | netmask = 24 27 | assign_ipv6_cidr = true 28 | nat_gateway_configuration = "all_azs" # options: "single_az", "none" 29 | } 30 | # IPv4 only subnet 31 | private = { 32 | # omitting name_prefix defaults value to "private" 33 | # name_prefix = "private_with_egress" 34 | netmask = 24 35 | connect_to_public_natgw = true 36 | } 37 | # IPv6-only subnet 38 | private_ipv6 = { 39 | ipv6_native = true 40 | assign_ipv6_cidr = true 41 | connect_to_eigw = true 42 | } 43 | } 44 | 45 | vpc_flow_logs = { 46 | log_destination_type = "cloud-watch-logs" 47 | retention_in_days = 180 48 | } 49 | } 50 | ``` 51 | 52 | ## Reserved Subnet Key Names 53 | 54 | There are 3 reserved keys for subnet key names in var.subnets corresponding to types "public", "transit_gateway", and "core_network" [(an AWS Cloud WAN feature)](https://docs.aws.amazon.com/vpc/latest/cloudwan/cloudwan-networks-working-with.html). Other custom subnet key names are valid are and those subnets will be private subnets. 55 | 56 | ```hcl 57 | subnets = { 58 | public = { 59 | name_prefix = "my-public" # omit to prefix with "public" 60 | netmask = 24 61 | nat_gateway_configuration = "all_azs" # options: "single_az", "none" 62 | } 63 | 64 | # naming private is not required, can use any key 65 | private = { 66 | # omitting name_prefix defaults value to "private" 67 | # name_prefix = "private" 68 | netmask = 24 69 | connect_to_public_natgw = true 70 | } 71 | 72 | # can be any valid key name 73 | privatetwo = { 74 | # omitting name_prefix defaults value to "privatetwo" 75 | # name_prefix = "private" 76 | netmask = 24 77 | } 78 | ``` 79 | 80 | ```hcl 81 | transit_gateway_id = <> 82 | transit_gateway_routes = { 83 | private = "0.0.0.0/0" 84 | vpce = "pl-123" 85 | } 86 | transit_gateway_ipv6_routes = { 87 | private = "::/0" 88 | } 89 | 90 | subnets = { 91 | private = { 92 | netmask = 24 93 | assign_ipv6_cidr = true 94 | } 95 | vpce = { netmask = 24} 96 | 97 | transit_gateway = { 98 | netmask = 28 99 | assign_ipv6_cidr = true 100 | transit_gateway_default_route_table_association = true 101 | transit_gateway_default_route_table_propagation = true 102 | transit_gateway_appliance_mode_support = "enable" 103 | transit_gateway_dns_support = "disable" 104 | transit_gateway_security_group_referencing_support = "enable" 105 | 106 | tags = { 107 | subnet_type = "tgw" 108 | } 109 | } 110 | ``` 111 | 112 | ```hcl 113 | core_network = { 114 | id = <> 115 | arn = <> 116 | } 117 | core_network_routes = { 118 | workload = "pl-123" 119 | } 120 | core_network_ipv6_routes = { 121 | workload = "::/0" 122 | } 123 | 124 | subnets = { 125 | workload = { 126 | name_prefix = "workload-private" 127 | netmask = 24 128 | assign_ipv6_cidr = true 129 | } 130 | 131 | core_network = { 132 | netmask = 28 133 | assign_ipv6_cidr = true 134 | appliance_mode_support = false 135 | require_acceptance = true 136 | accept_attachment = true 137 | 138 | tags = { 139 | env = "prod" 140 | } 141 | } 142 | ``` 143 | 144 | ## Updating a VPC with new or removed subnets 145 | 146 | If using `netmask` or `assign_ipv6_cidr` to calculate subnets and you wish to either add or remove subnets (ex: adding / removing an AZ), you may have to change from using `netmask` / `assign_ipv6_cidr` for some subnets and set to explicit instead. Private subnets are always calculated before public. 147 | 148 | When changing to explicit cidrs, subnets are always ordered by AZ. `0` -> a, `1` -> b, etc. 149 | 150 | Example: Changing from 2 azs to 3 151 | 152 | Before: 153 | ```hcl 154 | cidr_block = "10.0.0.0/16" 155 | vpc_assign_generated_ipv6_cidr_block = true 156 | az_count = 2 157 | 158 | subnets = { 159 | public = { 160 | netmask = 24 161 | assign_ipv6_cidr = true 162 | } 163 | 164 | private = { 165 | netmask = 24 166 | assign_ipv6_cidr = true 167 | } 168 | } 169 | ``` 170 | 171 | After: 172 | ```hcl 173 | cidr_block = "10.0.0.0/16" 174 | vpc_assign_generated_ipv6_cidr_block = true 175 | az_count = 3 176 | 177 | subnets = { 178 | public = { 179 | cidrs = ["10.0.0.0/24", "10.0.1.0/24", "10.0.4.0/24"] 180 | ipv6_cidrs = ["2a05:d01c:bc3:b200::/64", "2a05:d01c:bc3:b201::/64", "2a05:d01c:bc3:b204::/64"] 181 | } 182 | 183 | private = { 184 | cidrs = ["10.0.2.0/24", "10.0.3.0/24", "10.0.5.0/24"] 185 | ipv6_cidrs = ["2a05:d01c:bc3:b202::/64", "2a05:d01c:bc3:b203::/64", "2a05:d01c:bc3:b205::/64"] 186 | } 187 | } 188 | ``` 189 | 190 | The above example will cause only creating 2 new subnets in az `c` of the region being used. 191 | 192 | ## Output usage examples 193 | 194 | The outputs in this module attempt to align to a methodology of outputting resource attributes in a reasonable collection. The benefit of this is that, most likely, attributes you want access to are already present without having to create new `output {}` for each possible attribute. The [potential] downside is that you will have to extract it yourself using HCL logic. Below are some common examples: 195 | 196 | For more examples and explanation see [output docs]((https://github.com/aws-ia/terraform-aws-vpc/blob/main/docs/how-to-use-module-outputs.md) 197 | 198 | ### Extracting subnet IDs for private subnets 199 | 200 | Example Configuration: 201 | ```terraform 202 | module "vpc" { 203 | source = "aws-ia/vpc/aws" 204 | version = ">= 4.2.0" 205 | 206 | name = "multi-az-vpc" 207 | cidr_block = "10.0.0.0/20" 208 | az_count = 3 209 | 210 | subnets = { 211 | private = { netmask = 24 } 212 | } 213 | } 214 | ``` 215 | 216 | Extracting subnet_ids to a list (using `terraform console` for example output): 217 | ```terraform 218 | > [ for _, value in module.vpc.private_subnet_attributes_by_az: value.id] 219 | [ 220 | "subnet-04a86315c4839b519", 221 | "subnet-02a7249c8652a7136", 222 | "subnet-09af79b5329b3681f", 223 | ] 224 | ``` 225 | 226 | Alternatively, since these are maps, you can use key in another resource `for_each` loop. The benefit here is that your dependent resource will have keys that match the AZ the subnet is in: 227 | 228 | ```terraform 229 | resource "aws_route53recoveryreadiness_cell" "cell_per_az" { 230 | for_each = module.vpc.private_subnet_attributes_by_az 231 | 232 | cell_name = "${each.key}-failover-cell-for-subnet-${each.value.id}" 233 | } 234 | ... 235 | ``` 236 | 237 | Terraform Plan: 238 | 239 | ```shell 240 | # aws_route53recoveryreadiness_cell.cell_per_az["us-east-1a"] will be created 241 | + resource "aws_route53recoveryreadiness_cell" "cell_per_az" { 242 | + cell_name = "us-east-1a-failover-cell-for-subnet-subnet-070696086c5864da1" 243 | ... 244 | } 245 | 246 | # aws_route53recoveryreadiness_cell.cell_per_az["us-east-1b"] will be created 247 | ... 248 | ``` 249 | 250 | # Common Errors and their Fixes 251 | 252 | ## Error creating routes to Core Network 253 | 254 | Error: 255 | 256 | > error creating Route in Route Table (rtb-xxx) with destination (YYY): InvalidCoreNetworkArn.NotFound: The core network arn 'arn:aws:networkmanager::XXXX:core-network/core-network-YYYYY' does not exist. 257 | 258 | This happens when the Core Network's VPC attachment requires acceptance, so it's not possible to create the routes in the VPC until the attachment is accepted. Check the following: 259 | 260 | * If the VPC attachment requires acceptance and you want the module to automatically accept it, configure `require_acceptance` and `accept_attachment` to `true`. 261 | 262 | ```terraform 263 | subnets = { 264 | core_network = { 265 | netmask = 28 266 | assign_ipv6_cidr = true 267 | require_acceptance = true 268 | accept_attachment = true 269 | } 270 | } 271 | ``` 272 | 273 | * If the VPC attachment requires acceptance but you want to accept it outside the module, first configure `require_acceptance` to `true` and `accept_attachment` to `false`. 274 | 275 | ```terraform 276 | subnets = { 277 | core_network = { 278 | netmask = 28 279 | assign_ipv6_cidr = true 280 | require_acceptance = true 281 | accept_attachment = true 282 | } 283 | } 284 | ``` 285 | 286 | After you apply and the attachment is accepted (outside the module), change the subnet configuration with `require_acceptance` to `false`. 287 | 288 | ```terraform 289 | subnets = { 290 | core_network = { 291 | netmask = 28 292 | assign_ipv6_cidr = true 293 | require_acceptance = false 294 | } 295 | } 296 | ``` 297 | 298 | * Alternatively, you can also not configure any subnet route (`var.core_network_routes`) to the Core Network until the attachment gets accepted. 299 | 300 | # Contributing 301 | 302 | Please see our [developer documentation](https://github.com/aws-ia/terraform-aws-vpc/blob/main/contributing.md) for guidance on contributing to this module. 303 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | variable "name" { 2 | type = string 3 | description = "Name to give VPC. Note: does not effect subnet names, which get assigned name based on name_prefix." 4 | } 5 | 6 | variable "cidr_block" { 7 | description = "IPv4 CIDR range to assign to VPC if creating VPC or to associate as a secondary IPv6 CIDR. Overridden by var.vpc_id output from data.aws_vpc." 8 | default = null 9 | type = string 10 | } 11 | 12 | variable "vpc_id" { 13 | description = "VPC ID to use if not creating VPC." 14 | default = null 15 | type = string 16 | } 17 | 18 | variable "create_vpc" { 19 | description = "Determines whether to create the VPC or not; defaults to enabling the creation." 20 | default = true 21 | type = bool 22 | } 23 | 24 | variable "az_count" { 25 | type = number 26 | description = "Searches region for # of AZs to use and takes a slice based on count. Assume slice is sorted a-z. Required if `azs` is not provided." 27 | default = null 28 | } 29 | 30 | variable "azs" { 31 | type = list(string) 32 | description = "(Optional) A list of AZs to use. e.g. `azs = [\"us-east-1a\",\"us-east-1c\"]` Incompatible with `az_count`" 33 | default = null 34 | } 35 | 36 | variable "vpc_enable_dns_hostnames" { 37 | type = bool 38 | description = "Indicates whether the instances launched in the VPC get DNS hostnames. If enabled, instances in the VPC get DNS hostnames; otherwise, they do not. Disabled by default for nondefault VPCs." 39 | default = true 40 | } 41 | 42 | variable "vpc_secondary_cidr" { 43 | type = bool 44 | description = "If `true` the module will create a `aws_vpc_ipv4_cidr_block_association` and subnets for that secondary cidr. If using IPAM for both primary and secondary CIDRs, you may only call this module serially (aka using `-target`, etc)." 45 | default = false 46 | } 47 | 48 | variable "vpc_secondary_cidr_natgw" { 49 | type = any 50 | description = "If attaching a secondary IPv4 CIDR instead of creating a VPC, you can map private/ tgw subnets to your public NAT GW with this argument. Simply pass the output `nat_gateway_attributes_by_az`, ex: `vpc_secondary_cidr_natgw = module.vpc.natgw_id_per_az`. If you did not build your primary with this module, you must construct a map { az : { id : nat-123asdb }} for each az." 51 | default = {} 52 | } 53 | 54 | variable "vpc_enable_dns_support" { 55 | type = bool 56 | description = "Indicates whether the DNS resolution is supported for the VPC. If enabled, queries to the Amazon provided DNS server at the 169.254.169.253 IP address, or the reserved IP address at the base of the VPC network range \"plus two\" succeed. If disabled, the Amazon provided DNS service in the VPC that resolves public DNS hostnames to IP addresses is not enabled. Enabled by default." 57 | default = true 58 | } 59 | 60 | variable "vpc_instance_tenancy" { 61 | type = string 62 | description = "The allowed tenancy of instances launched into the VPC." 63 | default = "default" 64 | } 65 | 66 | variable "vpc_ipv4_ipam_pool_id" { 67 | description = "Set to use IPAM to get an IPv4 CIDR block." 68 | type = string 69 | default = null 70 | } 71 | 72 | variable "vpc_ipv4_netmask_length" { 73 | description = "Set to use IPAM to get an IPv4 CIDR block using a specified netmask. Must be set with var.vpc_ipv4_ipam_pool_id." 74 | type = string 75 | default = null 76 | } 77 | 78 | variable "vpc_assign_generated_ipv6_cidr_block" { 79 | description = "Requests and Amazon-provided IPv6 CIDR block with a /56 prefix length. You cannot specify the range of IP addresses, or the size of the CIDR block. Conflicts with `vpc_ipv6_ipam_pool_id`." 80 | type = bool 81 | default = null 82 | } 83 | 84 | variable "vpc_ipv6_ipam_pool_id" { 85 | description = "Set to use IPAM to get an IPv6 CIDR block." 86 | type = string 87 | default = null 88 | } 89 | 90 | variable "vpc_ipv6_cidr_block" { 91 | description = "IPv6 CIDR range to assign to VPC if creating VPC. You need to use `vpc_ipv6_ipam_pool_id` and set explicitly the CIDR block to use, or derived from IPAM using using `vpc_ipv6_netmask_length`." 92 | type = string 93 | default = null 94 | } 95 | 96 | variable "vpc_ipv6_netmask_length" { 97 | description = "Set to use IPAM to get an IPv6 CIDR block using a specified netmask. Must be set with `var.vpc_ipv6_ipam_pool_id`." 98 | type = string 99 | default = null 100 | } 101 | 102 | variable "vpc_egress_only_internet_gateway" { 103 | description = "Set to use the Egress-only Internet gateway for all IPv6 traffic going to the Internet." 104 | type = bool 105 | default = false 106 | } 107 | 108 | variable "subnets" { 109 | description = <<-EOF 110 | Configuration of subnets to build in VPC. 1 Subnet per AZ is created. Subnet types are defined as maps with the available keys: "private", "public", "transit_gateway", "core_network". Each Subnet type offers its own set of available arguments detailed below. Subnets are calculated in lexicographical order of the keys in the map. 111 | 112 | **Attributes shared across subnet types:** 113 | - `cidrs` = (Optional|list(string)) **Cannot set if `netmask` is set.** List of IPv4 CIDRs to set to subnets. Count of CIDRs defined must match quantity of azs in `az_count`. 114 | - `netmask` = (Optional|Int) **Cannot set if `cidrs` is set.** Netmask of the `var.cidr_block` to calculate for each subnet. 115 | - `assign_ipv6_cidr` = (Optional|bool) **Cannot set if `ipv6_cidrs` is set.** If true, it will calculate a /64 block from the IPv6 VPC CIDR to set in the subnets. 116 | - `ipv6_cidrs` = (Optional|list(string)) **Cannot set if `assign_ipv6_cidr` is set.** List of IPv6 CIDRs to set to subnets. The subnet size must use a /64 prefix length. Count of CIDRs defined must match quantity of azs in `az_count`. 117 | - `name_prefix` = (Optional|String) A string prefix to use for the name of your subnet and associated resources. Subnet type key name is used if omitted (aka private, public, transit_gateway). Example `name_prefix = "private"` for `var.subnets.private` is redundant. 118 | - `tags` = (Optional|map(string)) Tags to set on the subnet and associated resources. 119 | 120 | **Any private subnet type options:** 121 | - All shared keys above 122 | - `connect_to_public_natgw` = (Optional|bool) Determines if routes to NAT Gateways should be created. Must also set `var.subnets.public.nat_gateway_configuration` in public subnets. 123 | - `ipv6_native` = (Optional|bool) Indicates whether to create an IPv6-ony subnet. Either `var.assign_ipv6_cidr` or `var.ipv6_cidrs` should be defined to allocate an IPv6 CIDR block. 124 | - `connect_to_eigw` = (Optional|bool) Determines if routes to the Egress-only Internet gateway should be created. Must also set `var.vpc_egress_only_internet_gateway`. 125 | 126 | **public subnet type options:** 127 | - All shared keys above 128 | - `nat_gateway_configuration` = (Optional|string) Determines if NAT Gateways should be created and in how many AZs. Valid values = `"none"`, `"single_az"`, `"all_azs"`. Default = "none". Must also set `var.subnets.private.connect_to_public_natgw = true`. 129 | - `connect_to_igw` = (Optional|bool) Determines if the default route (0.0.0.0/0 or ::/0) is created in the public subnets with destination the Internet gateway. Defaults to `true`. 130 | - `ipv6_native` = (Optional|bool) Indicates whether to create an IPv6-ony subnet. Either `var.assign_ipv6_cidr` or `var.ipv6_cidrs` should be defined to allocate an IPv6 CIDR block. 131 | - `map_public_ip_on_launch` = (Optional|bool) Specify true to indicate that instances launched into the subnet should be assigned a public IP address. Default to `false`. 132 | 133 | **transit_gateway subnet type options:** 134 | - All shared keys above 135 | - `connect_to_public_natgw` = (Optional|string) Determines if routes to NAT Gateways should be created. Specify the CIDR range or a prefix-list-id that you want routed to nat gateway. Usually `0.0.0.0/0`. Must also set `var.subnets.public.nat_gateway_configuration`. 136 | - `transit_gateway_default_route_table_association` = (Optional|bool) Boolean whether the VPC Attachment should be associated with the EC2 Transit Gateway association default route table. This cannot be configured or perform drift detection with Resource Access Manager shared EC2 Transit Gateways. 137 | - `transit_gateway_default_route_table_propagation` = (Optional|bool) Boolean whether the VPC Attachment should propagate routes with the EC2 Transit Gateway propagation default route table. This cannot be configured or perform drift detection with Resource Access Manager shared EC2 Transit Gateways. 138 | - `transit_gateway_appliance_mode_support` = (Optional|string) Whether Appliance Mode is enabled. If enabled, a traffic flow between a source and a destination uses the same Availability Zone for the VPC attachment for the lifetime of that flow. Valid values: `disable` (default) and `enable`. 139 | - `transit_gateway_dns_support` = (Optional|string) DNS Support is used if you need the VPC to resolve public IPv4 DNS host names to private IPv4 addresses when queried from instances in another VPC attached to the transit gateway. Valid values: `enable` (default) and `disable`. 140 | - `transit_gateway_security_group_referencing_support` = (Optional|string) Security group referencing support enables you to simplify Security group management and control of instance-to-instance traffic across VPCs that are connected by Transit gateway. Valid values: `disable` and `enable` (default). 141 | 142 | **core_network subnet type options:** 143 | - All shared keys abovce 144 | - `connect_to_public_natgw` = (Optional|string) Determines if routes to NAT Gateways should be created. Specify the CIDR range or a prefix-list-id that you want routed to nat gateway. Usually `0.0.0.0/0`. Must also set `var.subnets.public.nat_gateway_configuration`. 145 | - `appliance_mode_support` = (Optional|bool) Indicates whether appliance mode is supported. If enabled, traffic flow between a source and destination use the same Availability Zone for the VPC attachment for the lifetime of that flow. Defaults to `false`. 146 | - `require_acceptance` = (Optional|bool) Boolean whether the core network VPC attachment to create requires acceptance or not. Defaults to `false`. 147 | - `accept_attachment` = (Optional|bool) Boolean whether the core network VPC attachment is accepted or not in the segment. Only valid if `require_acceptance` is set to `true`. Defaults to `true`. 148 | 149 | Example: 150 | ``` 151 | subnets = { 152 | # Dual-stack subnet 153 | public = { 154 | netmask = 24 155 | assign_ipv6_cidr = true 156 | nat_gateway_configuration = "single_az" 157 | } 158 | # IPv4 only subnet 159 | private = { 160 | netmask = 24 161 | connect_to_public_natgw = true 162 | } 163 | # IPv6 only subnet 164 | ipv6 = { 165 | ipv6_native = true 166 | assign_ipv6_cidr = true 167 | connect_to_eigw = true 168 | } 169 | # Transit gateway subnets (dual-stack) 170 | transit_gateway = { 171 | netmask = 24 172 | assign_ipv6_cidr = true 173 | connect_to_public_natgw = true 174 | transit_gateway_default_route_table_association = true 175 | transit_gateway_default_route_table_propagation = true 176 | } 177 | # Core Network subnets (dual-stack) 178 | core_network = { 179 | netmask = 24 180 | assign_ipv6_cidr = true 181 | connect_to_public_natgw = true 182 | appliance_mode_support = true 183 | require_acceptance = true 184 | accept_attachment = true 185 | } 186 | } 187 | ``` 188 | EOF 189 | type = any 190 | 191 | # All var.subnets.public valid keys 192 | validation { 193 | error_message = "Invalid key in public subnets. Valid options include: \"cidrs\", \"netmask\", \"name_prefix\", \"connect_to_igw\", \"nat_gateway_configuration\", \"ipv6_native\", \"assign_ipv6_cidr\", \"ipv6_cidrs\", \"tags\"." 194 | condition = length(setsubtract(keys(try(var.subnets.public, {})), [ 195 | "cidrs", 196 | "netmask", 197 | "name_prefix", 198 | "connect_to_igw", 199 | "nat_gateway_configuration", 200 | "ipv6_native", 201 | "assign_ipv6_cidr", 202 | "ipv6_cidrs", 203 | "map_public_ip_on_launch", 204 | "tags" 205 | ])) == 0 206 | } 207 | 208 | # All var.subnets.transit_gateway valid keys 209 | validation { 210 | error_message = "Invalid key in transit_gateway subnets. Valid options include: \"cidrs\", \"netmask\", \"name_prefix\", \"connect_to_public_natgw\", \"assign_ipv6_cidr\", \"ipv6_cidrs\", \"transit_gateway_default_route_table_association\", \"transit_gateway_default_route_table_propagation\", \"transit_gateway_appliance_mode_support\", \"transit_gateway_dns_support\", \"transit_gateway_security_group_referencing_support\", \"tags\"." 211 | condition = length(setsubtract(keys(try(var.subnets.transit_gateway, {})), [ 212 | "cidrs", 213 | "netmask", 214 | "name_prefix", 215 | "connect_to_public_natgw", 216 | "assign_ipv6_cidr", 217 | "ipv6_cidrs", 218 | "transit_gateway_default_route_table_association", 219 | "transit_gateway_default_route_table_propagation", 220 | "transit_gateway_appliance_mode_support", 221 | "transit_gateway_dns_support", 222 | "transit_gateway_security_group_referencing_support", 223 | "tags" 224 | ])) == 0 225 | } 226 | 227 | # All var.subnets.core_network valid keys 228 | validation { 229 | error_message = "Invalid key in core_network subnets. Valid options include: \"cidrs\", \"netmask\", \"name_prefix\", \"connect_to_public_natgw\", \"assign_ipv6_cidr\", \"ipv6_cidrs\", \"appliance_mode_support\", \"require_acceptance\", \"accept_attachment\", \"tags\"." 230 | condition = length(setsubtract(keys(try(var.subnets.core_network, {})), [ 231 | "cidrs", 232 | "netmask", 233 | "name_prefix", 234 | "connect_to_public_natgw", 235 | "assign_ipv6_cidr", 236 | "ipv6_cidrs", 237 | "appliance_mode_support", 238 | "require_acceptance", 239 | "accept_attachment", 240 | "tags" 241 | ])) == 0 242 | } 243 | 244 | validation { 245 | error_message = "Each subnet type must contain only 1 key: `cidrs` or `netmask` or `ipv6_native`." 246 | condition = alltrue([for subnet_type, v in var.subnets : length(setintersection(keys(v), ["cidrs", "netmask", "ipv6_native"])) == 1]) 247 | } 248 | 249 | validation { 250 | error_message = "Public subnet `nat_gateway_configuration` can only be `all_azs`, `single_az`, `none`, or `null`." 251 | condition = can(regex("^(all_azs|single_az|none)$", var.subnets.public.nat_gateway_configuration)) || try(var.subnets.public.nat_gateway_configuration, null) == null 252 | } 253 | 254 | validation { 255 | error_message = "Any subnet type `name_prefix` must not contain \"/\"." 256 | condition = alltrue([for _, v in var.subnets : !can(regex("/", try(v.name_prefix, "")))]) 257 | } 258 | } 259 | 260 | variable "optimize_subnet_cidr_ranges" { 261 | description = "Sort subnets to calculate by their netmask to efficiently use IP space." 262 | type = bool 263 | default = false 264 | } 265 | 266 | variable "tags" { 267 | description = "Tags to apply to all resources." 268 | type = map(string) 269 | default = {} 270 | } 271 | 272 | variable "vpc_flow_logs" { 273 | description = "Whether or not to create VPC flow logs and which type. Options: \"cloudwatch\", \"s3\", \"none\". By default creates flow logs to `cloudwatch`. Variable overrides null value types for some keys, defined in defaults.tf." 274 | 275 | type = object({ 276 | name_override = optional(string, "") 277 | log_destination = optional(string) 278 | iam_role_arn = optional(string) 279 | kms_key_id = optional(string) 280 | 281 | log_destination_type = string 282 | log_format = optional(string) 283 | retention_in_days = optional(number) 284 | log_bucket_lifecycle_filter_prefix = optional(string, null) 285 | tags = optional(map(string)) 286 | traffic_type = optional(string, "ALL") 287 | 288 | destination_options = optional(object({ 289 | file_format = optional(string, "plain-text") 290 | hive_compatible_partitions = optional(bool, false) 291 | per_hour_partition = optional(bool, false) 292 | }), 293 | { 294 | file_format = "plain-text" 295 | hive_compatible_partitions = false 296 | per_hour_partition = false 297 | }) 298 | }) 299 | 300 | default = { 301 | log_destination_type = "none" 302 | } 303 | 304 | validation { 305 | condition = contains(["cloud-watch-logs", "s3", "none"], var.vpc_flow_logs.log_destination_type) 306 | error_message = "Invalid input, options: \"cloud-watch-logs\", \"s3\", or \"none\"." 307 | } 308 | } 309 | 310 | variable "transit_gateway_id" { 311 | type = string 312 | description = "Transit gateway id to attach the VPC to. Required when `transit_gateway` subnet is defined." 313 | default = null 314 | } 315 | 316 | variable "transit_gateway_routes" { 317 | description = <<-EOF 318 | Configuration of route(s) to transit gateway. 319 | For each `public` and/or `private` subnets named in the `subnets` variable, 320 | Optionally create routes from the subnet to transit gateway. Specify the CIDR range or a prefix-list-id that you want routed to the transit gateway. 321 | Example: 322 | ``` 323 | transit_gateway_routes = { 324 | public = "10.0.0.0/8" 325 | private = "pl-123" 326 | } 327 | ``` 328 | EOF 329 | type = any 330 | default = {} 331 | } 332 | 333 | variable "transit_gateway_ipv6_routes" { 334 | description = <<-EOF 335 | Configuration of IPv6 route(s) to transit gateway. 336 | For each `public` and/or `private` subnets named in the `subnets` variable, 337 | Optionally create routes from the subnet to transit gateway. Specify the CIDR range or a prefix-list-id that you want routed to the transit gateway. 338 | Example: 339 | ``` 340 | transit_gateway_ipv6_routes = { 341 | public = "::/0" 342 | private = "pl-123" 343 | } 344 | ``` 345 | EOF 346 | type = any 347 | default = {} 348 | } 349 | 350 | variable "core_network" { 351 | type = object({ 352 | id = string 353 | arn = string 354 | }) 355 | description = "AWS Cloud WAN's core network information - to create a VPC attachment. Required when `cloud_wan` subnet is defined. Two attributes are required: the `id` and `arn` of the resource." 356 | 357 | default = { 358 | id = null 359 | arn = null 360 | } 361 | } 362 | 363 | variable "core_network_routes" { 364 | description = <<-EOF 365 | Configuration of route(s) to AWS Cloud WAN's core network. 366 | For each `public` and/or `private` subnets named in the `subnets` variable, optionally create routes from the subnet to the core network. 367 | You can specify either a CIDR range or a prefix-list-id that you want routed to the core network. 368 | Example: 369 | ``` 370 | core_network_routes = { 371 | public = "10.0.0.0/8" 372 | private = "pl-123" 373 | } 374 | ``` 375 | EOF 376 | type = any 377 | default = {} 378 | } 379 | 380 | variable "core_network_ipv6_routes" { 381 | description = <<-EOF 382 | Configuration of IPv6 route(s) to AWS Cloud WAN's core network. 383 | For each `public` and/or `private` subnets named in the `subnets` variable, optionally create routes from the subnet to the core network. 384 | You can specify either a CIDR range or a prefix-list-id that you want routed to the core network. 385 | Example: 386 | ``` 387 | core_network_ivp6_routes = { 388 | public = "::/0" 389 | private = "pl-123" 390 | } 391 | ``` 392 | EOF 393 | type = any 394 | default = {} 395 | } 396 | 397 | variable "vpc_lattice" { 398 | description = <<-EOF 399 | Amazon VPC Lattice Service Network VPC association. You can only associate one Service Network to the VPC. This association also support Security Groups (more than 1). 400 | This variable expects the following attributes: 401 | - `service_network_identifier` = (Required|string) The ID or ARN of the Service Network to associate. You must use the ARN if the Service Network and VPC resources are in different AWS Accounts. 402 | - `security_group_ids` = (Optional|list(string)) The IDs of the security groups to attach to the association. 403 | - `tags` = (Optional|map(string)) Tags to set on the Lattice VPC association resource. 404 | EOF 405 | type = any 406 | 407 | default = {} 408 | 409 | # All var.vpc_lattice valid keys 410 | validation { 411 | error_message = "Invalid key in var.vpc_lattice. Valid options include: \"service_network_identifier\", \"security_group_ids\", \"tags\"." 412 | condition = length(setsubtract(keys(var.vpc_lattice), [ 413 | "service_network_identifier", 414 | "security_group_ids", 415 | "tags" 416 | ])) == 0 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /main.tf: -------------------------------------------------------------------------------- 1 | 2 | # ---------- SUBNET CALCULATOR (IPv4 AND IPv6) ---------- 3 | 4 | module "calculate_subnets" { 5 | source = "./modules/calculate_subnets" 6 | 7 | cidr = local.cidr_block 8 | azs = local.azs 9 | 10 | subnets = var.subnets 11 | optimize_subnet_cidr_ranges = var.optimize_subnet_cidr_ranges 12 | } 13 | 14 | module "calculate_subnets_ipv6" { 15 | source = "./modules/calculate_subnets_ipv6" 16 | 17 | cidr_ipv6 = local.vpc_ipv6_cidr_block 18 | azs = local.azs 19 | 20 | subnets = var.subnets 21 | } 22 | 23 | # ---------- VPC RESOURCE ---------- 24 | # flow logs optionally enabled by standalone resource 25 | #tfsec:ignore:aws-ec2-require-vpc-flow-logs-for-all-vpcs 26 | resource "aws_vpc" "main" { 27 | count = var.create_vpc ? 1 : 0 28 | 29 | cidr_block = var.cidr_block 30 | ipv4_ipam_pool_id = var.vpc_ipv4_ipam_pool_id 31 | ipv4_netmask_length = var.vpc_ipv4_netmask_length 32 | assign_generated_ipv6_cidr_block = var.vpc_assign_generated_ipv6_cidr_block 33 | ipv6_cidr_block = var.vpc_ipv6_cidr_block 34 | ipv6_ipam_pool_id = var.vpc_ipv6_ipam_pool_id 35 | ipv6_netmask_length = var.vpc_ipv6_netmask_length 36 | 37 | enable_dns_hostnames = var.vpc_enable_dns_hostnames 38 | enable_dns_support = var.vpc_enable_dns_support 39 | instance_tenancy = var.vpc_instance_tenancy 40 | 41 | tags = merge( 42 | { "Name" = var.name }, 43 | module.tags.tags_aws 44 | ) 45 | } 46 | 47 | # ---------- SECONDARY IPv4 CIDR BLOCK (if configured) ---------- 48 | resource "aws_vpc_ipv4_cidr_block_association" "secondary" { 49 | count = (var.vpc_secondary_cidr && !var.create_vpc) ? 1 : 0 50 | 51 | vpc_id = var.vpc_id 52 | cidr_block = local.cidr_block 53 | ipv4_ipam_pool_id = var.vpc_ipv4_ipam_pool_id 54 | } 55 | 56 | # ---------- PUBLIC SUBNET CONFIGURATION ---------- 57 | # Public Subnets 58 | resource "aws_subnet" "public" { 59 | for_each = contains(local.subnet_keys, "public") ? toset(local.azs) : toset([]) 60 | 61 | availability_zone = each.key 62 | vpc_id = local.vpc.id 63 | cidr_block = can(local.calculated_subnets["public"][each.key]) ? local.calculated_subnets["public"][each.key] : null 64 | ipv6_cidr_block = can(local.calculated_subnets_ipv6["public"][each.key]) ? local.calculated_subnets_ipv6["public"][each.key] : null 65 | ipv6_native = contains(local.subnets_with_ipv6_native, "public") ? true : false 66 | map_public_ip_on_launch = try(var.subnets.public.map_public_ip_on_launch, local.public_ipv6only ? null : true) 67 | assign_ipv6_address_on_creation = local.public_ipv6only || local.public_dualstack ? true : null 68 | enable_resource_name_dns_aaaa_record_on_launch = local.public_ipv6only || local.public_dualstack ? true : false 69 | 70 | tags = merge( 71 | { Name = "${local.subnet_names["public"]}-${each.key}" }, 72 | module.tags.tags_aws, 73 | try(module.subnet_tags["public"].tags_aws, {}) 74 | ) 75 | } 76 | 77 | # Public subnet Route Table and association 78 | resource "aws_route_table" "public" { 79 | for_each = contains(local.subnet_keys, "public") ? toset(local.azs) : toset([]) 80 | 81 | vpc_id = local.vpc.id 82 | 83 | tags = merge( 84 | { Name = "${local.subnet_names["public"]}-${each.key}" }, 85 | module.tags.tags_aws, 86 | try(module.subnet_tags["public"].tags_aws, {}) 87 | ) 88 | } 89 | 90 | resource "aws_route_table_association" "public" { 91 | for_each = contains(local.subnet_keys, "public") ? toset(local.azs) : toset([]) 92 | 93 | subnet_id = aws_subnet.public[each.key].id 94 | route_table_id = aws_route_table.public[each.key].id 95 | } 96 | 97 | # Elastic IP - used in NAT gateways (if configured) 98 | resource "aws_eip" "nat" { 99 | for_each = toset(local.nat_configuration) 100 | domain = "vpc" 101 | 102 | tags = merge( 103 | { Name = "nat-${local.subnet_names["public"]}-${each.key}" }, 104 | module.tags.tags_aws, 105 | try(module.subnet_tags["public"].tags_aws, {}) 106 | ) 107 | } 108 | 109 | # NAT gateways (if configured) 110 | resource "aws_nat_gateway" "main" { 111 | for_each = toset(local.nat_configuration) 112 | 113 | allocation_id = aws_eip.nat[each.key].id 114 | subnet_id = aws_subnet.public[each.key].id 115 | 116 | tags = merge( 117 | { Name = "nat-${local.subnet_names["public"]}-${each.key}" }, 118 | module.tags.tags_aws, 119 | try(module.subnet_tags["public"].tags_aws, {}) 120 | ) 121 | 122 | depends_on = [ 123 | aws_internet_gateway.main 124 | ] 125 | } 126 | 127 | # Internet gateway (if public subnets are created) 128 | resource "aws_internet_gateway" "main" { 129 | count = contains(local.subnet_keys, "public") ? 1 : 0 130 | vpc_id = local.vpc.id 131 | 132 | tags = merge( 133 | { Name = "${var.name}-igw" }, 134 | module.tags.tags_aws, 135 | try(module.subnet_tags["public"].tags_aws, {}) 136 | ) 137 | } 138 | 139 | # Egress-only IGW (if indicated) 140 | resource "aws_egress_only_internet_gateway" "eigw" { 141 | count = var.vpc_egress_only_internet_gateway ? 1 : 0 142 | vpc_id = local.vpc.id 143 | 144 | tags = merge( 145 | { "Name" = var.name }, 146 | module.tags.tags_aws 147 | ) 148 | } 149 | 150 | # Route: 0.0.0.0/0 from public subnets to the Internet gateway 151 | resource "aws_route" "public_to_igw" { 152 | for_each = contains(local.subnet_keys, "public") && !local.public_ipv6only && local.connect_to_igw ? toset(local.azs) : toset([]) 153 | 154 | route_table_id = aws_route_table.public[each.key].id 155 | destination_cidr_block = "0.0.0.0/0" 156 | gateway_id = aws_internet_gateway.main[0].id 157 | } 158 | 159 | # Route: ::/0 from public subnets to the Internet gateway 160 | resource "aws_route" "public_ipv6_to_igw" { 161 | for_each = contains(local.subnet_keys, "public") && (local.public_ipv6only || local.public_dualstack) && local.connect_to_igw ? toset(local.azs) : toset([]) 162 | 163 | route_table_id = aws_route_table.public[each.key].id 164 | destination_ipv6_cidr_block = "::/0" 165 | gateway_id = aws_internet_gateway.main[0].id 166 | } 167 | 168 | # Route: IPv4 routes from public subnets to the Transit Gateway (if configured in var.transit_gateway_routes) 169 | resource "aws_route" "public_to_tgw" { 170 | for_each = (contains(local.subnet_keys, "public") && contains(local.subnets_tgw_routed, "public")) ? toset(local.azs) : toset([]) 171 | 172 | destination_cidr_block = can(regex("^pl-", var.transit_gateway_routes["public"])) ? null : var.transit_gateway_routes["public"] 173 | destination_prefix_list_id = can(regex("^pl-", var.transit_gateway_routes["public"])) ? var.transit_gateway_routes["public"] : null 174 | 175 | transit_gateway_id = var.transit_gateway_id 176 | route_table_id = aws_route_table.public[each.key].id 177 | 178 | depends_on = [ 179 | aws_ec2_transit_gateway_vpc_attachment.tgw 180 | ] 181 | } 182 | 183 | # Route: IPv6 routes from public subnets to the Transit Gateway (if configured in var.transit_gateway_ipv6_routes) 184 | resource "aws_route" "ipv6_public_to_tgw" { 185 | for_each = (contains(local.subnet_keys, "public") && contains(local.ipv6_subnets_tgw_routed, "public")) ? toset(local.azs) : toset([]) 186 | 187 | destination_ipv6_cidr_block = can(regex("^pl-", var.transit_gateway_ipv6_routes["public"])) ? null : var.transit_gateway_ipv6_routes["public"] 188 | destination_prefix_list_id = can(regex("^pl-", var.transit_gateway_ipv6_routes["public"])) ? var.transit_gateway_ipv6_routes["public"] : null 189 | 190 | transit_gateway_id = var.transit_gateway_id 191 | route_table_id = aws_route_table.public[each.key].id 192 | 193 | depends_on = [ 194 | aws_ec2_transit_gateway_vpc_attachment.tgw 195 | ] 196 | } 197 | 198 | # Route: IPv4 routes from public subnets to AWS Cloud WAN's core network (if configured in var.core_network_routes) 199 | resource "aws_route" "public_to_cwan" { 200 | for_each = (contains(local.subnet_keys, "public") && contains(local.subnets_cwan_routed, "public") && local.create_cwan_routes) ? toset(local.azs) : toset([]) 201 | 202 | destination_cidr_block = can(regex("^pl-", var.core_network_routes["public"])) ? null : var.core_network_routes["public"] 203 | destination_prefix_list_id = can(regex("^pl-", var.core_network_routes["public"])) ? var.core_network_routes["public"] : null 204 | 205 | core_network_arn = var.core_network.arn 206 | route_table_id = aws_route_table.public[each.key].id 207 | 208 | depends_on = [ 209 | aws_networkmanager_vpc_attachment.cwan, 210 | aws_networkmanager_attachment_accepter.cwan 211 | ] 212 | } 213 | 214 | # Route: IPv6 routes from public subnets to AWS Cloud WAN's core network (if configured in var.core_network_routes) 215 | resource "aws_route" "ipv6_public_to_cwan" { 216 | for_each = (contains(local.subnet_keys, "public") && contains(local.ipv6_subnets_cwan_routed, "public") && local.create_cwan_routes) ? toset(local.azs) : toset([]) 217 | 218 | destination_ipv6_cidr_block = can(regex("^pl-", var.core_network_ipv6_routes["public"])) ? null : var.core_network_ipv6_routes["public"] 219 | destination_prefix_list_id = can(regex("^pl-", var.core_network_ipv6_routes["public"])) ? var.core_network_ipv6_routes["public"] : null 220 | 221 | core_network_arn = var.core_network.arn 222 | route_table_id = aws_route_table.public[each.key].id 223 | 224 | depends_on = [ 225 | aws_networkmanager_vpc_attachment.cwan, 226 | aws_networkmanager_attachment_accepter.cwan 227 | ] 228 | } 229 | 230 | # ---------- PRIVATE SUBNETS CONFIGURATION ---------- 231 | # Private Subnets 232 | resource "aws_subnet" "private" { 233 | for_each = toset(try(local.private_per_az, [])) 234 | 235 | availability_zone = split("/", each.key)[1] 236 | vpc_id = local.vpc.id 237 | cidr_block = can(local.calculated_subnets[split("/", each.key)[0]][split("/", each.key)[1]]) ? local.calculated_subnets[split("/", each.key)[0]][split("/", each.key)[1]] : null 238 | ipv6_cidr_block = can(local.calculated_subnets_ipv6[split("/", each.key)[0]][split("/", each.key)[1]]) ? local.calculated_subnets_ipv6[split("/", each.key)[0]][split("/", each.key)[1]] : null 239 | ipv6_native = contains(local.subnets_with_ipv6_native, split("/", each.key)[0]) ? true : false 240 | map_public_ip_on_launch = contains(local.subnets_with_ipv6_native, split("/", each.key)[0]) ? null : false 241 | assign_ipv6_address_on_creation = contains(local.subnets_with_ipv6_native, split("/", each.key)[0]) ? true : try(var.subnets[split("/", each.key)[0]].assign_ipv6_address_on_creation, false) 242 | enable_resource_name_dns_aaaa_record_on_launch = contains(local.subnets_with_ipv6_native, split("/", each.key)[0]) ? true : try(var.subnets[split("/", each.key)[0]].enable_resource_name_dns_aaaa_record_on_launch, false) 243 | 244 | tags = merge( 245 | { Name = "${local.subnet_names[split("/", each.key)[0]]}-${split("/", each.key)[1]}" }, 246 | module.tags.tags_aws, 247 | try(module.subnet_tags[split("/", each.key)[0]].tags_aws, {}) 248 | ) 249 | 250 | depends_on = [ 251 | aws_vpc_ipv4_cidr_block_association.secondary 252 | ] 253 | } 254 | 255 | # Private subnet Route Table and association 256 | resource "aws_route_table" "private" { 257 | for_each = toset(try(local.private_per_az, [])) 258 | 259 | vpc_id = local.vpc.id 260 | 261 | tags = merge( 262 | { Name = "${local.subnet_names[split("/", each.key)[0]]}-${split("/", each.key)[1]}" }, 263 | module.tags.tags_aws, 264 | try(module.subnet_tags[split("/", each.key)[0]].tags_aws, {}) 265 | ) 266 | } 267 | 268 | resource "aws_route_table_association" "private" { 269 | for_each = toset(try(local.private_per_az, [])) 270 | 271 | subnet_id = aws_subnet.private[each.key].id 272 | route_table_id = aws_route_table.private[each.key].id 273 | } 274 | 275 | # Route: from the private subnet to the NAT gateway (if Internet access configured) 276 | resource "aws_route" "private_to_nat" { 277 | for_each = toset(try(local.private_subnet_names_nat_routed, [])) 278 | 279 | route_table_id = aws_route_table.private[each.key].id 280 | destination_cidr_block = "0.0.0.0/0" 281 | # try to get nat for AZ, else use singular nat 282 | nat_gateway_id = local.nat_per_az[split("/", each.key)[1]].id 283 | } 284 | 285 | # Route: from the private subnet to the Egress-only IGW (if configured) 286 | resource "aws_route" "private_to_egress_only" { 287 | for_each = toset(try(local.private_subnet_names_egress_routed, [])) 288 | 289 | route_table_id = aws_route_table.private[each.key].id 290 | destination_ipv6_cidr_block = "::/0" 291 | egress_only_gateway_id = aws_egress_only_internet_gateway.eigw[0].id 292 | } 293 | 294 | # Route: IPv4 routes from private subnets to the Transit Gateway (if configured in var.transit_gateway_routes) 295 | resource "aws_route" "private_to_tgw" { 296 | for_each = toset(local.private_subnet_key_names_tgw_routed) 297 | 298 | destination_cidr_block = can(regex("^pl-", var.transit_gateway_routes[split("/", each.key)[0]])) ? null : var.transit_gateway_routes[split("/", each.key)[0]] 299 | destination_prefix_list_id = can(regex("^pl-", var.transit_gateway_routes[split("/", each.key)[0]])) ? var.transit_gateway_routes[split("/", each.key)[0]] : null 300 | 301 | route_table_id = aws_route_table.private[each.key].id 302 | transit_gateway_id = var.transit_gateway_id 303 | 304 | depends_on = [ 305 | aws_ec2_transit_gateway_vpc_attachment.tgw 306 | ] 307 | } 308 | 309 | # Route: IPv6 routes from private subnets to the Transit Gateway (if configured in var.transit_gateway_ipv6_routes) 310 | resource "aws_route" "ipv6_private_to_tgw" { 311 | for_each = toset(local.ipv6_private_subnet_key_names_tgw_routed) 312 | 313 | destination_ipv6_cidr_block = can(regex("^pl-", var.transit_gateway_ipv6_routes[split("/", each.key)[0]])) ? null : var.transit_gateway_ipv6_routes[split("/", each.key)[0]] 314 | destination_prefix_list_id = can(regex("^pl-", var.transit_gateway_ipv6_routes[split("/", each.key)[0]])) ? var.transit_gateway_ipv6_routes[split("/", each.key)[0]] : null 315 | 316 | route_table_id = aws_route_table.private[each.key].id 317 | transit_gateway_id = var.transit_gateway_id 318 | 319 | depends_on = [ 320 | aws_ec2_transit_gateway_vpc_attachment.tgw 321 | ] 322 | } 323 | 324 | # Route: IPv4 routes from private subnets to AWS Cloud WAN's core network (if configured in var.core_network_routes) 325 | resource "aws_route" "private_to_cwan" { 326 | for_each = { 327 | for k, v in toset(local.private_subnet_key_names_cwan_routes) : k => v 328 | if local.create_cwan_routes 329 | } 330 | 331 | destination_cidr_block = can(regex("^pl-", var.core_network_routes[split("/", each.key)[0]])) ? null : var.core_network_routes[split("/", each.key)[0]] 332 | destination_prefix_list_id = can(regex("^pl-", var.core_network_routes[split("/", each.key)[0]])) ? var.core_network_routes[split("/", each.key)[0]] : null 333 | 334 | core_network_arn = var.core_network.arn 335 | route_table_id = aws_route_table.private[each.key].id 336 | 337 | depends_on = [ 338 | aws_networkmanager_vpc_attachment.cwan, 339 | aws_networkmanager_attachment_accepter.cwan 340 | ] 341 | } 342 | 343 | # Route: IPv6 routes from private subnets to AWS Cloud WAN's core network (if configured in var.core_network_routes) 344 | resource "aws_route" "ipv6_private_to_cwan" { 345 | for_each = { 346 | for k, v in toset(local.ipv6_private_subnet_keys_names_cwan_routes) : k => v 347 | if local.create_cwan_routes 348 | } 349 | 350 | destination_ipv6_cidr_block = can(regex("^pl-", var.core_network_ipv6_routes[split("/", each.key)[0]])) ? null : var.core_network_ipv6_routes[split("/", each.key)[0]] 351 | destination_prefix_list_id = can(regex("^pl-", var.core_network_ipv6_routes[split("/", each.key)[0]])) ? var.core_network_ipv6_routes[split("/", each.key)[0]] : null 352 | 353 | core_network_arn = var.core_network.arn 354 | route_table_id = aws_route_table.private[each.key].id 355 | 356 | depends_on = [ 357 | aws_networkmanager_vpc_attachment.cwan, 358 | aws_networkmanager_attachment_accepter.cwan 359 | ] 360 | } 361 | 362 | # ---------- TRANSIT GATEWAY SUBNET CONFIGURATION ---------- 363 | # Transit Gateway Subnets 364 | resource "aws_subnet" "tgw" { 365 | for_each = contains(local.subnet_keys, "transit_gateway") ? toset(local.azs) : toset([]) 366 | 367 | availability_zone = each.key 368 | vpc_id = local.vpc.id 369 | cidr_block = local.calculated_subnets["transit_gateway"][each.key] 370 | ipv6_cidr_block = can(local.calculated_subnets_ipv6["transit_gateway"][each.key]) ? local.calculated_subnets_ipv6["transit_gateway"][each.key] : null 371 | 372 | tags = merge( 373 | { Name = "${local.subnet_names["transit_gateway"]}-${each.key}" }, 374 | module.tags.tags_aws, 375 | try(module.subnet_tags["transit_gateway"].tags_aws, {}) 376 | ) 377 | 378 | } 379 | 380 | # Transit Gateway subnet Route Table and association 381 | resource "aws_route_table" "tgw" { 382 | for_each = contains(local.subnet_keys, "transit_gateway") ? toset(local.azs) : toset([]) 383 | 384 | vpc_id = local.vpc.id 385 | 386 | tags = merge( 387 | { Name = "${local.subnet_names["transit_gateway"]}-${each.key}" }, 388 | module.tags.tags_aws, 389 | try(module.subnet_tags["transit_gateway"].tags_aws, {}) 390 | ) 391 | } 392 | 393 | resource "aws_route_table_association" "tgw" { 394 | for_each = contains(local.subnet_keys, "transit_gateway") ? toset(local.azs) : toset([]) 395 | 396 | subnet_id = aws_subnet.tgw[each.key].id 397 | route_table_id = aws_route_table.tgw[each.key].id 398 | } 399 | 400 | # Route: from transit_gateway subnet to NAT gateway (if Internet access configured) 401 | resource "aws_route" "tgw_to_nat" { 402 | for_each = (try(var.subnets.transit_gateway.connect_to_public_natgw == true, false) && contains(local.subnet_keys, "public")) ? toset(local.azs) : toset([]) 403 | 404 | route_table_id = aws_route_table.tgw[each.key].id 405 | destination_cidr_block = "0.0.0.0/0" 406 | # try to get nat for AZ, else use singular nat 407 | nat_gateway_id = local.nat_per_az[each.key].id 408 | } 409 | 410 | # Transit Gateway VPC attachment 411 | resource "aws_ec2_transit_gateway_vpc_attachment" "tgw" { 412 | count = contains(local.subnet_keys, "transit_gateway") ? 1 : 0 413 | 414 | subnet_ids = values(aws_subnet.tgw)[*].id 415 | transit_gateway_id = var.transit_gateway_id 416 | vpc_id = local.vpc.id 417 | 418 | transit_gateway_default_route_table_association = try(var.subnets.transit_gateway.transit_gateway_default_route_table_association, null) 419 | transit_gateway_default_route_table_propagation = try(var.subnets.transit_gateway.transit_gateway_default_route_table_propagation, null) 420 | appliance_mode_support = try(var.subnets.transit_gateway.transit_gateway_appliance_mode_support, "disable") 421 | dns_support = try(var.subnets.transit_gateway.transit_gateway_dns_support, "enable") 422 | ipv6_support = local.tgw_dualstack ? "enable" : "disable" 423 | security_group_referencing_support = try(var.subnets.transit_gateway.transit_gateway_security_group_referencing_support, "enable") 424 | 425 | tags = merge( 426 | { Name = "${var.name}-vpc_attachment" }, 427 | module.tags.tags_aws, 428 | try(module.subnet_tags["transit_gateway"].tags_aws, {}) 429 | ) 430 | } 431 | 432 | # ---------- CORE NETWORK SUBNET CONFIGURATION ---------- 433 | # Core Network Subnets 434 | resource "aws_subnet" "cwan" { 435 | for_each = contains(local.subnet_keys, "core_network") ? toset(local.azs) : toset([]) 436 | 437 | availability_zone = each.key 438 | vpc_id = local.vpc.id 439 | cidr_block = local.calculated_subnets["core_network"][each.key] 440 | ipv6_cidr_block = can(local.calculated_subnets_ipv6["core_network"][each.key]) ? local.calculated_subnets_ipv6["core_network"][each.key] : null 441 | 442 | tags = merge( 443 | { Name = "${local.subnet_names["core_network"]}-${each.key}" }, 444 | module.tags.tags_aws, 445 | try(module.subnet_tags["core_network"].tags_aws, {}) 446 | ) 447 | } 448 | 449 | # Core Network subnet Route Table and association 450 | resource "aws_route_table" "cwan" { 451 | for_each = contains(local.subnet_keys, "core_network") ? toset(local.azs) : toset([]) 452 | 453 | vpc_id = local.vpc.id 454 | 455 | tags = merge( 456 | { Name = "${local.subnet_names["core_network"]}-${each.key}" }, 457 | module.tags.tags_aws, 458 | try(module.subnet_tags["core_network"].tags_aws, {}) 459 | ) 460 | } 461 | 462 | resource "aws_route_table_association" "cwan" { 463 | for_each = contains(local.subnet_keys, "core_network") ? toset(local.azs) : toset([]) 464 | 465 | subnet_id = aws_subnet.cwan[each.key].id 466 | route_table_id = aws_route_table.cwan[each.key].id 467 | } 468 | 469 | # Route: from core_network subnet to NAT gateway (if Internet access configured) 470 | resource "aws_route" "cwan_to_nat" { 471 | for_each = (try(var.subnets.core_network.connect_to_public_natgw == true, false) && contains(local.subnet_keys, "public")) ? toset(local.azs) : toset([]) 472 | 473 | route_table_id = aws_route_table.cwan[each.key].id 474 | destination_cidr_block = "0.0.0.0/0" 475 | # try to get nat for AZ, else use singular nat 476 | nat_gateway_id = local.nat_per_az[each.key].id 477 | } 478 | 479 | # AWS Cloud WAN's Core Network VPC attachment 480 | resource "aws_networkmanager_vpc_attachment" "cwan" { 481 | count = contains(local.subnet_keys, "core_network") ? 1 : 0 482 | 483 | core_network_id = var.core_network.id 484 | subnet_arns = values(aws_subnet.cwan)[*].arn 485 | vpc_arn = local.vpc.arn 486 | 487 | options { 488 | ipv6_support = local.cwan_dualstack ? true : false 489 | appliance_mode_support = try(var.subnets.core_network.appliance_mode_support, false) 490 | } 491 | 492 | tags = merge( 493 | { Name = "${var.name}-vpc_attachment" }, 494 | module.tags.tags_aws, 495 | try(module.subnet_tags["core_network"].tags_aws, {}) 496 | ) 497 | } 498 | 499 | # Core Network's attachment acceptance (if required) 500 | resource "aws_networkmanager_attachment_accepter" "cwan" { 501 | count = contains(local.subnet_keys, "core_network") && local.create_acceptance ? 1 : 0 502 | 503 | attachment_id = aws_networkmanager_vpc_attachment.cwan[0].id 504 | attachment_type = "VPC" 505 | } 506 | 507 | # FLOW LOGS 508 | module "flow_logs" { 509 | count = local.create_flow_logs ? 1 : 0 510 | 511 | source = "./modules/flow_logs" 512 | 513 | name = local.log_name 514 | flow_log_definition = var.vpc_flow_logs 515 | vpc_id = local.vpc.id 516 | 517 | tags = module.tags.tags_aws 518 | } 519 | 520 | # ---------- VPC LATTICE SERVICE NETWORK VPC ASSOCIATION ---------- 521 | resource "aws_vpclattice_service_network_vpc_association" "vpc_lattice_service_network_association" { 522 | count = local.lattice_association ? 1 : 0 523 | 524 | vpc_identifier = aws_vpc.main[0].id 525 | service_network_identifier = var.vpc_lattice.service_network_identifier 526 | security_group_ids = try(var.vpc_lattice.security_group_ids, null) 527 | 528 | tags = merge( 529 | module.tags.tags_aws, 530 | module.vpc_lattice_tags.tags_aws 531 | ) 532 | } 533 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # AWS VPC Module 3 | 4 | This module can be used to deploy a pragmatic VPC with various subnets types in # AZs. Common deployment examples can be found in [examples/](https://github.com/aws-ia/terraform-aws-vpc/tree/main/examples). 5 | 6 | Note: For information regarding the 4.0 upgrade see our [upgrade guide](https://github.com/aws-ia/terraform-aws-vpc/blob/main/docs/UPGRADE-GUIDE-4.0.md). 7 | 8 | ## Usage 9 | 10 | The example below builds a dual-stack VPC with public and private subnets in 3 AZs. Each subnet calculates an IPv4 CIDR based on the `netmask` argument passed, and an IPv6 CIDR with a /64 prefix length. The public subnets build NAT gateways in each AZ but optionally can be switched to `single_az`. An Egress-only Internet gateway is created by using the variable `vpc_egress_only_internet_gateway`. 11 | 12 | ```hcl 13 | module "vpc" { 14 | source = "aws-ia/vpc/aws" 15 | version = ">= 4.2.0" 16 | 17 | name = "multi-az-vpc" 18 | cidr_block = "10.0.0.0/16" 19 | vpc_assign_generated_ipv6_cidr_block = true 20 | vpc_egress_only_internet_gateway = true 21 | az_count = 3 22 | 23 | subnets = { 24 | # Dual-stack subnet 25 | public = { 26 | name_prefix = "my_public" # omit to prefix with "public" 27 | netmask = 24 28 | assign_ipv6_cidr = true 29 | nat_gateway_configuration = "all_azs" # options: "single_az", "none" 30 | } 31 | # IPv4 only subnet 32 | private = { 33 | # omitting name_prefix defaults value to "private" 34 | # name_prefix = "private_with_egress" 35 | netmask = 24 36 | connect_to_public_natgw = true 37 | } 38 | # IPv6-only subnet 39 | private_ipv6 = { 40 | ipv6_native = true 41 | assign_ipv6_cidr = true 42 | connect_to_eigw = true 43 | } 44 | } 45 | 46 | vpc_flow_logs = { 47 | log_destination_type = "cloud-watch-logs" 48 | retention_in_days = 180 49 | } 50 | } 51 | ``` 52 | 53 | ## Reserved Subnet Key Names 54 | 55 | There are 3 reserved keys for subnet key names in var.subnets corresponding to types "public", "transit\_gateway", and "core\_network" [(an AWS Cloud WAN feature)](https://docs.aws.amazon.com/vpc/latest/cloudwan/cloudwan-networks-working-with.html). Other custom subnet key names are valid are and those subnets will be private subnets. 56 | 57 | ```hcl 58 | subnets = { 59 | public = { 60 | name_prefix = "my-public" # omit to prefix with "public" 61 | netmask = 24 62 | nat_gateway_configuration = "all_azs" # options: "single_az", "none" 63 | } 64 | 65 | # naming private is not required, can use any key 66 | private = { 67 | # omitting name_prefix defaults value to "private" 68 | # name_prefix = "private" 69 | netmask = 24 70 | connect_to_public_natgw = true 71 | } 72 | 73 | # can be any valid key name 74 | privatetwo = { 75 | # omitting name_prefix defaults value to "privatetwo" 76 | # name_prefix = "private" 77 | netmask = 24 78 | } 79 | ``` 80 | 81 | ```hcl 82 | transit_gateway_id = <> 83 | transit_gateway_routes = { 84 | private = "0.0.0.0/0" 85 | vpce = "pl-123" 86 | } 87 | transit_gateway_ipv6_routes = { 88 | private = "::/0" 89 | } 90 | 91 | subnets = { 92 | private = { 93 | netmask = 24 94 | assign_ipv6_cidr = true 95 | } 96 | vpce = { netmask = 24} 97 | 98 | transit_gateway = { 99 | netmask = 28 100 | assign_ipv6_cidr = true 101 | transit_gateway_default_route_table_association = true 102 | transit_gateway_default_route_table_propagation = true 103 | transit_gateway_appliance_mode_support = "enable" 104 | transit_gateway_dns_support = "disable" 105 | transit_gateway_security_group_referencing_support = "enable" 106 | 107 | tags = { 108 | subnet_type = "tgw" 109 | } 110 | } 111 | ``` 112 | 113 | ```hcl 114 | core_network = { 115 | id = <> 116 | arn = <> 117 | } 118 | core_network_routes = { 119 | workload = "pl-123" 120 | } 121 | core_network_ipv6_routes = { 122 | workload = "::/0" 123 | } 124 | 125 | subnets = { 126 | workload = { 127 | name_prefix = "workload-private" 128 | netmask = 24 129 | assign_ipv6_cidr = true 130 | } 131 | 132 | core_network = { 133 | netmask = 28 134 | assign_ipv6_cidr = true 135 | appliance_mode_support = false 136 | require_acceptance = true 137 | accept_attachment = true 138 | 139 | tags = { 140 | env = "prod" 141 | } 142 | } 143 | ``` 144 | 145 | ## Updating a VPC with new or removed subnets 146 | 147 | If using `netmask` or `assign_ipv6_cidr` to calculate subnets and you wish to either add or remove subnets (ex: adding / removing an AZ), you may have to change from using `netmask` / `assign_ipv6_cidr` for some subnets and set to explicit instead. Private subnets are always calculated before public. 148 | 149 | When changing to explicit cidrs, subnets are always ordered by AZ. `0` -> a, `1` -> b, etc. 150 | 151 | Example: Changing from 2 azs to 3 152 | 153 | Before: 154 | ```hcl 155 | cidr_block = "10.0.0.0/16" 156 | vpc_assign_generated_ipv6_cidr_block = true 157 | az_count = 2 158 | 159 | subnets = { 160 | public = { 161 | netmask = 24 162 | assign_ipv6_cidr = true 163 | } 164 | 165 | private = { 166 | netmask = 24 167 | assign_ipv6_cidr = true 168 | } 169 | } 170 | ``` 171 | 172 | After: 173 | ```hcl 174 | cidr_block = "10.0.0.0/16" 175 | vpc_assign_generated_ipv6_cidr_block = true 176 | az_count = 3 177 | 178 | subnets = { 179 | public = { 180 | cidrs = ["10.0.0.0/24", "10.0.1.0/24", "10.0.4.0/24"] 181 | ipv6_cidrs = ["2a05:d01c:bc3:b200::/64", "2a05:d01c:bc3:b201::/64", "2a05:d01c:bc3:b204::/64"] 182 | } 183 | 184 | private = { 185 | cidrs = ["10.0.2.0/24", "10.0.3.0/24", "10.0.5.0/24"] 186 | ipv6_cidrs = ["2a05:d01c:bc3:b202::/64", "2a05:d01c:bc3:b203::/64", "2a05:d01c:bc3:b205::/64"] 187 | } 188 | } 189 | ``` 190 | 191 | The above example will cause only creating 2 new subnets in az `c` of the region being used. 192 | 193 | ## Output usage examples 194 | 195 | The outputs in this module attempt to align to a methodology of outputting resource attributes in a reasonable collection. The benefit of this is that, most likely, attributes you want access to are already present without having to create new `output {}` for each possible attribute. The [potential] downside is that you will have to extract it yourself using HCL logic. Below are some common examples: 196 | 197 | For more examples and explanation see [output docs]((https://github.com/aws-ia/terraform-aws-vpc/blob/main/docs/how-to-use-module-outputs.md) 198 | 199 | ### Extracting subnet IDs for private subnets 200 | 201 | Example Configuration: 202 | ```terraform 203 | module "vpc" { 204 | source = "aws-ia/vpc/aws" 205 | version = ">= 4.2.0" 206 | 207 | name = "multi-az-vpc" 208 | cidr_block = "10.0.0.0/20" 209 | az_count = 3 210 | 211 | subnets = { 212 | private = { netmask = 24 } 213 | } 214 | } 215 | ``` 216 | 217 | Extracting subnet\_ids to a list (using `terraform console` for example output): 218 | ```terraform 219 | > [ for _, value in module.vpc.private_subnet_attributes_by_az: value.id] 220 | [ 221 | "subnet-04a86315c4839b519", 222 | "subnet-02a7249c8652a7136", 223 | "subnet-09af79b5329b3681f", 224 | ] 225 | ``` 226 | 227 | Alternatively, since these are maps, you can use key in another resource `for_each` loop. The benefit here is that your dependent resource will have keys that match the AZ the subnet is in: 228 | 229 | ```terraform 230 | resource "aws_route53recoveryreadiness_cell" "cell_per_az" { 231 | for_each = module.vpc.private_subnet_attributes_by_az 232 | 233 | cell_name = "${each.key}-failover-cell-for-subnet-${each.value.id}" 234 | } 235 | ... 236 | ``` 237 | 238 | Terraform Plan: 239 | 240 | ```shell 241 | # aws_route53recoveryreadiness_cell.cell_per_az["us-east-1a"] will be created 242 | + resource "aws_route53recoveryreadiness_cell" "cell_per_az" { 243 | + cell_name = "us-east-1a-failover-cell-for-subnet-subnet-070696086c5864da1" 244 | ... 245 | } 246 | 247 | # aws_route53recoveryreadiness_cell.cell_per_az["us-east-1b"] will be created 248 | ... 249 | ``` 250 | 251 | # Common Errors and their Fixes 252 | 253 | ## Error creating routes to Core Network 254 | 255 | Error: 256 | 257 | > error creating Route in Route Table (rtb-xxx) with destination (YYY): InvalidCoreNetworkArn.NotFound: The core network arn 'arn:aws:networkmanager::XXXX:core-network/core-network-YYYYY' does not exist. 258 | 259 | This happens when the Core Network's VPC attachment requires acceptance, so it's not possible to create the routes in the VPC until the attachment is accepted. Check the following: 260 | 261 | * If the VPC attachment requires acceptance and you want the module to automatically accept it, configure `require_acceptance` and `accept_attachment` to `true`. 262 | 263 | ```terraform 264 | subnets = { 265 | core_network = { 266 | netmask = 28 267 | assign_ipv6_cidr = true 268 | require_acceptance = true 269 | accept_attachment = true 270 | } 271 | } 272 | ``` 273 | 274 | * If the VPC attachment requires acceptance but you want to accept it outside the module, first configure `require_acceptance` to `true` and `accept_attachment` to `false`. 275 | 276 | ```terraform 277 | subnets = { 278 | core_network = { 279 | netmask = 28 280 | assign_ipv6_cidr = true 281 | require_acceptance = true 282 | accept_attachment = true 283 | } 284 | } 285 | ``` 286 | 287 | After you apply and the attachment is accepted (outside the module), change the subnet configuration with `require_acceptance` to `false`. 288 | 289 | ```terraform 290 | subnets = { 291 | core_network = { 292 | netmask = 28 293 | assign_ipv6_cidr = true 294 | require_acceptance = false 295 | } 296 | } 297 | ``` 298 | 299 | * Alternatively, you can also not configure any subnet route (`var.core_network_routes`) to the Core Network until the attachment gets accepted. 300 | 301 | # Contributing 302 | 303 | Please see our [developer documentation](https://github.com/aws-ia/terraform-aws-vpc/blob/main/contributing.md) for guidance on contributing to this module. 304 | 305 | ## Requirements 306 | 307 | | Name | Version | 308 | |------|---------| 309 | | [terraform](#requirement\_terraform) | >= 1.3.0 | 310 | | [aws](#requirement\_aws) | >= 5.0.0 | 311 | 312 | ## Providers 313 | 314 | | Name | Version | 315 | |------|---------| 316 | | [aws](#provider\_aws) | >= 5.0.0 | 317 | 318 | ## Modules 319 | 320 | | Name | Source | Version | 321 | |------|--------|---------| 322 | | [calculate\_subnets](#module\_calculate\_subnets) | ./modules/calculate_subnets | n/a | 323 | | [calculate\_subnets\_ipv6](#module\_calculate\_subnets\_ipv6) | ./modules/calculate_subnets_ipv6 | n/a | 324 | | [flow\_logs](#module\_flow\_logs) | ./modules/flow_logs | n/a | 325 | | [subnet\_tags](#module\_subnet\_tags) | aws-ia/label/aws | 0.0.6 | 326 | | [tags](#module\_tags) | aws-ia/label/aws | 0.0.6 | 327 | | [vpc\_lattice\_tags](#module\_vpc\_lattice\_tags) | aws-ia/label/aws | 0.0.6 | 328 | 329 | ## Resources 330 | 331 | | Name | Type | 332 | |------|------| 333 | | [aws_ec2_transit_gateway_vpc_attachment.tgw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_vpc_attachment) | resource | 334 | | [aws_egress_only_internet_gateway.eigw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/egress_only_internet_gateway) | resource | 335 | | [aws_eip.nat](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/eip) | resource | 336 | | [aws_internet_gateway.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/internet_gateway) | resource | 337 | | [aws_nat_gateway.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/nat_gateway) | resource | 338 | | [aws_networkmanager_attachment_accepter.cwan](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/networkmanager_attachment_accepter) | resource | 339 | | [aws_networkmanager_vpc_attachment.cwan](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/networkmanager_vpc_attachment) | resource | 340 | | [aws_route.cwan_to_nat](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | 341 | | [aws_route.ipv6_private_to_cwan](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | 342 | | [aws_route.ipv6_private_to_tgw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | 343 | | [aws_route.ipv6_public_to_cwan](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | 344 | | [aws_route.ipv6_public_to_tgw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | 345 | | [aws_route.private_to_cwan](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | 346 | | [aws_route.private_to_egress_only](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | 347 | | [aws_route.private_to_nat](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | 348 | | [aws_route.private_to_tgw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | 349 | | [aws_route.public_ipv6_to_igw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | 350 | | [aws_route.public_to_cwan](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | 351 | | [aws_route.public_to_igw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | 352 | | [aws_route.public_to_tgw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | 353 | | [aws_route.tgw_to_nat](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | 354 | | [aws_route_table.cwan](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table) | resource | 355 | | [aws_route_table.private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table) | resource | 356 | | [aws_route_table.public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table) | resource | 357 | | [aws_route_table.tgw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table) | resource | 358 | | [aws_route_table_association.cwan](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association) | resource | 359 | | [aws_route_table_association.private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association) | resource | 360 | | [aws_route_table_association.public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association) | resource | 361 | | [aws_route_table_association.tgw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route_table_association) | resource | 362 | | [aws_subnet.cwan](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet) | resource | 363 | | [aws_subnet.private](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet) | resource | 364 | | [aws_subnet.public](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet) | resource | 365 | | [aws_subnet.tgw](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/subnet) | resource | 366 | | [aws_vpc.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc) | resource | 367 | | [aws_vpc_ipv4_cidr_block_association.secondary](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc_ipv4_cidr_block_association) | resource | 368 | | [aws_vpclattice_service_network_vpc_association.vpc_lattice_service_network_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpclattice_service_network_vpc_association) | resource | 369 | | [aws_availability_zones.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | 370 | | [aws_vpc.main](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpc) | data source | 371 | 372 | ## Inputs 373 | 374 | | Name | Description | Type | Default | Required | 375 | |------|-------------|------|---------|:--------:| 376 | | [name](#input\_name) | Name to give VPC. Note: does not effect subnet names, which get assigned name based on name\_prefix. | `string` | n/a | yes | 377 | | [subnets](#input\_subnets) | Configuration of subnets to build in VPC. 1 Subnet per AZ is created. Subnet types are defined as maps with the available keys: "private", "public", "transit\_gateway", "core\_network". Each Subnet type offers its own set of available arguments detailed below. Subnets are calculated in lexicographical order of the keys in the map.

**Attributes shared across subnet types:**
- `cidrs` = (Optional\|list(string)) **Cannot set if `netmask` is set.** List of IPv4 CIDRs to set to subnets. Count of CIDRs defined must match quantity of azs in `az_count`.
- `netmask` = (Optional\|Int) **Cannot set if `cidrs` is set.** Netmask of the `var.cidr_block` to calculate for each subnet.
- `assign_ipv6_cidr` = (Optional\|bool) **Cannot set if `ipv6_cidrs` is set.** If true, it will calculate a /64 block from the IPv6 VPC CIDR to set in the subnets.
- `ipv6_cidrs` = (Optional\|list(string)) **Cannot set if `assign_ipv6_cidr` is set.** List of IPv6 CIDRs to set to subnets. The subnet size must use a /64 prefix length. Count of CIDRs defined must match quantity of azs in `az_count`.
- `name_prefix` = (Optional\|String) A string prefix to use for the name of your subnet and associated resources. Subnet type key name is used if omitted (aka private, public, transit\_gateway). Example `name_prefix = "private"` for `var.subnets.private` is redundant.
- `tags` = (Optional\|map(string)) Tags to set on the subnet and associated resources.

**Any private subnet type options:**
- All shared keys above
- `connect_to_public_natgw` = (Optional\|bool) Determines if routes to NAT Gateways should be created. Must also set `var.subnets.public.nat_gateway_configuration` in public subnets.
- `ipv6_native` = (Optional\|bool) Indicates whether to create an IPv6-ony subnet. Either `var.assign_ipv6_cidr` or `var.ipv6_cidrs` should be defined to allocate an IPv6 CIDR block.
- `connect_to_eigw` = (Optional\|bool) Determines if routes to the Egress-only Internet gateway should be created. Must also set `var.vpc_egress_only_internet_gateway`.

**public subnet type options:**
- All shared keys above
- `nat_gateway_configuration` = (Optional\|string) Determines if NAT Gateways should be created and in how many AZs. Valid values = `"none"`, `"single_az"`, `"all_azs"`. Default = "none". Must also set `var.subnets.private.connect_to_public_natgw = true`.
- `connect_to_igw` = (Optional\|bool) Determines if the default route (0.0.0.0/0 or ::/0) is created in the public subnets with destination the Internet gateway. Defaults to `true`.
- `ipv6_native` = (Optional\|bool) Indicates whether to create an IPv6-ony subnet. Either `var.assign_ipv6_cidr` or `var.ipv6_cidrs` should be defined to allocate an IPv6 CIDR block.
- `map_public_ip_on_launch` = (Optional\|bool) Specify true to indicate that instances launched into the subnet should be assigned a public IP address. Default to `false`.

**transit\_gateway subnet type options:**
- All shared keys above
- `connect_to_public_natgw` = (Optional\|string) Determines if routes to NAT Gateways should be created. Specify the CIDR range or a prefix-list-id that you want routed to nat gateway. Usually `0.0.0.0/0`. Must also set `var.subnets.public.nat_gateway_configuration`.
- `transit_gateway_default_route_table_association` = (Optional\|bool) Boolean whether the VPC Attachment should be associated with the EC2 Transit Gateway association default route table. This cannot be configured or perform drift detection with Resource Access Manager shared EC2 Transit Gateways.
- `transit_gateway_default_route_table_propagation` = (Optional\|bool) Boolean whether the VPC Attachment should propagate routes with the EC2 Transit Gateway propagation default route table. This cannot be configured or perform drift detection with Resource Access Manager shared EC2 Transit Gateways.
- `transit_gateway_appliance_mode_support` = (Optional\|string) Whether Appliance Mode is enabled. If enabled, a traffic flow between a source and a destination uses the same Availability Zone for the VPC attachment for the lifetime of that flow. Valid values: `disable` (default) and `enable`.
- `transit_gateway_dns_support` = (Optional\|string) DNS Support is used if you need the VPC to resolve public IPv4 DNS host names to private IPv4 addresses when queried from instances in another VPC attached to the transit gateway. Valid values: `enable` (default) and `disable`.
- `transit_gateway_security_group_referencing_support` = (Optional\|string) Security group referencing support enables you to simplify Security group management and control of instance-to-instance traffic across VPCs that are connected by Transit gateway. Valid values: `disable` and `enable` (default).

**core\_network subnet type options:**
- All shared keys abovce
- `connect_to_public_natgw` = (Optional\|string) Determines if routes to NAT Gateways should be created. Specify the CIDR range or a prefix-list-id that you want routed to nat gateway. Usually `0.0.0.0/0`. Must also set `var.subnets.public.nat_gateway_configuration`.
- `appliance_mode_support` = (Optional\|bool) Indicates whether appliance mode is supported. If enabled, traffic flow between a source and destination use the same Availability Zone for the VPC attachment for the lifetime of that flow. Defaults to `false`.
- `require_acceptance` = (Optional\|bool) Boolean whether the core network VPC attachment to create requires acceptance or not. Defaults to `false`.
- `accept_attachment` = (Optional\|bool) Boolean whether the core network VPC attachment is accepted or not in the segment. Only valid if `require_acceptance` is set to `true`. Defaults to `true`.

Example:
subnets = {
# Dual-stack subnet
public = {
netmask = 24
assign_ipv6_cidr = true
nat_gateway_configuration = "single_az"
}
# IPv4 only subnet
private = {
netmask = 24
connect_to_public_natgw = true
}
# IPv6 only subnet
ipv6 = {
ipv6_native = true
assign_ipv6_cidr = true
connect_to_eigw = true
}
# Transit gateway subnets (dual-stack)
transit_gateway = {
netmask = 24
assign_ipv6_cidr = true
connect_to_public_natgw = true
transit_gateway_default_route_table_association = true
transit_gateway_default_route_table_propagation = true
}
# Core Network subnets (dual-stack)
core_network = {
netmask = 24
assign_ipv6_cidr = true
connect_to_public_natgw = true
appliance_mode_support = true
require_acceptance = true
accept_attachment = true
}
}
| `any` | n/a | yes | 378 | | [az\_count](#input\_az\_count) | Searches region for # of AZs to use and takes a slice based on count. Assume slice is sorted a-z. Required if `azs` is not provided. | `number` | `null` | no | 379 | | [azs](#input\_azs) | (Optional) A list of AZs to use. e.g. `azs = ["us-east-1a","us-east-1c"]` Incompatible with `az_count` | `list(string)` | `[]` | no | 380 | | [cidr\_block](#input\_cidr\_block) | IPv4 CIDR range to assign to VPC if creating VPC or to associate as a secondary IPv6 CIDR. Overridden by var.vpc\_id output from data.aws\_vpc. | `string` | `null` | no | 381 | | [core\_network](#input\_core\_network) | AWS Cloud WAN's core network information - to create a VPC attachment. Required when `cloud_wan` subnet is defined. Two attributes are required: the `id` and `arn` of the resource. |
object({
id = string
arn = string
})
|
{
"arn": null,
"id": null
}
| no | 382 | | [core\_network\_ipv6\_routes](#input\_core\_network\_ipv6\_routes) | Configuration of IPv6 route(s) to AWS Cloud WAN's core network.
For each `public` and/or `private` subnets named in the `subnets` variable, optionally create routes from the subnet to the core network.
You can specify either a CIDR range or a prefix-list-id that you want routed to the core network.
Example:
core_network_ivp6_routes = {
public = "::/0"
private = "pl-123"
}
| `any` | `{}` | no | 383 | | [core\_network\_routes](#input\_core\_network\_routes) | Configuration of route(s) to AWS Cloud WAN's core network.
For each `public` and/or `private` subnets named in the `subnets` variable, optionally create routes from the subnet to the core network.
You can specify either a CIDR range or a prefix-list-id that you want routed to the core network.
Example:
core_network_routes = {
public = "10.0.0.0/8"
private = "pl-123"
}
| `any` | `{}` | no | 384 | | [create\_vpc](#input\_create\_vpc) | Determines whether to create the VPC or not; defaults to enabling the creation. | `bool` | `true` | no | 385 | | [optimize\_subnet\_cidr\_ranges](#input\_optimize\_subnet\_cidr\_ranges) | Sort subnets to calculate by their netmask to efficiently use IP space. | `bool` | `false` | no | 386 | | [tags](#input\_tags) | Tags to apply to all resources. | `map(string)` | `{}` | no | 387 | | [transit\_gateway\_id](#input\_transit\_gateway\_id) | Transit gateway id to attach the VPC to. Required when `transit_gateway` subnet is defined. | `string` | `null` | no | 388 | | [transit\_gateway\_ipv6\_routes](#input\_transit\_gateway\_ipv6\_routes) | Configuration of IPv6 route(s) to transit gateway.
For each `public` and/or `private` subnets named in the `subnets` variable,
Optionally create routes from the subnet to transit gateway. Specify the CIDR range or a prefix-list-id that you want routed to the transit gateway.
Example:
transit_gateway_ipv6_routes = {
public = "::/0"
private = "pl-123"
}
| `any` | `{}` | no | 389 | | [transit\_gateway\_routes](#input\_transit\_gateway\_routes) | Configuration of route(s) to transit gateway.
For each `public` and/or `private` subnets named in the `subnets` variable,
Optionally create routes from the subnet to transit gateway. Specify the CIDR range or a prefix-list-id that you want routed to the transit gateway.
Example:
transit_gateway_routes = {
public = "10.0.0.0/8"
private = "pl-123"
}
| `any` | `{}` | no | 390 | | [vpc\_assign\_generated\_ipv6\_cidr\_block](#input\_vpc\_assign\_generated\_ipv6\_cidr\_block) | Requests and Amazon-provided IPv6 CIDR block with a /56 prefix length. You cannot specify the range of IP addresses, or the size of the CIDR block. Conflicts with `vpc_ipv6_ipam_pool_id`. | `bool` | `null` | no | 391 | | [vpc\_egress\_only\_internet\_gateway](#input\_vpc\_egress\_only\_internet\_gateway) | Set to use the Egress-only Internet gateway for all IPv6 traffic going to the Internet. | `bool` | `false` | no | 392 | | [vpc\_enable\_dns\_hostnames](#input\_vpc\_enable\_dns\_hostnames) | Indicates whether the instances launched in the VPC get DNS hostnames. If enabled, instances in the VPC get DNS hostnames; otherwise, they do not. Disabled by default for nondefault VPCs. | `bool` | `true` | no | 393 | | [vpc\_enable\_dns\_support](#input\_vpc\_enable\_dns\_support) | Indicates whether the DNS resolution is supported for the VPC. If enabled, queries to the Amazon provided DNS server at the 169.254.169.253 IP address, or the reserved IP address at the base of the VPC network range "plus two" succeed. If disabled, the Amazon provided DNS service in the VPC that resolves public DNS hostnames to IP addresses is not enabled. Enabled by default. | `bool` | `true` | no | 394 | | [vpc\_flow\_logs](#input\_vpc\_flow\_logs) | Whether or not to create VPC flow logs and which type. Options: "cloudwatch", "s3", "none". By default creates flow logs to `cloudwatch`. Variable overrides null value types for some keys, defined in defaults.tf. |
object({
name_override = optional(string, "")
log_destination = optional(string)
iam_role_arn = optional(string)
kms_key_id = optional(string)

log_destination_type = string
log_format = optional(string)
retention_in_days = optional(number)
log_bucket_lifecycle_filter_prefix = optional(string, null)
tags = optional(map(string))
traffic_type = optional(string, "ALL")

destination_options = optional(object({
file_format = optional(string, "plain-text")
hive_compatible_partitions = optional(bool, false)
per_hour_partition = optional(bool, false)
}),
{
file_format = "plain-text"
hive_compatible_partitions = false
per_hour_partition = false
})
})
|
{
"log_destination_type": "none"
}
| no | 395 | | [vpc\_id](#input\_vpc\_id) | VPC ID to use if not creating VPC. | `string` | `null` | no | 396 | | [vpc\_instance\_tenancy](#input\_vpc\_instance\_tenancy) | The allowed tenancy of instances launched into the VPC. | `string` | `"default"` | no | 397 | | [vpc\_ipv4\_ipam\_pool\_id](#input\_vpc\_ipv4\_ipam\_pool\_id) | Set to use IPAM to get an IPv4 CIDR block. | `string` | `null` | no | 398 | | [vpc\_ipv4\_netmask\_length](#input\_vpc\_ipv4\_netmask\_length) | Set to use IPAM to get an IPv4 CIDR block using a specified netmask. Must be set with var.vpc\_ipv4\_ipam\_pool\_id. | `string` | `null` | no | 399 | | [vpc\_ipv6\_cidr\_block](#input\_vpc\_ipv6\_cidr\_block) | IPv6 CIDR range to assign to VPC if creating VPC. You need to use `vpc_ipv6_ipam_pool_id` and set explicitly the CIDR block to use, or derived from IPAM using using `vpc_ipv6_netmask_length`. | `string` | `null` | no | 400 | | [vpc\_ipv6\_ipam\_pool\_id](#input\_vpc\_ipv6\_ipam\_pool\_id) | Set to use IPAM to get an IPv6 CIDR block. | `string` | `null` | no | 401 | | [vpc\_ipv6\_netmask\_length](#input\_vpc\_ipv6\_netmask\_length) | Set to use IPAM to get an IPv6 CIDR block using a specified netmask. Must be set with `var.vpc_ipv6_ipam_pool_id`. | `string` | `null` | no | 402 | | [vpc\_lattice](#input\_vpc\_lattice) | Amazon VPC Lattice Service Network VPC association. You can only associate one Service Network to the VPC. This association also support Security Groups (more than 1).
This variable expects the following attributes:
- `service_network_identifier` = (Required\|string) The ID or ARN of the Service Network to associate. You must use the ARN if the Service Network and VPC resources are in different AWS Accounts.
- `security_group_ids` = (Optional\|list(string)) The IDs of the security groups to attach to the association.
- `tags` = (Optional\|map(string)) Tags to set on the Lattice VPC association resource. | `any` | `{}` | no | 403 | | [vpc\_secondary\_cidr](#input\_vpc\_secondary\_cidr) | If `true` the module will create a `aws_vpc_ipv4_cidr_block_association` and subnets for that secondary cidr. If using IPAM for both primary and secondary CIDRs, you may only call this module serially (aka using `-target`, etc). | `bool` | `false` | no | 404 | | [vpc\_secondary\_cidr\_natgw](#input\_vpc\_secondary\_cidr\_natgw) | If attaching a secondary IPv4 CIDR instead of creating a VPC, you can map private/ tgw subnets to your public NAT GW with this argument. Simply pass the output `nat_gateway_attributes_by_az`, ex: `vpc_secondary_cidr_natgw = module.vpc.natgw_id_per_az`. If you did not build your primary with this module, you must construct a map { az : { id : nat-123asdb }} for each az. | `any` | `{}` | no | 405 | 406 | ## Outputs 407 | 408 | | Name | Description | 409 | |------|-------------| 410 | | [azs](#output\_azs) | List of AZs where subnets are created. | 411 | | [core\_network\_attachment](#output\_core\_network\_attachment) | AWS Cloud WAN's core network attachment. Full output of aws\_networkmanager\_vpc\_attachment. | 412 | | [core\_network\_subnet\_attributes\_by\_az](#output\_core\_network\_subnet\_attributes\_by\_az) | Map of all core\_network subnets containing their attributes.

Example:
core_network_subnet_attributes_by_az = {
"us-east-1a" = {
"arn" = "arn:aws:ec2:us-east-1:<>:subnet/subnet-04a86315c4839b519"
"assign_ipv6_address_on_creation" = false
...

}
"us-east-1b" = {...)
}
| 413 | | [egress\_only\_internet\_gateway](#output\_egress\_only\_internet\_gateway) | Egress-only Internet gateway attributes. Full output of aws\_egress\_only\_internet\_gateway. | 414 | | [flow\_log\_attributes](#output\_flow\_log\_attributes) | Flow Log information. | 415 | | [internet\_gateway](#output\_internet\_gateway) | Internet gateway attributes. Full output of aws\_internet\_gateway. | 416 | | [nat\_gateway\_attributes\_by\_az](#output\_nat\_gateway\_attributes\_by\_az) | Map of nat gateway resource attributes by AZ.

Example:
nat_gateway_attributes_by_az = {
"us-east-1a" = {
"allocation_id" = "eipalloc-0e8b20303eea88b13"
"connectivity_type" = "public"
"id" = "nat-0fde39f9550f4abb5"
"network_interface_id" = "eni-0d422727088bf9a86"
"private_ip" = "10.0.3.40"
"public_ip" = <>
"subnet_id" = "subnet-0f11c92e439c8ab4a"
"tags" = tomap({
"Name" = "nat-my-public-us-east-1a"
})
"tags_all" = tomap({
"Name" = "nat-my-public-us-east-1a"
})
}
"us-east-1b" = { ... }
}
| 417 | | [natgw\_id\_per\_az](#output\_natgw\_id\_per\_az) | Map of nat gateway IDs for each resource. Will be duplicate ids if your var.subnets.public.nat\_gateway\_configuration = "single\_az".

Example:
natgw_id_per_az = {
"us-east-1a" = {
"id" = "nat-0fde39f9550f4abb5"
}
"us-east-1b" = {
"id" = "nat-0fde39f9550f4abb5"
}
}
| 418 | | [private\_subnet\_attributes\_by\_az](#output\_private\_subnet\_attributes\_by\_az) | Map of all private subnets containing their attributes.

Example:
private_subnet_attributes_by_az = {
"private/us-east-1a" = {
"arn" = "arn:aws:ec2:us-east-1:<>:subnet/subnet-04a86315c4839b519"
"assign_ipv6_address_on_creation" = false
...

}
"us-east-1b" = {...)
}
| 419 | | [public\_subnet\_attributes\_by\_az](#output\_public\_subnet\_attributes\_by\_az) | Map of all public subnets containing their attributes.

Example:
public_subnet_attributes_by_az = {
"us-east-1a" = {
"arn" = "arn:aws:ec2:us-east-1:<>:subnet/subnet-04a86315c4839b519"
"assign_ipv6_address_on_creation" = false
...

}
"us-east-1b" = {...)
}
| 420 | | [rt\_attributes\_by\_type\_by\_az](#output\_rt\_attributes\_by\_type\_by\_az) | Map of route tables by type => az => route table attributes. Example usage: module.vpc.rt\_attributes\_by\_type\_by\_az.private.id

Example:
rt_attributes_by_type_by_az = {
"private" = {
"us-east-1a" = {
"id" = "rtb-0e77040c0598df003"
"tags" = tolist([
{
"key" = "Name"
"value" = "private-us-east-1a"
},
])
"vpc_id" = "vpc-033e054f49409592a"
...

}
"us-east-1b" = { ... }
"public" = { ... }
| 421 | | [tgw\_subnet\_attributes\_by\_az](#output\_tgw\_subnet\_attributes\_by\_az) | Map of all tgw subnets containing their attributes.

Example:
tgw_subnet_attributes_by_az = {
"us-east-1a" = {
"arn" = "arn:aws:ec2:us-east-1:<>:subnet/subnet-04a86315c4839b519"
"assign_ipv6_address_on_creation" = false
...

}
"us-east-1b" = {...)
}
| 422 | | [transit\_gateway\_attachment\_id](#output\_transit\_gateway\_attachment\_id) | Transit gateway attachment id. | 423 | | [vpc\_attributes](#output\_vpc\_attributes) | VPC resource attributes. Full output of aws\_vpc. | 424 | | [vpc\_lattice\_service\_network\_association](#output\_vpc\_lattice\_service\_network\_association) | VPC Lattice Service Network VPC association. Full output of aws\_vpclattice\_service\_network\_vpc\_association | 425 | --------------------------------------------------------------------------------