diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a6cd369..42575b4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.96.1 + rev: v1.96.3 hooks: - id: terraform_fmt - id: terraform_docs diff --git a/README.md b/README.md index e6c780f..9b3c935 100644 --- a/README.md +++ b/README.md @@ -7,60 +7,44 @@ Terraform module which creates Transit Gateway resources on AWS. ```hcl module "tgw" { source = "terraform-aws-modules/transit-gateway/aws" - version = "~> 2.0" - name = "my-tgw" - description = "My TGW shared with several other AWS accounts" + name = "example" + description = "Example TGW shared with several other AWS accounts" + # When `true` there is no need for RAM resources if using multiple AWS accounts enable_auto_accept_shared_attachments = true vpc_attachments = { vpc = { - vpc_id = module.vpc.vpc_id - subnet_ids = module.vpc.private_subnets + attachment_type = "vpc" + create_vpc_attachment = true + vpc_id = "vpc-1234556abcdef" + subnet_ids = ["subnet-abcde012", "subnet-bcde012a", "subnet-fghi345a"] + dns_support = true ipv6_support = true - tgw_routes = [ - { + tgw_routes = { + vpc = { destination_cidr_block = "30.0.0.0/16" }, - { - blackhole = true + blackhole = { + blackhole = true destination_cidr_block = "40.0.0.0/20" } - ] + } } } - ram_allow_external_principals = true - ram_principals = [307990089504] - tags = { Purpose = "tgw-complete-example" } } - -module "vpc" { - source = "terraform-aws-modules/vpc/aws" - version = "~> 3.0" - - name = "my-vpc" - - cidr = "10.10.0.0/16" - - azs = ["eu-west-1a", "eu-west-1b", "eu-west-1c"] - private_subnets = ["10.10.1.0/24", "10.10.2.0/24", "10.10.3.0/24"] - - enable_ipv6 = true - private_subnet_assign_ipv6_address_on_creation = true - private_subnet_ipv6_prefixes = [0, 1, 2] -} ``` ## Examples -- [Complete example](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/tree/master/examples/complete) shows TGW in combination with the [VPC module](https://github.com/terraform-aws-modules/terraform-aws-vpc) and [Resource Access Manager (RAM)](https://aws.amazon.com/ram/). +- [Complete example](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/tree/master/examples/complete) shows TGW in combination with the [VPC module](https://github.com/terraform-aws-modules/terraform-aws-vpc). - [Multi-account example](https://github.com/terraform-aws-modules/terraform-aws-transit-gateway/tree/master/examples/multi-account) shows TGW resources shared with different AWS accounts (via [Resource Access Manager (RAM)](https://aws.amazon.com/ram/)). @@ -68,14 +52,14 @@ module "vpc" { | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.1 | -| [aws](#requirement\_aws) | >= 4.4 | +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 5.0 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 4.4 | +| [aws](#provider\_aws) | >= 5.0 | ## Modules @@ -87,22 +71,29 @@ No modules. |------|------| | [aws_ec2_tag.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_tag) | resource | | [aws_ec2_transit_gateway.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway) | resource | +| [aws_ec2_transit_gateway_peering_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_peering_attachment) | resource | +| [aws_ec2_transit_gateway_peering_attachment_accepter.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_peering_attachment_accepter) | resource | | [aws_ec2_transit_gateway_route.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route) | resource | | [aws_ec2_transit_gateway_route_table.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table) | resource | | [aws_ec2_transit_gateway_route_table_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_association) | resource | | [aws_ec2_transit_gateway_route_table_propagation.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_propagation) | resource | | [aws_ec2_transit_gateway_vpc_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_vpc_attachment) | resource | +| [aws_ec2_transit_gateway_vpc_attachment_accepter.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_vpc_attachment_accepter) | resource | +| [aws_flow_log.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/flow_log) | resource | | [aws_ram_principal_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_principal_association) | resource | | [aws_ram_resource_association.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_association) | resource | | [aws_ram_resource_share.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_share) | resource | | [aws_ram_resource_share_accepter.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ram_resource_share_accepter) | resource | | [aws_route.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/route) | resource | +| [aws_ec2_transit_gateway_peering_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ec2_transit_gateway_peering_attachment) | data source | +| [aws_ec2_transit_gateway_vpc_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ec2_transit_gateway_vpc_attachment) | data source | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [amazon\_side\_asn](#input\_amazon\_side\_asn) | The Autonomous System Number (ASN) for the Amazon side of the gateway. By default the TGW is created with the current default Amazon ASN. | `string` | `null` | no | +| [attachments](#input\_attachments) | Maps of maps of VPC details to attach to TGW. Type 'any' to disable type validation by Terraform. | `any` | `{}` | no | | [create\_tgw](#input\_create\_tgw) | Controls if TGW should be created (it affects almost all resources) | `bool` | `true` | no | | [create\_tgw\_routes](#input\_create\_tgw\_routes) | Controls if TGW Route Table / Routes should be created | `bool` | `true` | no | | [description](#input\_description) | Description of the EC2 Transit Gateway | `string` | `null` | no | @@ -112,22 +103,24 @@ No modules. | [enable\_dns\_support](#input\_enable\_dns\_support) | Should be true to enable DNS support in the TGW | `bool` | `true` | no | | [enable\_multicast\_support](#input\_enable\_multicast\_support) | Whether multicast support is enabled | `bool` | `false` | no | | [enable\_vpn\_ecmp\_support](#input\_enable\_vpn\_ecmp\_support) | Whether VPN Equal Cost Multipath Protocol support is enabled | `bool` | `true` | no | +| [flow\_logs](#input\_flow\_logs) | Flow Logs to create for Transit Gateway or attachments | `any` | `{}` | no | | [name](#input\_name) | Name to be used on all the resources as identifier | `string` | `""` | no | | [ram\_allow\_external\_principals](#input\_ram\_allow\_external\_principals) | Indicates whether principals outside your organization can be associated with a resource share. | `bool` | `false` | no | | [ram\_name](#input\_ram\_name) | The name of the resource share of TGW | `string` | `""` | no | -| [ram\_principals](#input\_ram\_principals) | A list of principals to share TGW with. Possible values are an AWS account ID, an AWS Organizations Organization ARN, or an AWS Organizations Organization Unit ARN | `list(string)` | `[]` | no | +| [ram\_principals](#input\_ram\_principals) | A list of principals to share TGW with. Possible values are an AWS account ID, an AWS Organizations Organization ARN, or an AWS Organizations Organization Unit ARN | `set(string)` | `[]` | no | | [ram\_resource\_share\_arn](#input\_ram\_resource\_share\_arn) | ARN of RAM resource share | `string` | `""` | no | | [ram\_tags](#input\_ram\_tags) | Additional tags for the RAM | `map(string)` | `{}` | no | | [share\_tgw](#input\_share\_tgw) | Whether to share your transit gateway with other accounts | `bool` | `true` | no | | [tags](#input\_tags) | A map of tags to add to all resources | `map(string)` | `{}` | no | +| [tgw\_attachment\_tags](#input\_tgw\_attachment\_tags) | Additional tags for VPC attachments | `map(string)` | `{}` | no | | [tgw\_default\_route\_table\_tags](#input\_tgw\_default\_route\_table\_tags) | Additional tags for the Default TGW route table | `map(string)` | `{}` | no | +| [tgw\_flow\_log\_tags](#input\_tgw\_flow\_log\_tags) | Additional tags for TGW or attachment flow logs | `map(string)` | `{}` | no | | [tgw\_route\_table\_tags](#input\_tgw\_route\_table\_tags) | Additional tags for the TGW route table | `map(string)` | `{}` | no | +| [tgw\_route\_tables](#input\_tgw\_route\_tables) | Custom TGW route tables to create | `set(string)` |
[
"custom"
]
| no | | [tgw\_tags](#input\_tgw\_tags) | Additional tags for the TGW | `map(string)` | `{}` | no | -| [tgw\_vpc\_attachment\_tags](#input\_tgw\_vpc\_attachment\_tags) | Additional tags for VPC attachments | `map(string)` | `{}` | no | | [timeouts](#input\_timeouts) | Create, update, and delete timeout configurations for the transit gateway | `map(string)` | `{}` | no | | [transit\_gateway\_cidr\_blocks](#input\_transit\_gateway\_cidr\_blocks) | One or more IPv4 or IPv6 CIDR blocks for the transit gateway. Must be a size /24 CIDR block or larger for IPv4, or a size /64 CIDR block or larger for IPv6 | `list(string)` | `[]` | no | | [transit\_gateway\_route\_table\_id](#input\_transit\_gateway\_route\_table\_id) | Identifier of EC2 Transit Gateway Route Table to use with the Target Gateway when reusing it between multiple TGWs | `string` | `null` | no | -| [vpc\_attachments](#input\_vpc\_attachments) | Maps of maps of VPC details to attach to TGW. Type 'any' to disable type validation by Terraform. | `any` | `{}` | no | ## Outputs diff --git a/examples/complete/README.md b/examples/complete/README.md index b4ef9c3..99c991d 100644 --- a/examples/complete/README.md +++ b/examples/complete/README.md @@ -20,7 +20,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.4 | +| [aws](#requirement\_aws) | >= 5.0 | ## Providers diff --git a/examples/complete/main.tf b/examples/complete/main.tf index 4c5566d..fb3faf9 100644 --- a/examples/complete/main.tf +++ b/examples/complete/main.tf @@ -6,10 +6,15 @@ locals { name = "ex-tgw-${replace(basename(path.cwd), "_", "-")}" region = "eu-west-1" + account_id = "012345678901" + + flow_logs_cloudwatch_dest_arn = "arn:aws:logs:${local.region}:${local.account_id}:log-group:/aws/tgw/" + flow_logs_cloudwatch_iam_role_arn = "arn:aws:iam::${local.account_id}:role/tgw-flow-logs-to-cloudwatch" + flow_logs_s3_dest_arn = "arn:aws:s3:::tgw-flow-logs-${local.account_id}-${local.region}" + tags = { Example = local.name - GithubRepo = "terraform-aws-eks" - GithubOrg = "terraform-aws-transit-gateway" + GithubRepo = "terraform-aws-transit-gateway" } } @@ -21,58 +26,172 @@ module "tgw" { source = "../../" name = local.name - description = "My TGW shared with several other AWS accounts" + description = "My TGW connecting multiple VPCs" amazon_side_asn = 64532 + create_tgw = true + # Creates RAM resources for hub (create_tgw = true) accounts + share_tgw = false + transit_gateway_cidr_blocks = ["10.99.0.0/24"] # When "true" there is no need for RAM resources if using multiple AWS accounts - enable_auto_accept_shared_attachments = true + enable_auto_accept_shared_attachments = false + + enable_default_route_table_association = false + enable_default_route_table_propagation = false # When "true", allows service discovery through IGMP enable_multicast_support = false - vpc_attachments = { - vpc1 = { - vpc_id = module.vpc1.vpc_id - subnet_ids = module.vpc1.private_subnets - dns_support = true - ipv6_support = true + flow_logs = [ + # Flow logs for the entire TGW + { + cloudwatch_dest_arn = local.flow_logs_cloudwatch_dest_arn + cloudwatch_iam_role_arn = local.flow_logs_cloudwatch_iam_role_arn + dest_enabled = true + dest_type = "cloud-watch-logs" + key = "tgw" + s3_dest_arn = null + }, + { + cloudwatch_dest_arn = null + cloudwatch_iam_role_arn = null + dest_enabled = true + dest_type = "s3" + key = "tgw" + s3_dest_arn = local.flow_logs_s3_dest_arn + }, + # Flow logs for individual attachments + { + attachment_type = "vpc" + cloudwatch_dest_arn = local.flow_logs_cloudwatch_dest_arn + cloudwatch_iam_role_arn = local.flow_logs_cloudwatch_iam_role_arn + create_tgw_peering = false + create_vpc_attachment = true + dest_enabled = true + dest_type = "cloud-watch-logs" + key = "vpc1" + s3_dest_arn = null + }, + { + attachment_type = "vpc" + cloudwatch_dest_arn = null + cloudwatch_iam_role_arn = null + create_tgw_peering = false + create_vpc_attachment = true + dest_enabled = true + dest_type = "s3" + key = "vpc1" + s3_dest_arn = local.flow_logs_s3_dest_arn + }, + { + attachment_type = "vpc" + cloudwatch_dest_arn = local.flow_logs_cloudwatch_dest_arn + cloudwatch_iam_role_arn = local.flow_logs_cloudwatch_iam_role_arn + create_tgw_peering = false + create_vpc_attachment = true + dest_enabled = true + dest_type = "cloud-watch-logs" + key = "vpc2" + s3_dest_arn = null + }, + { + attachment_type = "vpc" + cloudwatch_dest_arn = null + cloudwatch_iam_role_arn = null + create_tgw_peering = false + create_vpc_attachment = true + dest_enabled = true + dest_type = "s3" + key = "vpc2" + s3_dest_arn = local.flow_logs_s3_dest_arn + }, + ] + + tgw_route_tables = [ + "prod", + "staging", + ] + attachments = { + vpc1 = { + attachment_type = "vpc" + create_vpc_attachment = true + # Keep enable_vpc_attachment = false until the corresponding VPC attachment is created/accepted + enable_vpc_attachment = false + vpc_id = module.vpc1.vpc_id + subnet_ids = module.vpc1.private_subnets transit_gateway_default_route_table_association = false transit_gateway_default_route_table_propagation = false - - tgw_routes = [ - { - destination_cidr_block = "30.0.0.0/16" + dns_support = true + ipv6_support = true + # Keep create_tgw_routes = false until the VPC/peering attachments exist/are accepted + create_tgw_routes = true + tgw_route_table_propagations = { + prod = { + enable = true }, - { - blackhole = true - destination_cidr_block = "0.0.0.0/0" + staging = { + enable = true } - ] - }, + } + tgw_routes = { + vpc1 = { + destination_cidr_blocks = module.vpc1.private_subnets_cidr_blocks + route_table = "prod" + } + blackhole = { + blackhole = true + destination_cidr_blocks = ["0.0.0.0/0"] + route_table = "prod" + } + } + } vpc2 = { - vpc_id = module.vpc2.vpc_id - subnet_ids = module.vpc2.private_subnets - - tgw_routes = [ - { - destination_cidr_block = "50.0.0.0/16" + attachment_type = "vpc" + create_vpc_attachment = true + # Keep enable_vpc_attachment = false until the corresponding VPC attachment is created/accepted + enable_vpc_attachment = false + vpc_id = module.vpc2.vpc_id + subnet_ids = module.vpc2.private_subnets + transit_gateway_default_route_table_association = false + transit_gateway_default_route_table_propagation = false + dns_support = true + ipv6_support = false + # Keep create_tgw_routes = false until the VPC/peering attachments exist/are accepted + create_tgw_routes = true + tgw_route_table_propagations = { + prod = { + enable = true }, - { - blackhole = true - destination_cidr_block = "10.10.10.10/32" + staging = { + enable = true } - ] + } + tgw_routes = { + vpc2 = { + destination_cidr_blocks = module.vpc2.private_subnets_cidr_blocks + route_table = "prod" + } + blackhole = { + blackhole = true + destination_cidr_blocks = ["0.0.0.0/0"] + route_table = "prod" + } + } + tags = { Name = "${local.name}-vpc2" } - }, + + } } - ram_allow_external_principals = true - ram_principals = [307990089504] + ram_allow_external_principals = false + ram_principals = [ + local.account_id + ] tags = local.tags } @@ -108,7 +227,5 @@ module "vpc2" { azs = ["${local.region}a", "${local.region}b", "${local.region}c"] private_subnets = ["10.20.1.0/24", "10.20.2.0/24", "10.20.3.0/24"] - enable_ipv6 = false - tags = local.tags } diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf index 46b7087..ddfcb0e 100644 --- a/examples/complete/versions.tf +++ b/examples/complete/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.4" + version = ">= 5.0" } } } diff --git a/examples/multi-account/README.md b/examples/multi-account/README.md index d3726ee..b6ae9df 100644 --- a/examples/multi-account/README.md +++ b/examples/multi-account/README.md @@ -20,7 +20,7 @@ Note that this example may create resources which cost money. Run `terraform des | Name | Version | |------|---------| | [terraform](#requirement\_terraform) | >= 1.0 | -| [aws](#requirement\_aws) | >= 4.4 | +| [aws](#requirement\_aws) | >= 5.0 | ## Providers @@ -30,10 +30,14 @@ No providers. | Name | Source | Version | |------|--------|---------| -| [tgw](#module\_tgw) | ../../ | n/a | -| [tgw\_peer](#module\_tgw\_peer) | ../../ | n/a | -| [vpc1](#module\_vpc1) | terraform-aws-modules/vpc/aws | ~> 5.0 | -| [vpc2](#module\_vpc2) | terraform-aws-modules/vpc/aws | ~> 5.0 | +| [peer\_hub](#module\_peer\_hub) | ../../ | n/a | +| [peer\_hub\_vpc](#module\_peer\_hub\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | +| [peer\_spoke](#module\_peer\_spoke) | ../../ | n/a | +| [peer\_spoke\_vpc](#module\_peer\_spoke\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | +| [primary\_hub](#module\_primary\_hub) | ../../ | n/a | +| [primary\_hub\_vpc](#module\_primary\_hub\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | +| [primary\_spoke](#module\_primary\_spoke) | ../../ | n/a | +| [primary\_spoke\_vpc](#module\_primary\_spoke\_vpc) | terraform-aws-modules/vpc/aws | ~> 5.0 | ## Resources @@ -47,21 +51,42 @@ No inputs. | Name | Description | |------|-------------| -| [ec2\_transit\_gateway\_arn](#output\_ec2\_transit\_gateway\_arn) | EC2 Transit Gateway Amazon Resource Name (ARN) | -| [ec2\_transit\_gateway\_association\_default\_route\_table\_id](#output\_ec2\_transit\_gateway\_association\_default\_route\_table\_id) | Identifier of the default association route table | -| [ec2\_transit\_gateway\_id](#output\_ec2\_transit\_gateway\_id) | EC2 Transit Gateway identifier | -| [ec2\_transit\_gateway\_owner\_id](#output\_ec2\_transit\_gateway\_owner\_id) | Identifier of the AWS account that owns the EC2 Transit Gateway | -| [ec2\_transit\_gateway\_propagation\_default\_route\_table\_id](#output\_ec2\_transit\_gateway\_propagation\_default\_route\_table\_id) | Identifier of the default propagation route table | -| [ec2\_transit\_gateway\_route\_ids](#output\_ec2\_transit\_gateway\_route\_ids) | List of EC2 Transit Gateway Route Table identifier combined with destination | -| [ec2\_transit\_gateway\_route\_table\_association](#output\_ec2\_transit\_gateway\_route\_table\_association) | Map of EC2 Transit Gateway Route Table Association attributes | -| [ec2\_transit\_gateway\_route\_table\_association\_ids](#output\_ec2\_transit\_gateway\_route\_table\_association\_ids) | List of EC2 Transit Gateway Route Table Association identifiers | -| [ec2\_transit\_gateway\_route\_table\_default\_association\_route\_table](#output\_ec2\_transit\_gateway\_route\_table\_default\_association\_route\_table) | Boolean whether this is the default association route table for the EC2 Transit Gateway | -| [ec2\_transit\_gateway\_route\_table\_default\_propagation\_route\_table](#output\_ec2\_transit\_gateway\_route\_table\_default\_propagation\_route\_table) | Boolean whether this is the default propagation route table for the EC2 Transit Gateway | -| [ec2\_transit\_gateway\_route\_table\_id](#output\_ec2\_transit\_gateway\_route\_table\_id) | EC2 Transit Gateway Route Table identifier | -| [ec2\_transit\_gateway\_route\_table\_propagation](#output\_ec2\_transit\_gateway\_route\_table\_propagation) | Map of EC2 Transit Gateway Route Table Propagation attributes | -| [ec2\_transit\_gateway\_route\_table\_propagation\_ids](#output\_ec2\_transit\_gateway\_route\_table\_propagation\_ids) | List of EC2 Transit Gateway Route Table Propagation identifiers | -| [ec2\_transit\_gateway\_vpc\_attachment](#output\_ec2\_transit\_gateway\_vpc\_attachment) | Map of EC2 Transit Gateway VPC Attachment attributes | -| [ec2\_transit\_gateway\_vpc\_attachment\_ids](#output\_ec2\_transit\_gateway\_vpc\_attachment\_ids) | List of EC2 Transit Gateway VPC Attachment identifiers | -| [ram\_principal\_association\_id](#output\_ram\_principal\_association\_id) | The Amazon Resource Name (ARN) of the Resource Share and the principal, separated by a comma | -| [ram\_resource\_share\_id](#output\_ram\_resource\_share\_id) | The Amazon Resource Name (ARN) of the resource share | +| [peer\_hub\_ec2\_transit\_gateway\_arn](#output\_peer\_hub\_ec2\_transit\_gateway\_arn) | EC2 Transit Gateway Amazon Resource Name (ARN) | +| [peer\_hub\_ec2\_transit\_gateway\_association\_default\_route\_table\_id](#output\_peer\_hub\_ec2\_transit\_gateway\_association\_default\_route\_table\_id) | Identifier of the default association route table | +| [peer\_hub\_ec2\_transit\_gateway\_id](#output\_peer\_hub\_ec2\_transit\_gateway\_id) | EC2 Transit Gateway identifier | +| [peer\_hub\_ec2\_transit\_gateway\_owner\_id](#output\_peer\_hub\_ec2\_transit\_gateway\_owner\_id) | Identifier of the AWS account that owns the EC2 Transit Gateway | +| [peer\_hub\_ec2\_transit\_gateway\_propagation\_default\_route\_table\_id](#output\_peer\_hub\_ec2\_transit\_gateway\_propagation\_default\_route\_table\_id) | Identifier of the default propagation route table | +| [peer\_hub\_ec2\_transit\_gateway\_route\_ids](#output\_peer\_hub\_ec2\_transit\_gateway\_route\_ids) | List of EC2 Transit Gateway Route Table identifier combined with destination | +| [peer\_hub\_ec2\_transit\_gateway\_route\_table\_association](#output\_peer\_hub\_ec2\_transit\_gateway\_route\_table\_association) | Map of EC2 Transit Gateway Route Table Association attributes | +| [peer\_hub\_ec2\_transit\_gateway\_route\_table\_association\_ids](#output\_peer\_hub\_ec2\_transit\_gateway\_route\_table\_association\_ids) | List of EC2 Transit Gateway Route Table Association identifiers | +| [peer\_hub\_ec2\_transit\_gateway\_route\_table\_default\_association\_route\_table](#output\_peer\_hub\_ec2\_transit\_gateway\_route\_table\_default\_association\_route\_table) | Boolean whether this is the default association route table for the EC2 Transit Gateway | +| [peer\_hub\_ec2\_transit\_gateway\_route\_table\_default\_propagation\_route\_table](#output\_peer\_hub\_ec2\_transit\_gateway\_route\_table\_default\_propagation\_route\_table) | Boolean whether this is the default propagation route table for the EC2 Transit Gateway | +| [peer\_hub\_ec2\_transit\_gateway\_route\_table\_id](#output\_peer\_hub\_ec2\_transit\_gateway\_route\_table\_id) | EC2 Transit Gateway Route Table identifier | +| [peer\_hub\_ec2\_transit\_gateway\_route\_table\_propagation](#output\_peer\_hub\_ec2\_transit\_gateway\_route\_table\_propagation) | Map of EC2 Transit Gateway Route Table Propagation attributes | +| [peer\_hub\_ec2\_transit\_gateway\_route\_table\_propagation\_ids](#output\_peer\_hub\_ec2\_transit\_gateway\_route\_table\_propagation\_ids) | List of EC2 Transit Gateway Route Table Propagation identifiers | +| [peer\_hub\_ec2\_transit\_gateway\_vpc\_attachment](#output\_peer\_hub\_ec2\_transit\_gateway\_vpc\_attachment) | Map of EC2 Transit Gateway VPC Attachment attributes | +| [peer\_hub\_ec2\_transit\_gateway\_vpc\_attachment\_ids](#output\_peer\_hub\_ec2\_transit\_gateway\_vpc\_attachment\_ids) | List of EC2 Transit Gateway VPC Attachment identifiers | +| [peer\_hub\_ram\_principal\_association\_id](#output\_peer\_hub\_ram\_principal\_association\_id) | The Amazon Resource Name (ARN) of the Resource Share and the principal, separated by a comma | +| [peer\_hub\_ram\_resource\_share\_id](#output\_peer\_hub\_ram\_resource\_share\_id) | The Amazon Resource Name (ARN) of the resource share | +| [peer\_spoke\_ec2\_transit\_gateway\_vpc\_attachment](#output\_peer\_spoke\_ec2\_transit\_gateway\_vpc\_attachment) | Map of EC2 Transit Gateway VPC Attachment attributes | +| [peer\_spoke\_ec2\_transit\_gateway\_vpc\_attachment\_ids](#output\_peer\_spoke\_ec2\_transit\_gateway\_vpc\_attachment\_ids) | List of EC2 Transit Gateway VPC Attachment identifiers | +| [primary\_hub\_ec2\_transit\_gateway\_arn](#output\_primary\_hub\_ec2\_transit\_gateway\_arn) | EC2 Transit Gateway Amazon Resource Name (ARN) | +| [primary\_hub\_ec2\_transit\_gateway\_association\_default\_route\_table\_id](#output\_primary\_hub\_ec2\_transit\_gateway\_association\_default\_route\_table\_id) | Identifier of the default association route table | +| [primary\_hub\_ec2\_transit\_gateway\_id](#output\_primary\_hub\_ec2\_transit\_gateway\_id) | EC2 Transit Gateway identifier | +| [primary\_hub\_ec2\_transit\_gateway\_owner\_id](#output\_primary\_hub\_ec2\_transit\_gateway\_owner\_id) | Identifier of the AWS account that owns the EC2 Transit Gateway | +| [primary\_hub\_ec2\_transit\_gateway\_propagation\_default\_route\_table\_id](#output\_primary\_hub\_ec2\_transit\_gateway\_propagation\_default\_route\_table\_id) | Identifier of the default propagation route table | +| [primary\_hub\_ec2\_transit\_gateway\_route\_ids](#output\_primary\_hub\_ec2\_transit\_gateway\_route\_ids) | List of EC2 Transit Gateway Route Table identifier combined with destination | +| [primary\_hub\_ec2\_transit\_gateway\_route\_table\_association](#output\_primary\_hub\_ec2\_transit\_gateway\_route\_table\_association) | Map of EC2 Transit Gateway Route Table Association attributes | +| [primary\_hub\_ec2\_transit\_gateway\_route\_table\_association\_ids](#output\_primary\_hub\_ec2\_transit\_gateway\_route\_table\_association\_ids) | List of EC2 Transit Gateway Route Table Association identifiers | +| [primary\_hub\_ec2\_transit\_gateway\_route\_table\_default\_association\_route\_table](#output\_primary\_hub\_ec2\_transit\_gateway\_route\_table\_default\_association\_route\_table) | Boolean whether this is the default association route table for the EC2 Transit Gateway | +| [primary\_hub\_ec2\_transit\_gateway\_route\_table\_default\_propagation\_route\_table](#output\_primary\_hub\_ec2\_transit\_gateway\_route\_table\_default\_propagation\_route\_table) | Boolean whether this is the default propagation route table for the EC2 Transit Gateway | +| [primary\_hub\_ec2\_transit\_gateway\_route\_table\_id](#output\_primary\_hub\_ec2\_transit\_gateway\_route\_table\_id) | EC2 Transit Gateway Route Table identifier | +| [primary\_hub\_ec2\_transit\_gateway\_route\_table\_propagation](#output\_primary\_hub\_ec2\_transit\_gateway\_route\_table\_propagation) | Map of EC2 Transit Gateway Route Table Propagation attributes | +| [primary\_hub\_ec2\_transit\_gateway\_route\_table\_propagation\_ids](#output\_primary\_hub\_ec2\_transit\_gateway\_route\_table\_propagation\_ids) | List of EC2 Transit Gateway Route Table Propagation identifiers | +| [primary\_hub\_ec2\_transit\_gateway\_vpc\_attachment](#output\_primary\_hub\_ec2\_transit\_gateway\_vpc\_attachment) | Map of EC2 Transit Gateway VPC Attachment attributes | +| [primary\_hub\_ec2\_transit\_gateway\_vpc\_attachment\_ids](#output\_primary\_hub\_ec2\_transit\_gateway\_vpc\_attachment\_ids) | List of EC2 Transit Gateway VPC Attachment identifiers | +| [primary\_hub\_ram\_principal\_association\_id](#output\_primary\_hub\_ram\_principal\_association\_id) | The Amazon Resource Name (ARN) of the Resource Share and the principal, separated by a comma | +| [primary\_hub\_ram\_resource\_share\_id](#output\_primary\_hub\_ram\_resource\_share\_id) | The Amazon Resource Name (ARN) of the resource share | +| [primary\_spoke\_ec2\_transit\_gateway\_vpc\_attachment](#output\_primary\_spoke\_ec2\_transit\_gateway\_vpc\_attachment) | Map of EC2 Transit Gateway VPC Attachment attributes | +| [primary\_spoke\_ec2\_transit\_gateway\_vpc\_attachment\_ids](#output\_primary\_spoke\_ec2\_transit\_gateway\_vpc\_attachment\_ids) | List of EC2 Transit Gateway VPC Attachment identifiers | diff --git a/examples/multi-account/main.tf b/examples/multi-account/main.tf index 56e0b70..6e9c6e1 100644 --- a/examples/multi-account/main.tf +++ b/examples/multi-account/main.tf @@ -1,21 +1,55 @@ provider "aws" { - region = local.region + region = local.primary_region +} + +# This provider is required for TGW and peering attachment installation in another AWS Account +provider "aws" { + region = local.secondary_region + alias = "peer_hub" } # This provider is required for attachment only installation in another AWS Account provider "aws" { - region = local.region - alias = "peer" + region = local.primary_region + alias = "primary_spoke" +} + +# This provider is required for attachment only installation in another AWS Account +provider "aws" { + region = local.secondary_region + alias = "peer_spoke" } locals { - name = "ex-tgw-${replace(basename(path.cwd), "_", "-")}" - region = "eu-west-1" + name = "ex-tgw-${replace(basename(path.cwd), "_", "-")}" + + primary_region = "eu-west-1" + secondary_region = "eu-west-2" + + primary_hub_account_id = "012345678901" + peer_hub_account_id = "123456789012" + primary_spoke_account_id = "234567890123" + peer_spoke_account_id = "345678901234" + + primary_hub_flow_logs_cloudwatch_dest_arn = "arn:aws:logs:${local.primary_region}:${local.primary_hub_account_id}:log-group:/aws/tgw/" + primary_hub_flow_logs_cloudwatch_iam_role_arn = "arn:aws:iam::${local.primary_hub_account_id}:role/tgw-flow-logs-to-cloudwatch" + primary_hub_flow_logs_s3_dest_arn = "arn:aws:s3:::tgw-flow-logs-${local.primary_hub_account_id}-${local.primary_region}" + + primary_spoke_flow_logs_cloudwatch_dest_arn = "arn:aws:logs:${local.primary_region}:${local.primary_spoke_account_id}:log-group:/aws/tgw/" + primary_spoke_flow_logs_cloudwatch_iam_role_arn = "arn:aws:iam::${local.primary_spoke_account_id}:role/tgw-flow-logs-to-cloudwatch" + primary_spoke_flow_logs_s3_dest_arn = "arn:aws:s3:::tgw-flow-logs-${local.primary_spoke_account_id}-${local.primary_region}" + + peer_hub_flow_logs_cloudwatch_dest_arn = "arn:aws:logs:${local.secondary_region}:${local.peer_hub_account_id}:log-group:/aws/tgw/" + peer_hub_flow_logs_cloudwatch_iam_role_arn = "arn:aws:iam::${local.peer_hub_account_id}:role/tgw-flow-logs-to-cloudwatch" + peer_hub_flow_logs_s3_dest_arn = "arn:aws:s3:::tgw-flow-logs-${local.peer_hub_account_id}-${local.secondary_region}" + + peer_spoke_flow_logs_cloudwatch_dest_arn = "arn:aws:logs:${local.secondary_region}:${local.peer_spoke_account_id}:log-group:/aws/tgw/" + peer_spoke_flow_logs_cloudwatch_iam_role_arn = "arn:aws:iam::${local.peer_spoke_account_id}:role/tgw-flow-logs-to-cloudwatch" + peer_spoke_flow_logs_s3_dest_arn = "arn:aws:s3:::tgw-flow-logs-${local.peer_spoke_account_id}-${local.secondary_region}" tags = { Example = local.name - GithubRepo = "terraform-aws-eks" - GithubOrg = "terraform-aws-transit-gateway" + GithubRepo = "terraform-aws-transit-gateway" } } @@ -23,104 +57,598 @@ locals { # Transit Gateway Module ################################################################################ -module "tgw" { +module "primary_hub" { source = "../../" name = local.name - description = "My TGW shared with several other AWS accounts" + description = "Primary Hub TGW shared with several other AWS accounts in ${local.primary_region}" amazon_side_asn = 64532 - # When "true" there is no need for RAM resources if using multiple AWS accounts - enable_auto_accept_shared_attachments = true - - vpc_attachments = { - vpc1 = { - vpc_id = module.vpc1.vpc_id - subnet_ids = module.vpc1.private_subnets - dns_support = true - ipv6_support = true + create_tgw = true + # Creates RAM resources for hub (create_tgw = true) accounts + share_tgw = true + # When "true" there is no need for RAM resources if using multiple AWS accounts + enable_auto_accept_shared_attachments = false + + enable_default_route_table_association = false + enable_default_route_table_propagation = false + + flow_logs = [ + { + cloudwatch_dest_arn = local.primary_hub_flow_logs_cloudwatch_dest_arn + cloudwatch_iam_role_arn = local.primary_hub_flow_logs_cloudwatch_iam_role_arn + # Enable destinations after all TGWs/attachments are created/accepted + dest_enabled = true + dest_type = "cloud-watch-logs" + key = "tgw" + s3_dest_arn = null + }, + { + cloudwatch_dest_arn = null + cloudwatch_iam_role_arn = null + # Enable destinations after all TGWs/attachments are created/accepted + dest_enabled = true + dest_type = "s3" + key = "tgw" + s3_dest_arn = local.primary_hub_flow_logs_s3_dest_arn + }, + { + attachment_type = "vpc" + cloudwatch_dest_arn = local.primary_hub_flow_logs_cloudwatch_dest_arn + cloudwatch_iam_role_arn = local.primary_hub_flow_logs_cloudwatch_iam_role_arn + create_tgw_peering = false + create_vpc_attachment = true + # Enable destinations after all TGWs/attachments are created/accepted + dest_enabled = true + dest_type = "cloud-watch-logs" + key = "primary_hub_local" + s3_dest_arn = null + }, + { + attachment_type = "vpc" + cloudwatch_dest_arn = null + cloudwatch_iam_role_arn = null + create_tgw_peering = false + create_vpc_attachment = true + # Enable destinations after all TGWs/attachments are created/accepted + dest_enabled = true + dest_type = "s3" + key = "primary_hub_local" + s3_dest_arn = local.primary_hub_flow_logs_s3_dest_arn + }, + { + attachment_type = "peering" + cloudwatch_dest_arn = local.primary_hub_flow_logs_cloudwatch_dest_arn + cloudwatch_iam_role_arn = local.primary_hub_flow_logs_cloudwatch_iam_role_arn + create_tgw_peering = true + create_vpc_attachment = false + # Enable destinations after all TGWs/attachments are created/accepted + dest_enabled = true + dest_type = "cloud-watch-logs" + key = "peer_hub" + s3_dest_arn = null + }, + { + attachment_type = "peering" + cloudwatch_dest_arn = null + cloudwatch_iam_role_arn = null + create_tgw_peering = true + create_vpc_attachment = false + # Enable destinations after all TGWs/attachments are created/accepted + dest_enabled = true + dest_type = "s3" + key = "peer_hub" + s3_dest_arn = local.primary_hub_flow_logs_s3_dest_arn + }, + ] + + tgw_route_tables = [ + "prod", + "staging", + ] + + attachments = { + peer_hub = { + attachment_type = "peering" + create_tgw_peering = true + peer_account_id = local.peer_hub_account_id + peer_region = local.secondary_region + peer_tgw_id = module.peer_hub.ec2_transit_gateway_id + vpc_route_table_ids = module.primary_hub_vpc.private_route_table_ids + # Keep create_vpc_routes = false until the TGW is created and all attachments are created/accepted + create_vpc_routes = false + vpc_route_table_destination_cidrs = [ + module.peer_hub_vpc.vpc_cidr_block, + ] transit_gateway_default_route_table_association = false transit_gateway_default_route_table_propagation = false - - tgw_routes = [ - { - destination_cidr_block = "30.0.0.0/16" + # Keep create_tgw_routes = false until the VPC/peering attachments exist/are accepted + create_tgw_routes = true + tgw_route_table_propagations = { + prod = { + enable = true }, - { - blackhole = true - destination_cidr_block = "0.0.0.0/0" + staging = { + enable = true + } + } + tgw_routes = { + peer_hub = { + destination_cidr_blocks = module.peer_hub_vpc.vpc_cidr_block + route_table = "prod" } + blackhole = { + blackhole = true + destination_cidr_blocks = "0.0.0.0/0" + route_table = "prod" + } + } + } + primary_hub_local = { + attachment_type = "vpc" + create_vpc_attachment = true + # Keep enable_vpc_attachment = false until the corresponding VPC attachment is created/accepted + enable_vpc_attachment = false + vpc_id = module.primary_hub_vpc.vpc_id + subnet_ids = module.primary_hub_vpc.private_subnets + transit_gateway_default_route_table_association = false + transit_gateway_default_route_table_propagation = false + ipv6_support = true + # Keep create_tgw_routes = false until the VPC/peering attachments exist/are accepted + create_tgw_routes = true + tgw_route_table_propagations = { + prod = { + enable = true + } + } + tgw_routes = { + primary_hub_local = { + destination_cidr_blocks = module.primary_hub_vpc.vpc_cidr_block + route_table = "prod" + } + blackhole = { + blackhole = true + destination_cidr_blocks = "0.0.0.0/0" + route_table = "prod" + } + } + } + primary_spoke = { + attachment_type = "vpc" + # Keep accept_vpc_attachment = false until the corresponding VPC attachment is created + accept_vpc_attachment = false + # Keep enable_vpc_attachment = false until the corresponding VPC attachment is created + # Set to true when accepting the attachment + enable_vpc_attachment = false + vpc_id = module.primary_spoke_vpc.vpc_id + vpc_route_table_ids = module.primary_hub_vpc.private_route_table_ids + # Keep create_vpc_routes = false until the TGW is created and all attachments are created/accepted + create_vpc_routes = false + vpc_route_table_destination_cidrs = [ + module.primary_spoke_vpc.vpc_cidr_block, ] - }, - vpc2 = { - vpc_id = module.vpc2.vpc_id - subnet_ids = module.vpc2.private_subnets - - tgw_routes = [ - { - destination_cidr_block = "50.0.0.0/16" - }, - { - blackhole = true - destination_cidr_block = "10.10.10.10/32" + transit_gateway_default_route_table_association = false + transit_gateway_default_route_table_propagation = false + # Keep create_tgw_routes = false until the VPC/peering attachments exist/are accepted + create_tgw_routes = true + tgw_route_table_propagations = { + staging = { + enable = true + } + } + tgw_routes = { + primary_spoke = { + destination_cidr_blocks = module.primary_spoke_vpc.private_subnets_cidr_blocks + route_table = "staging" } + blackhole = { + blackhole = true + destination_cidr_blocks = ["0.0.0.0/0"] + route_table = "staging" + } + } + } + peer_spoke = { + attachment_type = "peering" + # For peering attachments that aren't accepted by this module, keep enable_peering_attachment = false until the peering attachment is accepted + enable_peering_attachment = true + peer_account_id = local.peer_hub_account_id + vpc_route_table_ids = module.primary_hub_vpc.private_route_table_ids + # Keep create_vpc_routes = false until the TGW is created and all attachments are created/accepted + create_vpc_routes = false + vpc_route_table_destination_cidrs = [ + module.peer_spoke_vpc.vpc_cidr_block, ] - }, + transit_gateway_default_route_table_association = false + transit_gateway_default_route_table_propagation = false + # Keep create_tgw_routes = false until the VPC/peering attachments exist/are accepted + create_tgw_routes = true + tgw_routes = { + peer_spoke = { + destination_cidr_blocks = module.peer_spoke_vpc.private_subnets_cidr_blocks + route_table = "staging" + } + blackhole = { + blackhole = true + destination_cidr_blocks = ["0.0.0.0/0"] + route_table = "staging" + } + } + } } - ram_allow_external_principals = true - ram_principals = [307990089504] + ram_allow_external_principals = false + ram_principals = [ + local.peer_hub_account_id, + local.primary_spoke_account_id, + ] tags = local.tags } -module "tgw_peer" { - # This is optional and connects to another account. Meaning you need to be authenticated with 2 separate AWS Accounts +module "peer_hub" { + # This is optional and connects to another account. Meaning you need to be authenticated with multiple separate AWS Accounts source = "../../" providers = { - aws = aws.peer + aws = aws.peer_hub } - name = "${local.name}-peer" - description = "My TGW shared with several other AWS accounts" + name = "${local.name}-peer-hub" + description = "Peer Hub TGW shared with several other AWS accounts in ${local.secondary_region}" amazon_side_asn = 64532 - create_tgw = false - share_tgw = true - ram_resource_share_arn = module.tgw.ram_resource_share_id - # When "true" there is no need for RAM resources if using multiple AWS accounts - enable_auto_accept_shared_attachments = true - - vpc_attachments = { - vpc1 = { - tgw_id = module.tgw.ec2_transit_gateway_id - vpc_id = module.vpc1.vpc_id - subnet_ids = module.vpc1.private_subnets - dns_support = true - ipv6_support = true + create_tgw = true + # Creates RAM resources for hub (create_tgw = true) accounts + share_tgw = true + # When "true" there is no need for RAM resources if using multiple AWS accounts + enable_auto_accept_shared_attachments = false + + enable_default_route_table_association = false + enable_default_route_table_propagation = false + + flow_logs = [ + { + cloudwatch_dest_arn = local.peer_hub_flow_logs_cloudwatch_dest_arn + cloudwatch_iam_role_arn = local.peer_hub_flow_logs_cloudwatch_iam_role_arn + dest_enabled = true + dest_type = "cloud-watch-logs" + key = "tgw" + s3_dest_arn = null + }, + { + cloudwatch_dest_arn = null + cloudwatch_iam_role_arn = null + dest_enabled = true + dest_type = "s3" + key = "tgw" + s3_dest_arn = local.peer_hub_flow_logs_s3_dest_arn + }, + { + attachment_type = "vpc" + cloudwatch_dest_arn = local.peer_hub_flow_logs_cloudwatch_dest_arn + cloudwatch_iam_role_arn = local.peer_hub_flow_logs_cloudwatch_iam_role_arn + create_tgw_peering = false + create_vpc_attachment = true + dest_enabled = true + dest_type = "cloud-watch-logs" + key = "peer_hub_local" + s3_dest_arn = null + }, + { + attachment_type = "vpc" + cloudwatch_dest_arn = null + cloudwatch_iam_role_arn = null + create_tgw_peering = false + create_vpc_attachment = true + dest_enabled = true + dest_type = "s3" + key = "peer_hub_local" + s3_dest_arn = local.peer_hub_flow_logs_s3_dest_arn + }, + ] + + tgw_route_tables = [ + "prod", + "staging", + ] + + attachments = { + primary_hub = { + attachment_type = "peering" + # Keep accept_tgw_peering = false until the peering attachment is created in the hub account + accept_tgw_peering = true + # For peering attachments to be accepted, keep enable_peering_attachment = false until the peering attachment is created in the hub account + # Set to true when accepting the attachment + enable_peering_attachment = true + peer_account_id = local.primary_hub_account_id + vpc_route_table_ids = module.peer_hub_vpc.private_route_table_ids + # Keep create_vpc_routes = false until the TGW is created and all attachments are created/accepted + create_vpc_routes = false + vpc_route_table_destination_cidrs = [ + module.primary_hub_vpc.vpc_cidr_block, + ] transit_gateway_default_route_table_association = false transit_gateway_default_route_table_propagation = false - - vpc_route_table_ids = module.vpc1.private_route_table_ids - tgw_destination_cidr = "0.0.0.0/0" - - tgw_routes = [ - { - destination_cidr_block = "30.0.0.0/16" - }, - { - blackhole = true - destination_cidr_block = "0.0.0.0/0" + # Keep create_tgw_routes = false until the VPC/peering attachments exist/are accepted + create_tgw_routes = true + tgw_routes = { + primary_hub = { + destination_cidr_blocks = module.primary_hub_vpc.private_subnets_cidr_blocks + route_table = "prod" + } + blackhole = { + blackhole = true + destination_cidr_blocks = ["0.0.0.0/0"] + } + } + } + peer_hub_local = { + attachment_type = "vpc" + create_vpc_attachment = true + # Keep enable_vpc_attachment = false until the corresponding VPC attachment is created/accepted + enable_vpc_attachment = false + vpc_id = module.peer_hub_vpc.vpc_id + subnet_ids = module.peer_hub_vpc.private_subnets + transit_gateway_default_route_table_association = false + transit_gateway_default_route_table_propagation = false + ipv6_support = true + # Keep create_tgw_routes = false until the VPC/peering attachments exist/are accepted + create_tgw_routes = true + tgw_route_table_propagations = { + prod = { + enable = true + } + } + tgw_routes = { + peer_hub_local = { + destination_cidr_blocks = module.peer_hub_vpc.private_subnets_cidr_blocks + route_table = "prod" + } + blackhole = { + blackhole = true + destination_cidr_blocks = ["0.0.0.0/0"] + } + } + } + peer_spoke = { + attachment_type = "vpc" + # Keep accept_vpc_attachment = false until the corresponding VPC attachment is created + accept_vpc_attachment = false + # Keep enable_vpc_attachment = false until the corresponding VPC attachment is created/accepted + enable_vpc_attachment = false + vpc_id = module.peer_spoke_vpc.vpc_id + vpc_route_table_ids = module.peer_hub_vpc.private_route_table_ids + # Keep create_vpc_routes = false until the TGW is created and all attachments are created/accepted + create_vpc_routes = false + vpc_route_table_destination_cidrs = [ + module.peer_spoke_vpc.vpc_cidr_block, + ] + transit_gateway_default_route_table_association = false + transit_gateway_default_route_table_propagation = false + # Keep create_tgw_routes = false until the VPC/peering attachments exist/are accepted + create_tgw_routes = true + tgw_route_table_propagations = { + staging = { + enable = true + } + } + tgw_routes = { + peer_spoke_account = { + destination_cidr_blocks = module.peer_spoke_vpc.private_subnets_cidr_blocks + route_table = "staging" } + blackhole = { + blackhole = true + destination_cidr_blocks = ["0.0.0.0/0"] + } + } + } + primary_spoke = { + attachment_type = "peering" + # For peering attachments that aren't accepted by this module, keep enable_peering_attachment = false until the peering attachment is accepted + enable_peering_attachment = true + peer_account_id = local.primary_hub_account_id + vpc_route_table_ids = module.peer_hub_vpc.private_route_table_ids + # Keep create_vpc_routes = false until the TGW is created and all attachments are created/accepted + create_vpc_routes = false + vpc_route_table_destination_cidrs = [ + module.primary_spoke_vpc.vpc_cidr_block, ] + transit_gateway_default_route_table_association = false + transit_gateway_default_route_table_propagation = false + # Keep create_tgw_routes = false until the VPC/peering attachments exist/are accepted + create_tgw_routes = true + tgw_routes = { + primary_spoke = { + destination_cidr_blocks = module.primary_spoke_vpc.private_subnets_cidr_blocks + route_table = "staging" + } + blackhole = { + blackhole = true + destination_cidr_blocks = ["0.0.0.0/0"] + } + } + } + } + + ram_allow_external_principals = false + ram_principals = [ + local.primary_hub_account_id, + local.peer_spoke_account_id, + ] + + tags = local.tags +} + +module "primary_spoke" { + # This is optional and connects to another account. Meaning you need to be authenticated with multiple separate AWS Accounts + source = "../../" + + providers = { + aws = aws.primary_spoke + } + + create_tgw = false + # Creates RAM Resource Share Accepter for spoke (create_tgw = false) accounts + share_tgw = true + + ram_resource_share_arn = module.primary_hub.ram_resource_share_id + description = "Primary Spoke sharing the TGW from the Primary Hub in ${local.primary_region}" + + flow_logs = [ + { + attachment_type = "vpc" + cloudwatch_dest_arn = local.primary_spoke_flow_logs_cloudwatch_dest_arn + cloudwatch_iam_role_arn = local.primary_spoke_flow_logs_cloudwatch_iam_role_arn + create_tgw_peering = false + create_vpc_attachment = true + dest_enabled = true + dest_type = "cloud-watch-logs" + key = "primary_spoke_local" + s3_dest_arn = null + }, + { + attachment_type = "vpc" + cloudwatch_dest_arn = null + cloudwatch_iam_role_arn = null + create_tgw_peering = false + create_vpc_attachment = true + dest_enabled = true + dest_type = "s3" + key = "primary_spoke_local" + s3_dest_arn = local.primary_spoke_flow_logs_s3_dest_arn }, + ] + + attachments = { + primary_spoke_local = { + attachment_type = "vpc" + create_vpc_attachment = true + # Keep enable_vpc_attachment = false until the corresponding VPC attachment is created/accepted + enable_vpc_attachment = false + tgw_id = module.primary_hub.ec2_transit_gateway_id + vpc_id = module.primary_spoke_vpc.vpc_id + subnet_ids = module.primary_spoke_vpc.private_subnets + ipv6_support = true + } + primary_hub = { + attachment_type = "vpc" + # Keep enable_vpc_attachment = false until the corresponding VPC attachment is created/accepted + enable_vpc_attachment = false + tgw_id = module.primary_hub.ec2_transit_gateway_id + vpc_route_table_ids = module.primary_spoke_vpc.private_route_table_ids + vpc_route_table_destination_cidrs = [ + module.primary_hub_vpc.vpc_cidr_block, + ] + } + peer_hub = { + attachment_type = "peering" + tgw_id = module.primary_hub.ec2_transit_gateway_id + vpc_route_table_ids = module.primary_spoke_vpc.private_route_table_ids + vpc_route_table_destination_cidrs = [ + module.peer_hub_vpc.vpc_cidr_block, + ] + } + peer_spoke = { + attachment_type = "peering" + tgw_id = module.primary_hub.ec2_transit_gateway_id + vpc_route_table_ids = module.primary_spoke_vpc.private_route_table_ids + vpc_route_table_destination_cidrs = [ + module.peer_spoke_vpc.vpc_cidr_block, + ] + } } - ram_allow_external_principals = true - ram_principals = [307990089504] + tags = local.tags +} + +module "peer_spoke" { + # This is optional and connects to another account. Meaning you need to be authenticated with multiple separate AWS Accounts + source = "../../" + + providers = { + aws = aws.peer_spoke + } + + create_tgw = false + # Creates RAM Resource Share Accepter for spoke (create_tgw = false) accounts + share_tgw = true + + ram_resource_share_arn = module.peer_hub.ram_resource_share_id + description = "Peer Spoke sharing the TGW from the Peer Hub in ${local.secondary_region}" + + flow_logs = [ + { + attachment_type = "vpc" + cloudwatch_dest_arn = local.peer_spoke_flow_logs_cloudwatch_dest_arn + cloudwatch_iam_role_arn = local.peer_spoke_flow_logs_cloudwatch_iam_role_arn + create_tgw_peering = false + create_vpc_attachment = true + dest_enabled = true + dest_type = "cloud-watch-logs" + key = "peer_spoke_local" + s3_dest_arn = null + }, + { + attachment_type = "vpc" + cloudwatch_dest_arn = null + cloudwatch_iam_role_arn = null + create_tgw_peering = false + create_vpc_attachment = true + dest_enabled = true + dest_type = "s3" + key = "peer_spoke_local" + s3_dest_arn = local.peer_spoke_flow_logs_s3_dest_arn + }, + ] + + attachments = { + peer_spoke_local = { + attachment_type = "vpc" + create_vpc_attachment = true + # Keep enable_vpc_attachment = false until the corresponding VPC attachment is created/accepted + enable_vpc_attachment = false + tgw_id = module.peer_hub.ec2_transit_gateway_id + vpc_id = module.peer_spoke_vpc.vpc_id + subnet_ids = module.peer_spoke_vpc.private_subnets + ipv6_support = true + } + peer_hub = { + attachment_type = "vpc" + # Keep enable_vpc_attachment = false until the corresponding VPC attachment is created/accepted + enable_vpc_attachment = false + tgw_id = module.peer_hub.ec2_transit_gateway_id + vpc_route_table_ids = module.peer_spoke_vpc.private_route_table_ids + # Keep create_vpc_routes = false until the TGW is created and all attachments are created/accepted + create_vpc_routes = false + vpc_route_table_destination_cidrs = [ + module.peer_hub_vpc.vpc_cidr_block, + ] + } + primary_hub = { + attachment_type = "peering" + tgw_id = module.peer_hub.ec2_transit_gateway_id + vpc_route_table_ids = module.peer_spoke_vpc.private_route_table_ids + # Keep create_vpc_routes = false until the TGW is created and all attachments are created/accepted + create_vpc_routes = false + vpc_route_table_destination_cidrs = [ + module.primary_hub_vpc.vpc_cidr_block, + ] + } + primary_spoke = { + attachment_type = "peering" + tgw_id = module.peer_hub.ec2_transit_gateway_id + vpc_route_table_ids = module.peer_spoke_vpc.private_route_table_ids + # Keep create_vpc_routes = false until the TGW is created and all attachments are created/accepted + create_vpc_routes = false + vpc_route_table_destination_cidrs = [ + module.primary_spoke_vpc.vpc_cidr_block, + ] + } + } tags = local.tags } @@ -129,14 +657,14 @@ module "tgw_peer" { # Supporting resources ################################################################################ -module "vpc1" { +module "primary_hub_vpc" { source = "terraform-aws-modules/vpc/aws" version = "~> 5.0" - name = "${local.name}-vpc1" + name = "${local.name}-primary-hub-vpc" cidr = "10.10.0.0/16" - azs = ["${local.region}a", "${local.region}b", "${local.region}c"] + azs = ["${local.primary_region}a", "${local.primary_region}b", "${local.primary_region}c"] private_subnets = ["10.10.1.0/24", "10.10.2.0/24", "10.10.3.0/24"] enable_ipv6 = true @@ -147,21 +675,65 @@ module "vpc1" { } -module "vpc2" { +module "peer_hub_vpc" { source = "terraform-aws-modules/vpc/aws" version = "~> 5.0" providers = { - aws = aws.peer + aws = aws.peer_hub } - name = "${local.name}-vpc2" + name = "${local.name}-peer-hub-vpc" cidr = "10.20.0.0/16" - azs = ["${local.region}a", "${local.region}b", "${local.region}c"] + azs = ["${local.secondary_region}a", "${local.secondary_region}b", "${local.secondary_region}c"] private_subnets = ["10.20.1.0/24", "10.20.2.0/24", "10.20.3.0/24"] - enable_ipv6 = false + enable_ipv6 = true + private_subnet_assign_ipv6_address_on_creation = true + private_subnet_ipv6_prefixes = [3, 4, 5] + + tags = local.tags +} + +module "primary_spoke_vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.0" + + providers = { + aws = aws.primary_spoke + } + + name = "${local.name}-primary-spoke-vpc" + cidr = "10.30.0.0/16" + + azs = ["${local.primary_region}a", "${local.primary_region}b", "${local.primary_region}c"] + private_subnets = ["10.30.1.0/24", "10.30.2.0/24", "10.30.3.0/24"] + + enable_ipv6 = true + private_subnet_assign_ipv6_address_on_creation = true + private_subnet_ipv6_prefixes = [0, 1, 2] + + tags = local.tags +} + +module "peer_spoke_vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.0" + + providers = { + aws = aws.peer_spoke + } + + name = "${local.name}-peer-spoke-vpc" + cidr = "10.40.0.0/16" + + azs = ["${local.secondary_region}a", "${local.secondary_region}b", "${local.secondary_region}c"] + private_subnets = ["10.40.1.0/24", "10.40.2.0/24", "10.40.3.0/24"] + + enable_ipv6 = true + private_subnet_assign_ipv6_address_on_creation = true + private_subnet_ipv6_prefixes = [9, 10, 11] tags = local.tags } diff --git a/examples/multi-account/outputs.tf b/examples/multi-account/outputs.tf index b9a0a40..8fa3fd6 100644 --- a/examples/multi-account/outputs.tf +++ b/examples/multi-account/outputs.tf @@ -1,100 +1,229 @@ ################################################################################ -# Transit Gateway +# Primary Hub Transit Gateway ################################################################################ -output "ec2_transit_gateway_arn" { +output "primary_hub_ec2_transit_gateway_arn" { description = "EC2 Transit Gateway Amazon Resource Name (ARN)" - value = module.tgw.ec2_transit_gateway_arn + value = module.primary_hub.ec2_transit_gateway_arn } -output "ec2_transit_gateway_id" { +output "primary_hub_ec2_transit_gateway_id" { description = "EC2 Transit Gateway identifier" - value = module.tgw.ec2_transit_gateway_id + value = module.primary_hub.ec2_transit_gateway_id } -output "ec2_transit_gateway_owner_id" { +output "primary_hub_ec2_transit_gateway_owner_id" { description = "Identifier of the AWS account that owns the EC2 Transit Gateway" - value = module.tgw.ec2_transit_gateway_owner_id + value = module.primary_hub.ec2_transit_gateway_owner_id } -output "ec2_transit_gateway_association_default_route_table_id" { +output "primary_hub_ec2_transit_gateway_association_default_route_table_id" { description = "Identifier of the default association route table" - value = module.tgw.ec2_transit_gateway_association_default_route_table_id + value = module.primary_hub.ec2_transit_gateway_association_default_route_table_id } -output "ec2_transit_gateway_propagation_default_route_table_id" { +output "primary_hub_ec2_transit_gateway_propagation_default_route_table_id" { description = "Identifier of the default propagation route table" - value = module.tgw.ec2_transit_gateway_propagation_default_route_table_id + value = module.primary_hub.ec2_transit_gateway_propagation_default_route_table_id } ################################################################################ -# VPC Attachment +# Primary Hub VPC Attachment ################################################################################ -output "ec2_transit_gateway_vpc_attachment_ids" { +output "primary_hub_ec2_transit_gateway_vpc_attachment_ids" { description = "List of EC2 Transit Gateway VPC Attachment identifiers" - value = module.tgw.ec2_transit_gateway_vpc_attachment_ids + value = module.primary_hub.ec2_transit_gateway_vpc_attachment_ids } -output "ec2_transit_gateway_vpc_attachment" { +output "primary_hub_ec2_transit_gateway_vpc_attachment" { description = "Map of EC2 Transit Gateway VPC Attachment attributes" - value = module.tgw.ec2_transit_gateway_vpc_attachment + value = module.primary_hub.ec2_transit_gateway_vpc_attachment } ################################################################################ -# Route Table / Routes +# Primary Hub Route Table / Routes ################################################################################ -output "ec2_transit_gateway_route_table_id" { +output "primary_hub_ec2_transit_gateway_route_table_id" { description = "EC2 Transit Gateway Route Table identifier" - value = module.tgw.ec2_transit_gateway_route_table_id + value = module.primary_hub.ec2_transit_gateway_route_table_id } -output "ec2_transit_gateway_route_table_default_association_route_table" { +output "primary_hub_ec2_transit_gateway_route_table_default_association_route_table" { description = "Boolean whether this is the default association route table for the EC2 Transit Gateway" - value = module.tgw.ec2_transit_gateway_route_table_default_association_route_table + value = module.primary_hub.ec2_transit_gateway_route_table_default_association_route_table } -output "ec2_transit_gateway_route_table_default_propagation_route_table" { +output "primary_hub_ec2_transit_gateway_route_table_default_propagation_route_table" { description = "Boolean whether this is the default propagation route table for the EC2 Transit Gateway" - value = module.tgw.ec2_transit_gateway_route_table_default_propagation_route_table + value = module.primary_hub.ec2_transit_gateway_route_table_default_propagation_route_table } -output "ec2_transit_gateway_route_ids" { +output "primary_hub_ec2_transit_gateway_route_ids" { description = "List of EC2 Transit Gateway Route Table identifier combined with destination" - value = module.tgw.ec2_transit_gateway_route_ids + value = module.primary_hub.ec2_transit_gateway_route_ids } -output "ec2_transit_gateway_route_table_association_ids" { +output "primary_hub_ec2_transit_gateway_route_table_association_ids" { description = "List of EC2 Transit Gateway Route Table Association identifiers" - value = module.tgw.ec2_transit_gateway_route_table_association_ids + value = module.primary_hub.ec2_transit_gateway_route_table_association_ids } -output "ec2_transit_gateway_route_table_association" { +output "primary_hub_ec2_transit_gateway_route_table_association" { description = "Map of EC2 Transit Gateway Route Table Association attributes" - value = module.tgw.ec2_transit_gateway_route_table_association + value = module.primary_hub.ec2_transit_gateway_route_table_association } -output "ec2_transit_gateway_route_table_propagation_ids" { +output "primary_hub_ec2_transit_gateway_route_table_propagation_ids" { description = "List of EC2 Transit Gateway Route Table Propagation identifiers" - value = module.tgw.ec2_transit_gateway_route_table_propagation_ids + value = module.primary_hub.ec2_transit_gateway_route_table_propagation_ids } -output "ec2_transit_gateway_route_table_propagation" { +output "primary_hub_ec2_transit_gateway_route_table_propagation" { description = "Map of EC2 Transit Gateway Route Table Propagation attributes" - value = module.tgw.ec2_transit_gateway_route_table_propagation + value = module.primary_hub.ec2_transit_gateway_route_table_propagation } ################################################################################ -# Resource Access Manager +# Primary Hub Resource Access Manager ################################################################################ -output "ram_resource_share_id" { +output "primary_hub_ram_resource_share_id" { description = "The Amazon Resource Name (ARN) of the resource share" - value = module.tgw.ram_resource_share_id + value = module.primary_hub.ram_resource_share_id } -output "ram_principal_association_id" { +output "primary_hub_ram_principal_association_id" { description = "The Amazon Resource Name (ARN) of the Resource Share and the principal, separated by a comma" - value = module.tgw.ram_principal_association_id + value = module.primary_hub.ram_principal_association_id +} + +################################################################################ +# Primary Spoke VPC Attachment +################################################################################ + +output "primary_spoke_ec2_transit_gateway_vpc_attachment_ids" { + description = "List of EC2 Transit Gateway VPC Attachment identifiers" + value = module.primary_spoke.ec2_transit_gateway_vpc_attachment_ids +} + +output "primary_spoke_ec2_transit_gateway_vpc_attachment" { + description = "Map of EC2 Transit Gateway VPC Attachment attributes" + value = module.primary_spoke.ec2_transit_gateway_vpc_attachment +} + +################################################################################ +# Peer Hub Transit Gateway +################################################################################ + +output "peer_hub_ec2_transit_gateway_arn" { + description = "EC2 Transit Gateway Amazon Resource Name (ARN)" + value = module.peer_hub.ec2_transit_gateway_arn +} + +output "peer_hub_ec2_transit_gateway_id" { + description = "EC2 Transit Gateway identifier" + value = module.peer_hub.ec2_transit_gateway_id +} + +output "peer_hub_ec2_transit_gateway_owner_id" { + description = "Identifier of the AWS account that owns the EC2 Transit Gateway" + value = module.peer_hub.ec2_transit_gateway_owner_id +} + +output "peer_hub_ec2_transit_gateway_association_default_route_table_id" { + description = "Identifier of the default association route table" + value = module.peer_hub.ec2_transit_gateway_association_default_route_table_id +} + +output "peer_hub_ec2_transit_gateway_propagation_default_route_table_id" { + description = "Identifier of the default propagation route table" + value = module.peer_hub.ec2_transit_gateway_propagation_default_route_table_id +} + +################################################################################ +# Peer Hub VPC Attachment +################################################################################ + +output "peer_hub_ec2_transit_gateway_vpc_attachment_ids" { + description = "List of EC2 Transit Gateway VPC Attachment identifiers" + value = module.peer_hub.ec2_transit_gateway_vpc_attachment_ids +} + +output "peer_hub_ec2_transit_gateway_vpc_attachment" { + description = "Map of EC2 Transit Gateway VPC Attachment attributes" + value = module.peer_hub.ec2_transit_gateway_vpc_attachment +} + +################################################################################ +# Peer Hub Route Table / Routes +################################################################################ + +output "peer_hub_ec2_transit_gateway_route_table_id" { + description = "EC2 Transit Gateway Route Table identifier" + value = module.peer_hub.ec2_transit_gateway_route_table_id +} + +output "peer_hub_ec2_transit_gateway_route_table_default_association_route_table" { + description = "Boolean whether this is the default association route table for the EC2 Transit Gateway" + value = module.peer_hub.ec2_transit_gateway_route_table_default_association_route_table +} + +output "peer_hub_ec2_transit_gateway_route_table_default_propagation_route_table" { + description = "Boolean whether this is the default propagation route table for the EC2 Transit Gateway" + value = module.peer_hub.ec2_transit_gateway_route_table_default_propagation_route_table +} + +output "peer_hub_ec2_transit_gateway_route_ids" { + description = "List of EC2 Transit Gateway Route Table identifier combined with destination" + value = module.peer_hub.ec2_transit_gateway_route_ids +} + +output "peer_hub_ec2_transit_gateway_route_table_association_ids" { + description = "List of EC2 Transit Gateway Route Table Association identifiers" + value = module.peer_hub.ec2_transit_gateway_route_table_association_ids +} + +output "peer_hub_ec2_transit_gateway_route_table_association" { + description = "Map of EC2 Transit Gateway Route Table Association attributes" + value = module.peer_hub.ec2_transit_gateway_route_table_association +} + +output "peer_hub_ec2_transit_gateway_route_table_propagation_ids" { + description = "List of EC2 Transit Gateway Route Table Propagation identifiers" + value = module.peer_hub.ec2_transit_gateway_route_table_propagation_ids +} + +output "peer_hub_ec2_transit_gateway_route_table_propagation" { + description = "Map of EC2 Transit Gateway Route Table Propagation attributes" + value = module.peer_hub.ec2_transit_gateway_route_table_propagation +} + +################################################################################ +# Peer Hub Resource Access Manager +################################################################################ + +output "peer_hub_ram_resource_share_id" { + description = "The Amazon Resource Name (ARN) of the resource share" + value = module.peer_hub.ram_resource_share_id +} + +output "peer_hub_ram_principal_association_id" { + description = "The Amazon Resource Name (ARN) of the Resource Share and the principal, separated by a comma" + value = module.peer_hub.ram_principal_association_id +} + +################################################################################ +# Peer Spoke VPC Attachment +################################################################################ + +output "peer_spoke_ec2_transit_gateway_vpc_attachment_ids" { + description = "List of EC2 Transit Gateway VPC Attachment identifiers" + value = module.peer_spoke.ec2_transit_gateway_vpc_attachment_ids +} + +output "peer_spoke_ec2_transit_gateway_vpc_attachment" { + description = "Map of EC2 Transit Gateway VPC Attachment attributes" + value = module.peer_spoke.ec2_transit_gateway_vpc_attachment } diff --git a/examples/multi-account/versions.tf b/examples/multi-account/versions.tf index 46b7087..ddfcb0e 100644 --- a/examples/multi-account/versions.tf +++ b/examples/multi-account/versions.tf @@ -4,7 +4,7 @@ terraform { required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.4" + version = ">= 5.0" } } } diff --git a/main.tf b/main.tf index 0f642b9..86504dd 100644 --- a/main.tf +++ b/main.tf @@ -1,8 +1,34 @@ locals { # List of maps with key and route values - vpc_attachments_with_routes = chunklist(flatten([ - for k, v in var.vpc_attachments : setproduct([{ key = k }], v.tgw_routes) if var.create_tgw && can(v.tgw_routes) - ]), 2) + attachments_with_route_keys = flatten([ + for attachment_key, attachment_value in var.attachments : [ + for route_key, route_value in try(attachment_value.tgw_routes, {}) : [ + for cidr_block in try(route_value.destination_cidr_blocks, ["no-cidrs-defined"]) : { + accept_tgw_peering = try(attachment_value.accept_tgw_peering, null) + attachment_id = { + peering = try( + aws_ec2_transit_gateway_peering_attachment.this[attachment_key].id, + data.aws_ec2_transit_gateway_peering_attachment.this[attachment_key].id, + null + ) + vpc = try( + data.aws_ec2_transit_gateway_vpc_attachment.this[attachment_key].id, + null + ) + }[attachment_value.attachment_type] + attachment_key = attachment_key + attachment_type = attachment_value.attachment_type + create_tgw_routes = try(attachment_value.create_tgw_routes, false) + route_dest = cidr_block + route_key = route_key + route_table = try(route_value.route_table, var.tgw_route_tables[0]) + route_value = route_value + tgw_default_route_table_association = try(attachment_value.transit_gateway_default_route_table_association, true) + tgw_default_route_table_propagation = try(attachment_value.transit_gateway_default_route_table_propagation, true) + } if var.create_tgw && try(attachment_value.create_tgw_routes, false) + ] + ] + ]) tgw_default_route_table_tags_merged = merge( var.tags, @@ -10,13 +36,31 @@ locals { var.tgw_default_route_table_tags, ) - vpc_route_table_destination_cidr = flatten([ - for k, v in var.vpc_attachments : [ - for rtb_id in try(v.vpc_route_table_ids, []) : { - rtb_id = rtb_id - cidr = v.tgw_destination_cidr - tgw_id = var.create_tgw ? aws_ec2_transit_gateway.this[0].id : v.tgw_id - } + tgw_route_table_propagations = flatten([ + for attachment_key, attachment_value in var.attachments : [ + for tgw_rtb_name, tgw_rtb_value in try(attachment_value.tgw_route_table_propagations, {}) : { + attachment_id = try(data.aws_ec2_transit_gateway_vpc_attachment.this[attachment_key].id, null) + attachment_key = attachment_key + enable_propagation = try(tgw_rtb_value.enable, false) + rtb_name = tgw_rtb_name + } if var.create_tgw && + try(attachment_value.create_tgw_routes, false) && + try(attachment_value.transit_gateway_default_route_table_propagation, true) == false && + attachment_value.attachment_type != "peering" + ] + ]) + + vpc_route_table_destinations = flatten([ + for k, v in var.attachments : [ + for rtb_id in try(v.vpc_route_table_ids, []) : [ + for cidr in try(v.vpc_route_table_destination_cidrs, []) : { + cidr = cidr + create_vpc_routes = try(v.create_vpc_routes, false) + ipv6_support = try(v.ipv6_support, false) + rtb_id = rtb_id + tgw_id = !var.create_tgw ? try(v.tgw_id, null) : null + } if try(v.create_vpc_routes, false) + ] ] ]) } @@ -64,7 +108,7 @@ resource "aws_ec2_tag" "this" { ################################################################################ resource "aws_ec2_transit_gateway_vpc_attachment" "this" { - for_each = var.vpc_attachments + for_each = { for k, v in var.attachments : k => v if v.attachment_type == "vpc" && try(v.create_vpc_attachment, false) } transit_gateway_id = var.create_tgw ? aws_ec2_transit_gateway.this[0].id : each.value.tgw_id vpc_id = each.value.vpc_id @@ -78,10 +122,61 @@ resource "aws_ec2_transit_gateway_vpc_attachment" "this" { tags = merge( var.tags, - { Name = var.name }, - var.tgw_vpc_attachment_tags, + { Name = each.key }, + var.tgw_attachment_tags, + ) + + lifecycle { + ignore_changes = [ + transit_gateway_default_route_table_association, + transit_gateway_default_route_table_propagation, + ] + } +} + +data "aws_ec2_transit_gateway_vpc_attachment" "this" { + for_each = { for k, v in var.attachments : k => v if v.attachment_type == "vpc" && try(v.enable_vpc_attachment, false) } + + filter { + name = "vpc-id" + values = [each.value.vpc_id] + } + + filter { + name = "transit-gateway-id" + values = [var.create_tgw ? aws_ec2_transit_gateway.this[0].id : each.value.tgw_id] + } + + filter { + name = "state" + values = ["pendingAcceptance", "available"] + } + + depends_on = [ + aws_ec2_transit_gateway_vpc_attachment.this + ] +} + +resource "aws_ec2_transit_gateway_vpc_attachment_accepter" "this" { + for_each = { for k, v in var.attachments : k => v if v.attachment_type == "vpc" && try(v.accept_vpc_attachment, false) } + + transit_gateway_attachment_id = data.aws_ec2_transit_gateway_vpc_attachment.this[each.key].id + transit_gateway_default_route_table_association = try(each.value.transit_gateway_default_route_table_association, true) + transit_gateway_default_route_table_propagation = try(each.value.transit_gateway_default_route_table_propagation, true) + + tags = merge( + var.tags, + { Name = each.key }, + var.tgw_attachment_tags, try(each.value.tags, {}), ) + + lifecycle { + ignore_changes = [ + transit_gateway_default_route_table_association, + transit_gateway_default_route_table_propagation, + ] + } } ################################################################################ @@ -89,57 +184,65 @@ resource "aws_ec2_transit_gateway_vpc_attachment" "this" { ################################################################################ resource "aws_ec2_transit_gateway_route_table" "this" { - count = var.create_tgw && var.create_tgw_routes ? 1 : 0 + for_each = var.create_tgw ? var.tgw_route_tables : toset([]) transit_gateway_id = aws_ec2_transit_gateway.this[0].id tags = merge( var.tags, - { Name = var.name }, + { Name = "${var.name}-${each.key}" }, var.tgw_route_table_tags, ) } resource "aws_ec2_transit_gateway_route" "this" { - count = var.create_tgw_routes ? length(local.vpc_attachments_with_routes) : 0 + for_each = { for attachment_route in local.attachments_with_route_keys : "${attachment_route.attachment_key}-${attachment_route.route_table}-${attachment_route.route_dest}" => attachment_route if var.create_tgw && attachment_route.route_dest != "no-cidrs-defined" } - destination_cidr_block = local.vpc_attachments_with_routes[count.index][1].destination_cidr_block - blackhole = try(local.vpc_attachments_with_routes[count.index][1].blackhole, null) + destination_cidr_block = each.value.route_dest + blackhole = try(each.value.route_value.blackhole, null) - transit_gateway_route_table_id = var.create_tgw ? aws_ec2_transit_gateway_route_table.this[0].id : var.transit_gateway_route_table_id - transit_gateway_attachment_id = tobool(try(local.vpc_attachments_with_routes[count.index][1].blackhole, false)) == false ? aws_ec2_transit_gateway_vpc_attachment.this[local.vpc_attachments_with_routes[count.index][0].key].id : null + transit_gateway_route_table_id = var.create_tgw ? aws_ec2_transit_gateway_route_table.this[each.value.route_table].id : var.transit_gateway_route_table_id + transit_gateway_attachment_id = try(each.value.route_value.blackhole, false) == false ? each.value.attachment_id : null } resource "aws_route" "this" { - for_each = { for x in local.vpc_route_table_destination_cidr : x.rtb_id => { - cidr = x.cidr, - tgw_id = x.tgw_id - } } + for_each = { for route in local.vpc_route_table_destinations : "${route.rtb_id}-${route.cidr}" => route if route.create_vpc_routes } - route_table_id = each.key - destination_cidr_block = try(each.value.ipv6_support, false) ? null : each.value["cidr"] - destination_ipv6_cidr_block = try(each.value.ipv6_support, false) ? each.value["cidr"] : null - transit_gateway_id = each.value["tgw_id"] + route_table_id = each.value.rtb_id + destination_cidr_block = try(each.value.ipv6_support, false) ? null : each.value.cidr + destination_ipv6_cidr_block = try(each.value.ipv6_support, false) ? each.value.cidr : null + transit_gateway_id = var.create_tgw ? aws_ec2_transit_gateway.this[0].id : each.value.tgw_id + + depends_on = [ + aws_ec2_transit_gateway.this + ] } resource "aws_ec2_transit_gateway_route_table_association" "this" { - for_each = { - for k, v in var.vpc_attachments : k => v if var.create_tgw && var.create_tgw_routes && try(v.transit_gateway_default_route_table_association, true) != true + for_each = { for attachment_route in local.attachments_with_route_keys : "${attachment_route.attachment_key}-${attachment_route.route_table}" => attachment_route... if var.create_tgw && attachment_route.create_tgw_routes && attachment_route.accept_tgw_peering == null && attachment_route.tgw_default_route_table_association == false && contains(keys(aws_ec2_transit_gateway_route_table.this), attachment_route.route_table) } - # Create association if it was not set already by aws_ec2_transit_gateway_vpc_attachment resource - transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.this[each.key].id - transit_gateway_route_table_id = var.create_tgw ? aws_ec2_transit_gateway_route_table.this[0].id : try(each.value.transit_gateway_route_table_id, var.transit_gateway_route_table_id) + transit_gateway_attachment_id = each.value[0].attachment_id + transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.this[each.value[0].route_table].id + + depends_on = [ + aws_ec2_transit_gateway_peering_attachment.this, + data.aws_ec2_transit_gateway_peering_attachment.this, + data.aws_ec2_transit_gateway_vpc_attachment.this, + aws_ec2_transit_gateway_route_table.this + ] } resource "aws_ec2_transit_gateway_route_table_propagation" "this" { - for_each = { - for k, v in var.vpc_attachments : k => v if var.create_tgw && var.create_tgw_routes && try(v.transit_gateway_default_route_table_propagation, true) != true - } + for_each = { for attachment in local.tgw_route_table_propagations : "${attachment.attachment_key}-${attachment.rtb_name}" => attachment if attachment.enable_propagation && contains(keys(aws_ec2_transit_gateway_route_table.this), attachment.rtb_name) } + + transit_gateway_attachment_id = each.value.attachment_id + transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.this[each.value.rtb_name].id - # Create association if it was not set already by aws_ec2_transit_gateway_vpc_attachment resource - transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.this[each.key].id - transit_gateway_route_table_id = var.create_tgw ? aws_ec2_transit_gateway_route_table.this[0].id : try(each.value.transit_gateway_route_table_id, var.transit_gateway_route_table_id) + depends_on = [ + data.aws_ec2_transit_gateway_vpc_attachment.this, + aws_ec2_transit_gateway_route_table.this + ] } ################################################################################ @@ -167,9 +270,9 @@ resource "aws_ram_resource_association" "this" { } resource "aws_ram_principal_association" "this" { - count = var.create_tgw && var.share_tgw ? length(var.ram_principals) : 0 + for_each = var.create_tgw && var.share_tgw ? var.ram_principals : [] - principal = var.ram_principals[count.index] + principal = each.value resource_share_arn = aws_ram_resource_share.this[0].arn } @@ -178,3 +281,48 @@ resource "aws_ram_resource_share_accepter" "this" { share_arn = var.ram_resource_share_arn } + +################################################################################ +# TGW Peering +################################################################################ + +resource "aws_ec2_transit_gateway_peering_attachment" "this" { + for_each = { for k, v in var.attachments : k => v if v.attachment_type == "peering" && try(v.create_tgw_peering, false) } + + peer_account_id = each.value.peer_account_id + peer_region = each.value.peer_region + peer_transit_gateway_id = each.value.peer_tgw_id + transit_gateway_id = aws_ec2_transit_gateway.this[0].id + + tags = merge( + var.tags, + { Name = var.name }, + var.tgw_tags, + ) +} + +data "aws_ec2_transit_gateway_peering_attachment" "this" { + for_each = { for k, v in var.attachments : k => v if v.attachment_type == "peering" && try(v.enable_peering_attachment, false) && try(v.create_tgw_peering, false) == false } + + filter { + name = "remote-owner-id" + values = [each.value.peer_account_id] + } + + filter { + name = "state" + values = ["pendingAcceptance", "available"] + } +} + +resource "aws_ec2_transit_gateway_peering_attachment_accepter" "this" { + for_each = { for k, v in var.attachments : k => v if try(v.accept_tgw_peering, false) } + + transit_gateway_attachment_id = data.aws_ec2_transit_gateway_peering_attachment.this[each.key].id + + tags = merge( + var.tags, + { Name = var.name }, + var.tgw_tags, + ) +} diff --git a/outputs.tf b/outputs.tf index 8dcb8a5..f1bc7ac 100644 --- a/outputs.tf +++ b/outputs.tf @@ -4,27 +4,27 @@ output "ec2_transit_gateway_arn" { description = "EC2 Transit Gateway Amazon Resource Name (ARN)" - value = try(aws_ec2_transit_gateway.this[0].arn, "") + value = try(aws_ec2_transit_gateway.this[0].arn, null) } output "ec2_transit_gateway_id" { description = "EC2 Transit Gateway identifier" - value = try(aws_ec2_transit_gateway.this[0].id, "") + value = try(aws_ec2_transit_gateway.this[0].id, null) } output "ec2_transit_gateway_owner_id" { description = "Identifier of the AWS account that owns the EC2 Transit Gateway" - value = try(aws_ec2_transit_gateway.this[0].owner_id, "") + value = try(aws_ec2_transit_gateway.this[0].owner_id, null) } output "ec2_transit_gateway_association_default_route_table_id" { description = "Identifier of the default association route table" - value = try(aws_ec2_transit_gateway.this[0].association_default_route_table_id, "") + value = try(aws_ec2_transit_gateway.this[0].association_default_route_table_id, null) } output "ec2_transit_gateway_propagation_default_route_table_id" { description = "Identifier of the default propagation route table" - value = try(aws_ec2_transit_gateway.this[0].propagation_default_route_table_id, "") + value = try(aws_ec2_transit_gateway.this[0].propagation_default_route_table_id, null) } ################################################################################ @@ -47,22 +47,22 @@ output "ec2_transit_gateway_vpc_attachment" { output "ec2_transit_gateway_route_table_id" { description = "EC2 Transit Gateway Route Table identifier" - value = try(aws_ec2_transit_gateway_route_table.this[0].id, "") + value = try(aws_ec2_transit_gateway_route_table.this[0].id, null) } output "ec2_transit_gateway_route_table_default_association_route_table" { description = "Boolean whether this is the default association route table for the EC2 Transit Gateway" - value = try(aws_ec2_transit_gateway_route_table.this[0].default_association_route_table, "") + value = try(aws_ec2_transit_gateway_route_table.this[0].default_association_route_table, null) } output "ec2_transit_gateway_route_table_default_propagation_route_table" { description = "Boolean whether this is the default propagation route table for the EC2 Transit Gateway" - value = try(aws_ec2_transit_gateway_route_table.this[0].default_propagation_route_table, "") + value = try(aws_ec2_transit_gateway_route_table.this[0].default_propagation_route_table, null) } output "ec2_transit_gateway_route_ids" { description = "List of EC2 Transit Gateway Route Table identifier combined with destination" - value = aws_ec2_transit_gateway_route.this[*].id + value = tomap({ for k, route in aws_ec2_transit_gateway_route.this : k => route.id }) } output "ec2_transit_gateway_route_table_association_ids" { @@ -91,10 +91,10 @@ output "ec2_transit_gateway_route_table_propagation" { output "ram_resource_share_id" { description = "The Amazon Resource Name (ARN) of the resource share" - value = try(aws_ram_resource_share.this[0].id, "") + value = try(aws_ram_resource_share.this[0].id, null) } output "ram_principal_association_id" { description = "The Amazon Resource Name (ARN) of the Resource Share and the principal, separated by a comma" - value = try(aws_ram_principal_association.this[0].id, "") + value = [for k, v in aws_ram_principal_association.this : v.id] } diff --git a/tgw-flow-logs.tf b/tgw-flow-logs.tf new file mode 100644 index 0000000..9d4be43 --- /dev/null +++ b/tgw-flow-logs.tf @@ -0,0 +1,37 @@ +resource "aws_flow_log" "this" { + for_each = { for k, v in var.flow_logs : "${v.key}-${v.dest_type}" => v if v.dest_enabled } + + log_destination_type = each.value.dest_type + log_destination = { + s3 = each.value.s3_dest_arn, + cloud-watch-logs = each.value.cloudwatch_dest_arn + }[each.value.dest_type] + log_format = try(each.value.log_format, null) + iam_role_arn = each.value.dest_type == "cloud-watch-logs" ? each.value.cloudwatch_iam_role_arn : null + traffic_type = try(each.value.traffic_type, "ALL") + transit_gateway_id = each.value.key == "tgw" && var.create_tgw ? aws_ec2_transit_gateway.this[0].id : null + transit_gateway_attachment_id = each.value.key != "tgw" ? lookup({ + vpc = each.value.create_vpc_attachment ? aws_ec2_transit_gateway_vpc_attachment.this[each.value.key].id : null + peering = each.value.create_tgw_peering ? aws_ec2_transit_gateway_peering_attachment.this[each.value.key].id : null + }, each.value.attachment_type, null) : null + # When transit_gateway_id or transit_gateway_attachment_id is specified, max_aggregation_interval must be 60 seconds (1 minute). + max_aggregation_interval = 60 + + dynamic "destination_options" { + for_each = each.value.dest_type == "s3" ? [true] : [] + + content { + file_format = try(each.value.file_format, "parquet") + hive_compatible_partitions = try(each.value.hive_compatible_partitions, false) + per_hour_partition = try(each.value.per_hour_partition, true) + } + } + + tags = merge(var.tags, var.tgw_flow_log_tags) + + depends_on = [ + aws_ec2_transit_gateway.this, + aws_ec2_transit_gateway_peering_attachment.this, + data.aws_ec2_transit_gateway_vpc_attachment.this, + ] +} diff --git a/variables.tf b/variables.tf index 029c701..ec1bf6d 100644 --- a/variables.tf +++ b/variables.tf @@ -96,13 +96,13 @@ variable "tgw_default_route_table_tags" { # VPC Attachment ################################################################################ -variable "vpc_attachments" { +variable "attachments" { description = "Maps of maps of VPC details to attach to TGW. Type 'any' to disable type validation by Terraform." type = any default = {} } -variable "tgw_vpc_attachment_tags" { +variable "tgw_attachment_tags" { description = "Additional tags for VPC attachments" type = map(string) default = {} @@ -130,6 +130,12 @@ variable "tgw_route_table_tags" { default = {} } +variable "tgw_route_tables" { + description = "Custom TGW route tables to create" + type = set(string) + default = ["custom"] +} + ################################################################################ # Resource Access Manager ################################################################################ @@ -154,7 +160,7 @@ variable "ram_allow_external_principals" { variable "ram_principals" { description = "A list of principals to share TGW with. Possible values are an AWS account ID, an AWS Organizations Organization ARN, or an AWS Organizations Organization Unit ARN" - type = list(string) + type = set(string) default = [] } @@ -169,3 +175,19 @@ variable "ram_tags" { type = map(string) default = {} } + +################################################################################ +# Flow Logs +################################################################################ + +variable "flow_logs" { + description = "Flow Logs to create for Transit Gateway or attachments" + type = any + default = {} +} + +variable "tgw_flow_log_tags" { + description = "Additional tags for TGW or attachment flow logs" + type = map(string) + default = {} +} diff --git a/versions.tf b/versions.tf index 03533eb..ddfcb0e 100644 --- a/versions.tf +++ b/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.1" + required_version = ">= 1.0" required_providers { aws = { source = "hashicorp/aws" - version = ">= 4.4" + version = ">= 5.0" } } }