diff --git a/.header.md b/.header.md index c5ed203..85dc03b 100644 --- a/.header.md +++ b/.header.md @@ -294,31 +294,32 @@ central_vpcs = { ### Spoke VPCs -This variable is used to provide the Hub and Spoke module the neccessary information about the Spoke VPCs created. Note that the module does not create the VPCs, and the information you pass is the VPC IDs, and Transit Gateway VPC attachment IDs. It is recommended the use of the following [AWS VPC Module](https://github.com/aws-ia/terraform-aws-vpc) to simplify your infrastructure creation - also because the Hub and Spoke module makes use of the VPC module to create the Central VPCs. +This variable is used to provide the Hub and Spoke module the neccessary information about the Spoke VPCs created. Note that the module does not create the VPCs, and the information you pass is the routing domains you want to create, and the Spoke VPC information (VPC IDs and Transit Gateway VPC attachment IDs). It is recommended the use of the following [AWS VPC Module](https://github.com/aws-ia/terraform-aws-vpc) to simplify your infrastructure creation - also because the Hub and Spoke module makes use of the VPC module to create the Central VPCs. -Within this variable, a map of routing domains is expected. The *key* of each map will defined that specific routing domain (e.g. prod, nonprod, etc.) and a Transit Gateway Route Table for that routing domain will be created. Inside each routing domain definition, you can define a map of VPCs with the following attributes: +Within this variable, the following attributes are expected: -- `vpc_id` = (Optional|string) VPC ID. *This value is not used in this version of the module, we keep it as placehoder when adding support for centralized VPC endpoints*. -- `transit_gateway_attachment_id` = (Optional|string) Transit Gateway VPC attachment ID. +- `routing_domains` = (Optional|list(string)) Definition of the different routing domains for the Spoke VPCs - for example *prod* or *dev*. If this variable is not provided, all the Spoke VPCs will be associated to a common routing domain (*spokes*). +- `number_vpcs` = (Optional|number) Total number of Spoke VPCs that have been attached to the Transit Gateway, regardless of the routing domain. +- `vpc_information` = (Optional|map(string)) Information about the VPCs to include in the architecture. Inside the variable, a map of the following keys is expected: + - `vpc_id` = (Optional|string) VPC ID. *This value is not used in this version of the module, we keep it as placehoder when adding support for centralized VPC endpoints*. + - `transit_gateway_attachment_id` = (Optional|string) Transit Gateway VPC attachment ID. + - `routing_domain` = (Optional|string) Routing domain to include the VPC (Transit Gateway route table association). This value needs to be included in *var.spoke_vpcs.routing_domains*. ```hcl spoke_vpcs = { - production = { - prod1 = { - vpc_id = vpc-ID1 + routing_domains = ["prod", "nonprod"] + number_vpcs = 2 + vpc_information = { + prod = { + vpc_id = vpc-ID1 transit_gateway_attachment_id = tgw-attach-ID1 + routing_domain = "prod" } - prod2 = { - vpc_id = vpc-ID2 - transit_gateway_attachment_id = tgw-attach-ID2 - } - } - nonproduction = { nonprod = { - vpc_id = vpc-ID - transit_gateway_attachment_id = tgw-attach-ID + vpc_id = vpc-ID2 + transit_gateway_attachment_id = tgw-attach-ID2 + routing_domain = "nonprod" } - } } } ``` @@ -346,17 +347,6 @@ network_definition = { ### Deployment Considerations -#### Terraform Apply - Target - -Due to some limitations with Terraform, some resources need to be created beforehand (using `-target`): - -- Spoke VPCs' Transit Gateway VPC attachment IDs - needed to create the Transit Gateway Route Tables (for each segment), and the Transit Gateway Associations and Propagations. To deploy everything without problems, you can proceed in two ways: - - Do `-target` of the Transit Gateway attachments of your Spoke VPCs, and then proceed to deploy the Hub and Spoke architecture. - - Deploy your Spoke VPCs and Hub and Spoke module without the `spoke_vpcs` variable. Once all the resources are created, add this attribute to the definition and update the Hub and Spoke architecture (as now the TGW attachments are created). -- Managed Prefix List - if building an AWS Network Firewall resource in the Inspection VPC, as the module gets the list of CIDRs from the prefix list to create the routes to the Inspection endpoints. Terraform needs to know this value when created before creating the VPC routes. - -In the *./examples* folder you can find different deployment examples where you can check how you can use `-target` to deploy all the resources without problems. - #### Cross-segment (Spoke VPCs) communication Each Spoke VPC segment created is independent between each other, meaning that inter-segment communication is not allowed. However, if you add an Inspection VPC with the traffic inspection flow as `all` or `east-west`, potentially you can have communication between segments. **You need to block or allow inter-segment communication in the firewall solution deployed**. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cbafa4b..8d53d6b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,6 +6,6 @@ repos: repo: https://github.com/aws-ia/pre-commit-configs # To update run: # pre-commit autoupdate --freeze - rev: 80ed3f0a164f282afaac0b6aec70e20f7e541932 # frozen: v1.5.0 + rev: c7091ec774495a41986bd9c5ea59152655ec4f3a # frozen: v1.6.2 hooks: - id: aws-ia-meta-hook diff --git a/.tflint.hcl b/.tflint.hcl index bd1b0eb..aa2510f 100644 --- a/.tflint.hcl +++ b/.tflint.hcl @@ -3,7 +3,7 @@ plugin "aws" { enabled = true - version = "0.14.0" + version = "0.21.2" source = "github.com/terraform-linters/tflint-ruleset-aws" } diff --git a/README.md b/README.md index 8208c6f..801fa6d 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ central_vpcs = { netmask = 24 nat_gateway_configuration = "all_azs" } - endpoints = { netmask = 24 } + inspection = { netmask = 24 } transit_gateway = { netmask = 28 } } } @@ -101,7 +101,7 @@ central_vpcs = { az_count = 2 subnets = { - endpoints = { netmask = 24 } + inspection = { netmask = 24 } transit_gateway = { netmask = 28 } } } @@ -133,7 +133,7 @@ central_vpcs = { } subnets = { - endpoints = { netmask = 24 } + inspection = { netmask = 24 } transit_gateway = { netmask = 28 } } } @@ -295,31 +295,32 @@ central_vpcs = { ### Spoke VPCs -This variable is used to provide the Hub and Spoke module the neccessary information about the Spoke VPCs created. Note that the module does not create the VPCs, and the information you pass is the VPC IDs, and Transit Gateway VPC attachment IDs. It is recommended the use of the following [AWS VPC Module](https://github.com/aws-ia/terraform-aws-vpc) to simplify your infrastructure creation - also because the Hub and Spoke module makes use of the VPC module to create the Central VPCs. +This variable is used to provide the Hub and Spoke module the neccessary information about the Spoke VPCs created. Note that the module does not create the VPCs, and the information you pass is the routing domains you want to create, and the Spoke VPC information (VPC IDs and Transit Gateway VPC attachment IDs). It is recommended the use of the following [AWS VPC Module](https://github.com/aws-ia/terraform-aws-vpc) to simplify your infrastructure creation - also because the Hub and Spoke module makes use of the VPC module to create the Central VPCs. -Within this variable, a map of routing domains is expected. The *key* of each map will defined that specific routing domain (e.g. prod, nonprod, etc.) and a Transit Gateway Route Table for that routing domain will be created. Inside each routing domain definition, you can define a map of VPCs with the following attributes: +Within this variable, the following attributes are expected: -- `vpc_id` = (Optional|string) VPC ID. *This value is not used in this version of the module, we keep it as placehoder when adding support for centralized VPC endpoints*. -- `transit_gateway_attachment_id` = (Optional|string) Transit Gateway VPC attachment ID. +- `routing_domains` = (Optional|list(string)) Definition of the different routing domains for the Spoke VPCs - for example *prod* or *dev*. If this variable is not provided, all the Spoke VPCs will be associated to a common routing domain (*spokes*). +- `number_vpcs` = (Optional|number) Total number of Spoke VPCs that have been attached to the Transit Gateway, regardless of the routing domain. +- `vpc_information` = (Optional|map(string)) Information about the VPCs to include in the architecture. Inside the variable, a map of the following keys is expected: + - `vpc_id` = (Optional|string) VPC ID. *This value is not used in this version of the module, we keep it as placehoder when adding support for centralized VPC endpoints*. + - `transit_gateway_attachment_id` = (Optional|string) Transit Gateway VPC attachment ID. + - `routing_domain` = (Optional|string) Routing domain to include the VPC (Transit Gateway route table association). This value needs to be included in *var.spoke\_vpcs.routing\_domains*. ```hcl spoke_vpcs = { - production = { - prod1 = { - vpc_id = vpc-ID1 + routing_domains = ["prod", "nonprod"] + number_vpcs = 2 + vpc_information = { + prod = { + vpc_id = vpc-ID1 transit_gateway_attachment_id = tgw-attach-ID1 + routing_domain = "prod" } - prod2 = { - vpc_id = vpc-ID2 - transit_gateway_attachment_id = tgw-attach-ID2 - } - } - nonproduction = { nonprod = { - vpc_id = vpc-ID - transit_gateway_attachment_id = tgw-attach-ID + vpc_id = vpc-ID2 + transit_gateway_attachment_id = tgw-attach-ID2 + routing_domain = "nonprod" } - } } } ``` @@ -347,17 +348,6 @@ network_definition = { ### Deployment Considerations -#### Terraform Apply - Target - -Due to some limitations with Terraform, some resources need to be created beforehand (using `-target`): - -- Spoke VPCs' Transit Gateway VPC attachment IDs - needed to create the Transit Gateway Route Tables (for each segment), and the Transit Gateway Associations and Propagations. To deploy everything without problems, you can proceed in two ways: - - Do `-target` of the Transit Gateway attachments of your Spoke VPCs, and then proceed to deploy the Hub and Spoke architecture. - - Deploy your Spoke VPCs and Hub and Spoke module without the `spoke_vpcs` variable. Once all the resources are created, add this attribute to the definition and update the Hub and Spoke architecture (as now the TGW attachments are created). -- Managed Prefix List - if building an AWS Network Firewall resource in the Inspection VPC, as the module gets the list of CIDRs from the prefix list to create the routes to the Inspection endpoints. Terraform needs to know this value when created before creating the VPC routes. - -In the *./examples* folder you can find different deployment examples where you can check how you can use `-target` to deploy all the resources without problems. - #### Cross-segment (Spoke VPCs) communication Each Spoke VPC segment created is independent between each other, meaning that inter-segment communication is not allowed. However, if you add an Inspection VPC with the traffic inspection flow as `all` or `east-west`, potentially you can have communication between segments. **You need to block or allow inter-segment communication in the firewall solution deployed**. @@ -382,7 +372,6 @@ Each Spoke VPC segment created is independent between each other, meaning that i |------|--------|---------| | [aws\_network\_firewall](#module\_aws\_network\_firewall) | aws-ia/networkfirewall/aws | = 0.0.2 | | [central\_vpcs](#module\_central\_vpcs) | aws-ia/vpc/aws | = 3.0.1 | -| [spoke\_vpcs](#module\_spoke\_vpcs) | ./modules/spoke_vpcs | n/a | ## Resources @@ -398,15 +387,20 @@ Each Spoke VPC segment created is independent between each other, meaning that i | [aws_ec2_transit_gateway_route.spokes_to_egress_default_route](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route) | resource | | [aws_ec2_transit_gateway_route.spokes_to_inspection_default_route](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route) | resource | | [aws_ec2_transit_gateway_route.spokes_to_inspection_network_route](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route) | resource | +| [aws_ec2_transit_gateway_route_table.spokes_tgw_rt](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table) | resource | | [aws_ec2_transit_gateway_route_table.tgw_route_table](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table) | resource | +| [aws_ec2_transit_gateway_route_table_association.spokes_tgw_rt_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_association) | resource | | [aws_ec2_transit_gateway_route_table_association.tgw_route_table_association](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_association) | resource | | [aws_ec2_transit_gateway_route_table_propagation.hybrid_dns_to_spokes_propagation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_propagation) | resource | +| [aws_ec2_transit_gateway_route_table_propagation.ingress_to_inspection_propagation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_propagation) | resource | +| [aws_ec2_transit_gateway_route_table_propagation.ingress_to_spokes_propagation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_propagation) | resource | | [aws_ec2_transit_gateway_route_table_propagation.shared_services_to_spokes_propagation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_propagation) | resource | | [aws_ec2_transit_gateway_route_table_propagation.spokes_to_egress_propagation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_propagation) | resource | | [aws_ec2_transit_gateway_route_table_propagation.spokes_to_hybrid_dns_propagation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_propagation) | resource | | [aws_ec2_transit_gateway_route_table_propagation.spokes_to_ingress_propagation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_propagation) | resource | | [aws_ec2_transit_gateway_route_table_propagation.spokes_to_inspection_propagation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_propagation) | resource | | [aws_ec2_transit_gateway_route_table_propagation.spokes_to_shared_services_propagation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_propagation) | resource | +| [aws_ec2_transit_gateway_route_table_propagation.spokes_to_spokes_propagation](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ec2_transit_gateway_route_table_propagation) | resource | | [aws_ec2_managed_prefix_list.data_network_prefix_list](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/ec2_managed_prefix_list) | data source | ## Inputs @@ -416,7 +410,7 @@ Each Spoke VPC segment created is independent between each other, meaning that i | [central\_vpcs](#input\_central\_vpcs) | Configuration of the Central VPCs - used to centralized different services. You can create the following central VPCs: "inspection", "egress", "shared-services", "hybrid-dns", and "ingress".
In each Central VPC, You can specify the following attributes:
- `vpc_id` = (Optional\|string) **If you specify this value, no other attributes can be set** VPC ID, the VPC will be attached to the Transit Gateway, and its attachment associate/propagated to the corresponding TGW Route Tables.
- `cidr_block` = (Optional\|string) CIDR range to assign to the VPC if creating a new VPC.
- `az_count` = (Optional\|number) Searches the number of AZs in the region and takes a slice based on this number - the slice is sorted a-z.
- `vpc_enable_dns_hostnames` = (Optional\|bool) Indicates whether the instances launched in the VPC get DNS hostnames. Enabled by default.
- `vpc_enable_dns_support` = (Optional\|bool) 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.
- `vpc_instance_tenancy` = (Optional\|string) The allowed tenancy of instances launched into the VPC.
- `vpc_flow_logs` = (Optional\|object(any)) Configuration of the VPC Flow Logs of the VPC configured. Options: "cloudwatch", "s3", "none".
- `subnet_configuration` = (Optional\|any) Configuration of the subnets to create in the VPC. Depending the type of central VPC to create, the format (subnets to configure) will be different.
To get more information of the format of the variables, check the section "Central VPCs" in the README.
 | `any` | n/a | yes |
 |  [identifier](#input\_identifier) | String to identify the whole Hub and Spoke environment. | `string` | n/a | yes |
 |  [network\_definition](#input\_network\_definition) | "Definition of the IPv4 CIDR configuration. The definition is done by using two variables:"
- `type` = (string) Defines the type of network definition provided. It has to be either `CIDR` (Supernet's CIDR Block) or `PREFIX_LIST` (prefix list ID containing all the CIDR blocks of the network)
- `value` = (string) Either a Supernet's CIDR Block or a prefix list ID. This value needs to be consistent with the `type` provided in this variable.
 | 
object({
type = string
value = string
})
| n/a | yes | -| [spoke\_vpcs](#input\_spoke\_vpcs) | Variable is used to provide the Hub and Spoke module the neccessary information about the Spoke VPCs created. Within this variable, a map of routing domains is expected. The *key* of each map will defined that specific routing domain (e.g. prod, nonprod, etc.) and a Transit Gateway Route Table for that routing domain will be created. Inside each routing domain definition, you can define a map of VPCs with the following attributes:
- `vpc_id` = (Optional\|string) VPC ID. *This value is not used in this version of the module, we keep it as placehoder when adding support for centralized VPC endpoints*.
- `transit_gateway_attachment_id` = (Optional\|string) Transit Gateway VPC attachment ID.
To get more information of the format of the variables, check the section "Spoke VPCs" in the README.
 | `any` | `{}` | no |
+|  [spoke\_vpcs](#input\_spoke\_vpcs) | Variable used to provide the information about the Spoke VPCs to include in the hub and spoke architecture. Information to provide is the following one:
- `routing_domains` = (Optional\|list(string)) Definition of the different routing domains for the Spoke VPCs - for example *prod* or *dev*. If this variable is not provided, all the Spoke VPCs will be associated to a common routing domain (*spokes*).
- `number_vpcs` = (Optional\|number) Total number of Spoke VPCs that have been attached to the Transit Gateway, regardless of the routing domain.
- `vpc_information` = (Optional\|map(string)) Information about the VPCs to include in the architecture. Inside the variable, a map of the following keys is expected:
- `vpc_id` = (Optional\|string) VPC ID. *This value is not used in this version of the module, we keep it as placehoder when adding support for centralized VPC endpoints*.
- `transit_gateway_attachment_id` = (Optional\|string) Transit Gateway VPC attachment ID.
- `routing_domain` = (Optional\|string) Routing domain to include the VPC (Transit Gateway route table association). This value needs to be included in *var.spoke\_vpcs.routing\_domains*.
To get more information of the format of the variables, check the section "Spoke VPCs" in the README.
 | `any` | `{}` | no |
 |  [transit\_gateway\_attributes](#input\_transit\_gateway\_attributes) | Attributes about the new Transit Gateway to create. **If you specify this value, transit\_gateway\_id can't be set**:
- `name` = (Optional\|string) Name to apply to the new Transit Gateway.
- `description` = (Optional\|string) Description of the Transit Gateway
- `amazon_side_asn` = (Optional\|number) Private Autonomous System Number (ASN) for the Amazon side of a BGP session. The range is `64512` to `65534` for 16-bit ASNs and `4200000000` to `4294967294` for 32-bit ASNs. It is recommended to configure one to avoid ASN overlap. Default value: `64512`.
- `auto_accept_shared_attachments` = (Optional\|string) Wheter the attachment requests are automatically accepted. Valid values: `disable` (default) or `enable`.
- `dns_support` = (Optional\|string) Wheter DNS support is enabled. Valid values: `disable` or `enable` (default).
- `multicast_support` = (Optional\|string) Wheter Multicas support is enabled. Valid values: `disable` (default) or `enable`.
- `transit_gateway_cidr_blocks` = (Optional\|list(string)) One or more IPv4/IPv6 CIDR blocks for the Transit Gateway. Must be a size /24 for IPv4 CIDRs, and /64 for IPv6 CIDRs.
- `vpn_ecmp_support` = (Optional\|string) Whever VPN ECMP support is enabled. Valid values: `disable` or `enable` (default).
- `tags` = (Optional\|map(string)) Key-value tags to apply to the Transit Gateway.
 | `any` | `{}` | no |
 |  [transit\_gateway\_id](#input\_transit\_gateway\_id) | Transit Gateway ID. **If you specify this value, transit\_gateway\_attributes can't be set**. | `string` | `null` | no |
 
diff --git a/examples/central_egress_ingress/README.md b/examples/central_egress_ingress/README.md
index 9482ce4..0e05e9d 100644
--- a/examples/central_egress_ingress/README.md
+++ b/examples/central_egress_ingress/README.md
@@ -20,7 +20,7 @@ This example builds a central Ingress and Egress VPCs. The following resources a
 
 | Name | Version |
 |------|---------|
-|  [terraform](#requirement\_terraform) | >= 0.15.0 |
+|  [terraform](#requirement\_terraform) | >= 1.3.0 |
 |  [aws](#requirement\_aws) | >= 3.73.0 |
 |  [awscc](#requirement\_awscc) | >= 0.15.0 |
 
@@ -28,13 +28,14 @@ This example builds a central Ingress and Egress VPCs. The following resources a
 
 | Name | Version |
 |------|---------|
-|  [aws](#provider\_aws) | 4.33.0 |
+|  [aws](#provider\_aws) | >= 3.73.0 |
 
 ## Modules
 
 | Name | Source | Version |
 |------|--------|---------|
-|  [hub-and-spoke](#module\_hub-and-spoke) | ../.. | n/a |
+|  [hub-and-spoke](#module\_hub-and-spoke) | aws-ia/network-hubandspoke | 2.0.0 |
+|  [spoke\_vpcs](#module\_spoke\_vpcs) | aws-ia/vpc/aws | 3.1.0 |
 
 ## Resources
 
@@ -49,11 +50,13 @@ This example builds a central Ingress and Egress VPCs. The following resources a
 |------|-------------|------|---------|:--------:|
 |  [aws\_region](#input\_aws\_region) | AWS Region - to build the Hub and Spoke. | `string` | `"eu-west-1"` | no |
 |  [identifier](#input\_identifier) | Project identifier. | `string` | `"central-egress-ingress"` | no |
+|  [spoke\_vpcs](#input\_spoke\_vpcs) | Spoke VPCs. | `map(any)` | 
{
"vpc1": {
"cidr_block": "10.0.0.0/24",
"number_azs": 2,
"routing_domain": "prod"
},
"vpc2": {
"cidr_block": "10.0.1.0/24",
"number_azs": 2,
"routing_domain": "prod"
}
}
| no | ## Outputs | Name | Description | |------|-------------| | [central\_vpcs](#output\_central\_vpcs) | Central VPCs created. | +| [spoke\_vpcs](#output\_spoke\_vpcs) | Spoke VPCs created. | | [transit\_gateway\_id](#output\_transit\_gateway\_id) | ID of the AWS Transit Gateway resource. | \ No newline at end of file diff --git a/examples/central_egress_ingress/main.tf b/examples/central_egress_ingress/main.tf index c6502cd..3b02a69 100644 --- a/examples/central_egress_ingress/main.tf +++ b/examples/central_egress_ingress/main.tf @@ -19,7 +19,7 @@ resource "aws_ec2_transit_gateway" "tgw" { # Hub and Spoke module - we only centralize the Egress and Ingress traffic module "hub-and-spoke" { source = "aws-ia/network-hubandspoke" - version = "1.0.1" + version = "2.0.0" identifier = var.identifier transit_gateway_id = aws_ec2_transit_gateway.tgw.id @@ -52,6 +52,16 @@ module "hub-and-spoke" { } } } + + spoke_vpcs = { + number_vpcs = length(var.spoke_vpcs) + routing_domains = ["prod"] + vpc_information = { for k, v in module.spoke_vpcs : k => { + vpc_id = v.vpc_attributes.id + transit_gateway_attachment_id = v.transit_gateway_attachment_id + routing_domain = var.spoke_vpcs[k].routing_domain + } } + } } # Managed prefix list (to pass to the Hub and Spoke module) @@ -59,4 +69,29 @@ resource "aws_ec2_managed_prefix_list" "network_prefix_list" { name = "Network's Prefix List" address_family = "IPv4" max_entries = 2 +} + +# Spoke VPCs +module "spoke_vpcs" { + for_each = var.spoke_vpcs + source = "aws-ia/vpc/aws" + version = "3.1.0" + + name = each.key + cidr_block = each.value.cidr_block + az_count = each.value.number_azs + + transit_gateway_id = aws_ec2_transit_gateway.tgw.id + transit_gateway_routes = { + workloads = "0.0.0.0/0" + } + + subnets = { + workload = { netmask = 28 } + transit_gateway = { + netmask = 28 + transit_gateway_default_route_table_association = false + transit_gateway_default_route_table_propagation = false + } + } } \ No newline at end of file diff --git a/examples/central_egress_ingress/outputs.tf b/examples/central_egress_ingress/outputs.tf index 62a5bb9..74bc5d2 100644 --- a/examples/central_egress_ingress/outputs.tf +++ b/examples/central_egress_ingress/outputs.tf @@ -11,4 +11,9 @@ output "transit_gateway_id" { output "central_vpcs" { description = "Central VPCs created." value = { for k, v in module.hub-and-spoke.central_vpcs : k => v.vpc_attributes.id } +} + +output "spoke_vpcs" { + description = "Spoke VPCs created." + value = { for k, v in module.spoke_vpcs : k => v.vpc_attributes.id } } \ No newline at end of file diff --git a/examples/central_egress_ingress/variables.tf b/examples/central_egress_ingress/variables.tf index f2272ba..5b5a614 100644 --- a/examples/central_egress_ingress/variables.tf +++ b/examples/central_egress_ingress/variables.tf @@ -13,4 +13,21 @@ variable "identifier" { type = string description = "Project identifier." default = "central-egress-ingress" +} + +variable "spoke_vpcs" { + type = map(any) + description = "Spoke VPCs." + default = { + "vpc1" = { + cidr_block = "10.0.0.0/24" + number_azs = 2 + routing_domain = "prod" + } + "vpc2" = { + cidr_block = "10.0.1.0/24" + number_azs = 2 + routing_domain = "prod" + } + } } \ No newline at end of file diff --git a/examples/central_inspection/README.md b/examples/central_inspection/README.md index 6af54e2..6af83d7 100644 --- a/examples/central_inspection/README.md +++ b/examples/central_inspection/README.md @@ -21,7 +21,7 @@ This example centralizes the traffic inspection and egress traffic within the sa | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.15.0 | +| [terraform](#requirement\_terraform) | >= 1.3.0 | | [aws](#requirement\_aws) | >= 3.73.0 | | [awscc](#requirement\_awscc) | >= 0.15.0 | @@ -29,13 +29,14 @@ This example centralizes the traffic inspection and egress traffic within the sa | Name | Version | |------|---------| -| [aws](#provider\_aws) | 4.31.0 | +| [aws](#provider\_aws) | >= 3.73.0 | ## Modules | Name | Source | Version | |------|--------|---------| -| [hub-and-spoke](#module\_hub-and-spoke) | ../.. | n/a | +| [hub-and-spoke](#module\_hub-and-spoke) | aws-ia/network-hubandspoke | 2.0.0 | +| [spoke\_vpcs](#module\_spoke\_vpcs) | aws-ia/vpc/aws | 3.1.0 | ## Resources @@ -51,6 +52,7 @@ This example centralizes the traffic inspection and egress traffic within the sa |------|-------------|------|---------|:--------:| | [aws\_region](#input\_aws\_region) | AWS Region - to build the Hub and Spoke. | `string` | `"eu-west-1"` | no | | [identifier](#input\_identifier) | Project identifier. | `string` | `"central-inspection"` | no | +| [spoke\_vpcs](#input\_spoke\_vpcs) | Spoke VPCs. | `map(any)` |
{
"nonprod-vpc": {
"cidr_block": "10.0.1.0/24",
"number_azs": 2,
"routing_domain": "nonprod"
},
"prod-vpc": {
"cidr_block": "10.0.0.0/24",
"number_azs": 2,
"routing_domain": "prod"
}
}
| no | ## Outputs @@ -58,5 +60,6 @@ This example centralizes the traffic inspection and egress traffic within the sa |------|-------------| | [central\_vpcs](#output\_central\_vpcs) | Central VPCs created. | | [network\_firewall](#output\_network\_firewall) | AWS Network Firewall ID. | +| [spoke\_vpcs](#output\_spoke\_vpcs) | Spoke VPCs created. | | [transit\_gateway\_id](#output\_transit\_gateway\_id) | ID of the AWS Transit Gateway resource. | \ No newline at end of file diff --git a/examples/central_inspection/main.tf b/examples/central_inspection/main.tf index f450f9c..68bb4b5 100644 --- a/examples/central_inspection/main.tf +++ b/examples/central_inspection/main.tf @@ -6,7 +6,7 @@ # Hub and Spoke module - we only centralize the Inspection module "hub-and-spoke" { source = "aws-ia/network-hubandspoke" - version = "1.0.1" + version = "2.0.0" identifier = var.identifier transit_gateway_attributes = { @@ -39,4 +39,39 @@ module "hub-and-spoke" { } } } + + spoke_vpcs = { + number_vpcs = length(var.spoke_vpcs) + routing_domains = ["prod", "nonprod"] + vpc_information = { for k, v in module.spoke_vpcs : k => { + vpc_id = v.vpc_attributes.id + transit_gateway_attachment_id = v.transit_gateway_attachment_id + routing_domain = var.spoke_vpcs[k].routing_domain + } } + } +} + +# Spoke VPCs +module "spoke_vpcs" { + for_each = var.spoke_vpcs + source = "aws-ia/vpc/aws" + version = "3.1.0" + + name = each.key + cidr_block = each.value.cidr_block + az_count = each.value.number_azs + + transit_gateway_id = module.hub-and-spoke.transit_gateway.id + transit_gateway_routes = { + workloads = "0.0.0.0/0" + } + + subnets = { + workload = { netmask = 28 } + transit_gateway = { + netmask = 28 + transit_gateway_default_route_table_association = false + transit_gateway_default_route_table_propagation = false + } + } } \ No newline at end of file diff --git a/examples/central_inspection/outputs.tf b/examples/central_inspection/outputs.tf index 7a70c04..10eaf79 100644 --- a/examples/central_inspection/outputs.tf +++ b/examples/central_inspection/outputs.tf @@ -13,6 +13,11 @@ output "central_vpcs" { value = { for k, v in module.hub-and-spoke.central_vpcs : k => v.vpc_attributes.id } } +output "spoke_vpcs" { + description = "Spoke VPCs created." + value = { for k, v in module.spoke_vpcs : k => v.vpc_attributes.id } +} + output "network_firewall" { description = "AWS Network Firewall ID." value = module.hub-and-spoke.aws_network_firewall.id diff --git a/examples/central_inspection/variables.tf b/examples/central_inspection/variables.tf index fc1a63b..80a13d3 100644 --- a/examples/central_inspection/variables.tf +++ b/examples/central_inspection/variables.tf @@ -13,4 +13,21 @@ variable "identifier" { type = string description = "Project identifier." default = "central-inspection" +} + +variable "spoke_vpcs" { + type = map(any) + description = "Spoke VPCs." + default = { + "prod-vpc" = { + routing_domain = "prod" + cidr_block = "10.0.0.0/24" + number_azs = 2 + } + "nonprod-vpc" = { + routing_domain = "nonprod" + cidr_block = "10.0.1.0/24" + number_azs = 2 + } + } } \ No newline at end of file diff --git a/examples/central_shared_services/README.md b/examples/central_shared_services/README.md index e686392..44ade6c 100644 --- a/examples/central_shared_services/README.md +++ b/examples/central_shared_services/README.md @@ -19,7 +19,7 @@ This example centralizes VPC endpoints with a central Shared Services VPC. The f | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.15.0 | +| [terraform](#requirement\_terraform) | >= 1.3.0 | | [aws](#requirement\_aws) | >= 3.73.0 | | [awscc](#requirement\_awscc) | >= 0.15.0 | @@ -27,13 +27,14 @@ This example centralizes VPC endpoints with a central Shared Services VPC. The f | Name | Version | |------|---------| -| [aws](#provider\_aws) | 4.32.0 | +| [aws](#provider\_aws) | >= 3.73.0 | ## Modules | Name | Source | Version | |------|--------|---------| | [hub-and-spoke](#module\_hub-and-spoke) | ../.. | n/a | +| [spoke\_vpcs](#module\_spoke\_vpcs) | aws-ia/vpc/aws | 3.1.0 | ## Resources @@ -47,12 +48,13 @@ This example centralizes VPC endpoints with a central Shared Services VPC. The f |------|-------------|------|---------|:--------:| | [aws\_region](#input\_aws\_region) | AWS Region - to build the Hub and Spoke. | `string` | `"eu-west-1"` | no | | [identifier](#input\_identifier) | Project identifier. | `string` | `"central-shared-services"` | no | -| [spoke\_vpcs](#input\_spoke\_vpcs) | Spoke VPCs definition. | `any` |
{
"dev": {
"az_count": 2,
"cidr_block": "10.1.0.0/24",
"instance_type": "t2.micro",
"private_subnet_netmask": 28,
"tgw_subnet_netmask": 28,
"type": "development"
},
"prod": {
"az_count": 2,
"cidr_block": "10.0.0.0/24",
"instance_type": "t2.micro",
"private_subnet_netmask": 28,
"tgw_subnet_netmask": 28,
"type": "production"
},
"test": {
"az_count": 2,
"cidr_block": "10.2.0.0/24",
"instance_type": "t2.micro",
"private_subnet_netmask": 28,
"tgw_subnet_netmask": 28,
"type": "testing"
}
}
| no | +| [spoke\_vpcs](#input\_spoke\_vpcs) | Spoke VPCs. | `map(any)` |
{
"vpc1": {
"cidr_block": "10.0.0.0/24",
"number_azs": 2
},
"vpc2": {
"cidr_block": "10.0.1.0/24",
"number_azs": 2
}
}
| no | ## Outputs | Name | Description | |------|-------------| | [central\_vpcs](#output\_central\_vpcs) | Central VPCs created. | +| [spoke\_vpcs](#output\_spoke\_vpcs) | Spoke VPCs created. | | [transit\_gateway\_id](#output\_transit\_gateway\_id) | ID of the AWS Transit Gateway resource. | \ No newline at end of file diff --git a/examples/central_shared_services/main.tf b/examples/central_shared_services/main.tf index a28c755..2368ab0 100644 --- a/examples/central_shared_services/main.tf +++ b/examples/central_shared_services/main.tf @@ -18,8 +18,9 @@ resource "aws_ec2_transit_gateway" "tgw" { # Hub and Spoke module - we only centralize the Shared Services and Hybrid DNS VPCs module "hub-and-spoke" { - source = "aws-ia/network-hubandspoke" - version = "1.0.1" + source = "../.." + #source = "aws-ia/network-hubandspoke" + #version = "2.0.0" identifier = var.identifier transit_gateway_id = aws_ec2_transit_gateway.tgw.id @@ -41,4 +42,37 @@ module "hub-and-spoke" { } } } + + spoke_vpcs = { + number_vpcs = length(var.spoke_vpcs) + vpc_information = { for k, v in module.spoke_vpcs : k => { + vpc_id = v.vpc_attributes.id + transit_gateway_attachment_id = v.transit_gateway_attachment_id + } } + } +} + +# Spoke VPCs +module "spoke_vpcs" { + for_each = var.spoke_vpcs + source = "aws-ia/vpc/aws" + version = "3.1.0" + + name = each.key + cidr_block = each.value.cidr_block + az_count = each.value.number_azs + + transit_gateway_id = aws_ec2_transit_gateway.tgw.id + transit_gateway_routes = { + workloads = "0.0.0.0/0" + } + + subnets = { + workload = { netmask = 28 } + transit_gateway = { + netmask = 28 + transit_gateway_default_route_table_association = false + transit_gateway_default_route_table_propagation = false + } + } } \ No newline at end of file diff --git a/examples/central_shared_services/outputs.tf b/examples/central_shared_services/outputs.tf index 8f5ffb5..baee5ea 100644 --- a/examples/central_shared_services/outputs.tf +++ b/examples/central_shared_services/outputs.tf @@ -11,4 +11,9 @@ output "transit_gateway_id" { output "central_vpcs" { description = "Central VPCs created." value = { for k, v in module.hub-and-spoke.central_vpcs : k => v.vpc_attributes.id } +} + +output "spoke_vpcs" { + description = "Spoke VPCs created." + value = { for k, v in module.spoke_vpcs : k => v.vpc_attributes.id } } \ No newline at end of file diff --git a/examples/central_shared_services/variables.tf b/examples/central_shared_services/variables.tf index d4e2aa5..5529fe1 100644 --- a/examples/central_shared_services/variables.tf +++ b/examples/central_shared_services/variables.tf @@ -13,4 +13,19 @@ variable "identifier" { type = string description = "Project identifier." default = "central-shared-services" +} + +variable "spoke_vpcs" { + type = map(any) + description = "Spoke VPCs." + default = { + "vpc1" = { + cidr_block = "10.0.0.0/24" + number_azs = 2 + } + "vpc2" = { + cidr_block = "10.0.1.0/24" + number_azs = 2 + } + } } \ No newline at end of file diff --git a/locals.tf b/locals.tf index 3b4fa91..9f4c921 100644 --- a/locals.tf +++ b/locals.tf @@ -74,8 +74,11 @@ locals { } # ---------- SPOKE VPC LOCAL VARIABLES ---------- - # Boolean to indicate if any Spoke VPC Information has been provided - spoke_vpc_information = var.spoke_vpcs != null ? true : false + # Default values for var.spoke_vpcs + number_vpcs = try(var.spoke_vpcs.number_vpcs, 0) + routing_domains = local.number_vpcs > 0 ? try(var.spoke_vpcs.routing_domains, ["spokes"]) : [] + # List of the VPC Information (from map) + vpc_information = values(try(var.spoke_vpcs.vpc_information, [])) # Boolean to indicate if the network's route definition is done with a managed prefix list (for the Transit Gateway Route Tables) network_pl = var.network_definition.type == "PREFIX_LIST" ? true : false @@ -104,13 +107,6 @@ locals { # Spoke VPCs Propagate to Spoke TGW RT spoke_to_spoke_propagation = !contains(keys(var.central_vpcs), "inspection") || (contains(keys(var.central_vpcs), "inspection") && local.inspection_flow == "north-south") - # Map with all the Spoke VPCs (independently of the segment) - transit_gateway_attachment_ids = merge([ - for k, vpc in try(var.spoke_vpcs, {}) : { - for name, info in vpc : name => info.transit_gateway_attachment_id - } - ]...) - # ---------- CENTRAL VPC LOCAL VARIABLES ---------- # Inspection / Shared Services VPC configuration inspection_configuration = contains(keys(try(var.central_vpcs.inspection.subnets, {})), "public") ? "with_internet" : "without_internet" diff --git a/main.tf b/main.tf index d5556e7..432f9bf 100644 --- a/main.tf +++ b/main.tf @@ -68,18 +68,32 @@ resource "aws_ec2_transit_gateway_route_table_association" "tgw_route_table_asso transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.tgw_route_table[each.key].id } -# --------- TRANSIT GATEWAY ROUTE TABLE, ASSOCATIONS, AND PROPAGATIONS (IF APPLIES) - SPOKE VPCS --------- -module "spoke_vpcs" { - for_each = { for k, v in var.spoke_vpcs : k => v if local.spoke_vpc_information } - source = "./modules/spoke_vpcs" +# --------- TRANSIT GATEWAY ROUTE TABLE, ASSOCIATIONS, AND PROPAGATIONS (IF APPLIES) - SPOKE VPCS --------- +# Transit Gateway Route Tables (Routing Domains) +resource "aws_ec2_transit_gateway_route_table" "spokes_tgw_rt" { + for_each = toset(local.routing_domains) - identifier = var.identifier transit_gateway_id = local.transit_gateway_id - segment_name = each.key - segment_information = each.value + tags = { + Name = "${each.key}-tgw-rt-${var.identifier}" + } +} + +# Spoke VPC TGW association +resource "aws_ec2_transit_gateway_route_table_association" "spokes_tgw_rt_association" { + count = local.number_vpcs - tgw_attachment_propagation = local.spoke_to_spoke_propagation + transit_gateway_attachment_id = local.vpc_information[count.index].transit_gateway_attachment_id + transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.spokes_tgw_rt[try(local.vpc_information[count.index].routing_domain, "spokes")].id +} + +# Spoke VPC TGW propagation +resource "aws_ec2_transit_gateway_route_table_propagation" "spokes_to_spokes_propagation" { + count = local.spoke_to_spoke_propagation ? local.number_vpcs : 0 + + transit_gateway_attachment_id = local.vpc_information[count.index].transit_gateway_attachment_id + transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.spokes_tgw_rt[try(local.vpc_information[count.index].routing_domain, "spokes")].id } # ---------------------- TRANSIT GATEWAY STATIC ROUTES ---------------------- @@ -88,13 +102,13 @@ module "spoke_vpcs" { # 2/ Both Inspection VPC and Egress VPC are created, and the traffic inspection is "all" or "north-south". resource "aws_ec2_transit_gateway_route" "spokes_to_inspection_default_route" { for_each = { - for k, v in module.spoke_vpcs : k => v.transit_gateway_spoke_rt + for k, v in aws_ec2_transit_gateway_route_table.spokes_tgw_rt : k => v.id if local.spoke_to_inspection_default } destination_cidr_block = "0.0.0.0/0" transit_gateway_attachment_id = module.central_vpcs["inspection"].transit_gateway_attachment_id - transit_gateway_route_table_id = each.value.id + transit_gateway_route_table_id = each.value } # Static Route (0.0.0.0/0) from Spoke VPCs to Egress VPC if: @@ -102,37 +116,37 @@ resource "aws_ec2_transit_gateway_route" "spokes_to_inspection_default_route" { # 2/ Both Inspection VPC and Egress VPC are created, and the traffic inspection is "east-west". resource "aws_ec2_transit_gateway_route" "spokes_to_egress_default_route" { for_each = { - for k, v in module.spoke_vpcs : k => v.transit_gateway_spoke_rt + for k, v in aws_ec2_transit_gateway_route_table.spokes_tgw_rt : k => v.id if local.spoke_to_egress_default } destination_cidr_block = "0.0.0.0/0" transit_gateway_attachment_id = module.central_vpcs["egress"].transit_gateway_attachment_id - transit_gateway_route_table_id = each.value.id + transit_gateway_route_table_id = each.value } # Static Route (Network's CIDR) from Spoke VPCs to Inspection VPC if: # 1/ Both Inspection VPC and Egress VPC are created, and the traffic inspection is "east-west". resource "aws_ec2_transit_gateway_route" "spokes_to_inspection_network_route" { for_each = { - for k, v in module.spoke_vpcs : k => v.transit_gateway_spoke_rt + for k, v in aws_ec2_transit_gateway_route_table.spokes_tgw_rt : k => v.id if local.spoke_to_inspection_network && !local.network_pl } destination_cidr_block = var.network_definition.value transit_gateway_attachment_id = module.central_vpcs["inspection"].transit_gateway_attachment_id - transit_gateway_route_table_id = each.value.id + transit_gateway_route_table_id = each.value } resource "aws_ec2_transit_gateway_prefix_list_reference" "spokes_to_inspection_network_prefix_list" { for_each = { - for k, v in module.spoke_vpcs : k => v.transit_gateway_spoke_rt + for k, v in aws_ec2_transit_gateway_route_table.spokes_tgw_rt : k => v.id if local.spoke_to_inspection_network && local.network_pl } prefix_list_id = var.network_definition.value transit_gateway_attachment_id = module.central_vpcs["inspection"].transit_gateway_attachment_id - transit_gateway_route_table_id = each.value.id + transit_gateway_route_table_id = each.value } # Static Route (0.0.0.0/0) from Inspection VPC to Egress VPC if: @@ -182,14 +196,19 @@ resource "aws_ec2_transit_gateway_prefix_list_reference" "ingress_to_inspection_ } # -------------------- TRANSIT GATEWAY PROPAGATED ROUTES -------------------- +# Ingress VPC propagates to the Inspection VPC if both Ingress and Inspection are created, and traffic inspection is "all" or "north-south" +resource "aws_ec2_transit_gateway_route_table_propagation" "ingress_to_inspection_propagation" { + count = local.ingress_to_inspection_network ? 1 : 0 + + transit_gateway_attachment_id = module.central_vpcs["ingress"].transit_gateway_attachment_id + transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.tgw_route_table["inspection"].id +} + # Spoke VPCs propagation to the Inspection RT - anytime this VPC is created resource "aws_ec2_transit_gateway_route_table_propagation" "spokes_to_inspection_propagation" { - for_each = { - for k, v in local.transit_gateway_attachment_ids : k => v - if local.spoke_to_inspection_propagation - } + count = local.spoke_to_inspection_propagation ? local.number_vpcs : 0 - transit_gateway_attachment_id = each.value + transit_gateway_attachment_id = local.vpc_information[count.index].transit_gateway_attachment_id transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.tgw_route_table["inspection"].id } @@ -197,12 +216,9 @@ resource "aws_ec2_transit_gateway_route_table_propagation" "spokes_to_inspection # 1/ The Egress VPC is created without Inspection VPC or, # 2/ Both Egress and Inspection VPC are created, and the traffic inspeciton is "all" or "east-west" resource "aws_ec2_transit_gateway_route_table_propagation" "spokes_to_egress_propagation" { - for_each = { - for k, v in local.transit_gateway_attachment_ids : k => v - if local.spoke_to_egress_propagation - } + count = local.spoke_to_egress_propagation ? local.number_vpcs : 0 - transit_gateway_attachment_id = each.value + transit_gateway_attachment_id = local.vpc_information[count.index].transit_gateway_attachment_id transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.tgw_route_table["egress"].id } @@ -210,41 +226,45 @@ resource "aws_ec2_transit_gateway_route_table_propagation" "spokes_to_egress_pro # 1/ The Ingress VPC is created without Inspection VPC or, # 2/ Both Egress and Inspection VPC are created, and the traffic inspeciton is "east-west" resource "aws_ec2_transit_gateway_route_table_propagation" "spokes_to_ingress_propagation" { + count = local.spoke_to_ingress_propagation ? local.number_vpcs : 0 + + transit_gateway_attachment_id = local.vpc_information[count.index].transit_gateway_attachment_id + transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.tgw_route_table["ingress"].id +} + +# Ingress VPC propagates to Spoke VPCs if: +# 1/ The Ingress VPC is created without Inspection VPC or, +# 2/ Both Egress and Inspection VPC are created, and the traffic inspection is "east-west" +resource "aws_ec2_transit_gateway_route_table_propagation" "ingress_to_spokes_propagation" { for_each = { - for k, v in local.transit_gateway_attachment_ids : k => v + for k, v in aws_ec2_transit_gateway_route_table.spokes_tgw_rt : k => v.id if local.spoke_to_ingress_propagation } - transit_gateway_attachment_id = each.value - transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.tgw_route_table["ingress"].id + transit_gateway_attachment_id = module.central_vpcs["ingress"].transit_gateway_attachment_id + transit_gateway_route_table_id = each.value } # Spoke VPCs propagation to the Shared Services RT - anytime this VPC is created resource "aws_ec2_transit_gateway_route_table_propagation" "spokes_to_shared_services_propagation" { - for_each = { - for k, v in local.transit_gateway_attachment_ids : k => v - if contains(keys(var.central_vpcs), "shared_services") - } + count = contains(keys(var.central_vpcs), "shared_services") ? local.number_vpcs : 0 - transit_gateway_attachment_id = each.value + transit_gateway_attachment_id = local.vpc_information[count.index].transit_gateway_attachment_id transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.tgw_route_table["shared_services"].id } # Spoke VPCs propagation to the Hybrid DNS RT - anytime this VPC is created resource "aws_ec2_transit_gateway_route_table_propagation" "spokes_to_hybrid_dns_propagation" { - for_each = { - for k, v in local.transit_gateway_attachment_ids : k => v - if contains(keys(var.central_vpcs), "hybrid_dns") - } + count = contains(keys(var.central_vpcs), "hybrid_dns") ? local.number_vpcs : 0 - transit_gateway_attachment_id = each.value + transit_gateway_attachment_id = local.vpc_information[count.index].transit_gateway_attachment_id transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.tgw_route_table["hybrid_dns"].id } # If Shared Services VPC is created, it propagates its CIDR to all the Segment TGW Route Tables resource "aws_ec2_transit_gateway_route_table_propagation" "shared_services_to_spokes_propagation" { for_each = { - for k, v in module.spoke_vpcs : k => v.transit_gateway_spoke_rt.id + for k, v in aws_ec2_transit_gateway_route_table.spokes_tgw_rt : k => v.id if contains(keys(var.central_vpcs), "shared_services") } @@ -255,7 +275,7 @@ resource "aws_ec2_transit_gateway_route_table_propagation" "shared_services_to_s # If Hybrid DNS VPC is created, it propagates its CIDR to the Segment TGW Route Tables resource "aws_ec2_transit_gateway_route_table_propagation" "hybrid_dns_to_spokes_propagation" { for_each = { - for k, v in module.spoke_vpcs : k => v.transit_gateway_spoke_rt.id + for k, v in aws_ec2_transit_gateway_route_table.spokes_tgw_rt : k => v.id if contains(keys(var.central_vpcs), "hybrid_dns") } diff --git a/modules/spoke_vpcs/main.tf b/modules/spoke_vpcs/main.tf deleted file mode 100644 index 364f2eb..0000000 --- a/modules/spoke_vpcs/main.tf +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: MIT-0 - -# --- modules/spoke_vpcs/main.tf --- - -# Segment's Route Table -resource "aws_ec2_transit_gateway_route_table" "spokes_tgw_rt" { - transit_gateway_id = var.transit_gateway_id - - tags = { - Name = "${var.segment_name}-spokes-tgw-rt-${var.identifier}" - } -} - -# Spoke VPC association -resource "aws_ec2_transit_gateway_route_table_association" "spokes_tgw_rt_association" { - for_each = { for k, v in var.segment_information : k => v.transit_gateway_attachment_id } - - transit_gateway_attachment_id = each.value - transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.spokes_tgw_rt.id -} - -# Spoke VPC propagation (if Spoke VPCs should propagate in its own Segment Route Table) -resource "aws_ec2_transit_gateway_route_table_propagation" "spokes_to_spokes_propagation" { - for_each = { - for k, v in var.segment_information : k => v.transit_gateway_attachment_id - if var.tgw_attachment_propagation - } - - transit_gateway_attachment_id = each.value - transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.spokes_tgw_rt.id -} \ No newline at end of file diff --git a/modules/spoke_vpcs/outputs.tf b/modules/spoke_vpcs/outputs.tf deleted file mode 100644 index 97c1107..0000000 --- a/modules/spoke_vpcs/outputs.tf +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: MIT-0 - -# --- modules/spoke_vpcs/outputs.tf --- - -output "transit_gateway_spoke_rt" { - description = "Segment's Spoke Transit Gateway Route Table." - value = aws_ec2_transit_gateway_route_table.spokes_tgw_rt -} - diff --git a/modules/spoke_vpcs/providers.tf b/modules/spoke_vpcs/providers.tf deleted file mode 100644 index d9edfeb..0000000 --- a/modules/spoke_vpcs/providers.tf +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: MIT-0 - -# --- modules/spoke_vpcs/providers.tf --- - -terraform { - required_providers { - aws = { - source = "hashicorp/aws" - version = ">= 3.73.0" - } - awscc = { - source = "hashicorp/awscc" - version = ">= 0.15.0" - } - } - - required_version = ">= 0.15.0" -} \ No newline at end of file diff --git a/modules/spoke_vpcs/variables.tf b/modules/spoke_vpcs/variables.tf deleted file mode 100644 index 9f17215..0000000 --- a/modules/spoke_vpcs/variables.tf +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -# SPDX-License-Identifier: MIT-0 - -# --- modules/spoke_vpcs/variables.tf --- - -variable "identifier" { - type = string - description = "Project identifier." -} - -variable "transit_gateway_id" { - type = string - description = "AWS Transit Gateway ID." -} - -variable "segment_name" { - type = string - description = "Segment name." -} - -variable "segment_information" { - type = any - description = "Information about the segment to create (CIDR block/prefix list, and Spoke VPCs information)." -} - -variable "tgw_attachment_propagation" { - type = bool - description = "Boolean indicating if the Spoke VPC attachments should propagate in the Spoke TGW RT." -} \ No newline at end of file diff --git a/outputs.tf b/outputs.tf index fa48e6c..c41a949 100644 --- a/outputs.tf +++ b/outputs.tf @@ -45,7 +45,7 @@ output "transit_gateway_route_tables" { EOF value = { central_vpcs = aws_ec2_transit_gateway_route_table.tgw_route_table - spoke_vpcs = local.spoke_vpc_information ? { for k, v in module.spoke_vpcs : k => v.transit_gateway_spoke_rt } : null + spoke_vpcs = aws_ec2_transit_gateway_route_table.spokes_tgw_rt } } diff --git a/variables.tf b/variables.tf index 60c6229..68e69dc 100644 --- a/variables.tf +++ b/variables.tf @@ -239,12 +239,36 @@ EOF # Spoke VPCs variable "spoke_vpcs" { description = <<-EOF - Variable is used to provide the Hub and Spoke module the neccessary information about the Spoke VPCs created. Within this variable, a map of routing domains is expected. The *key* of each map will defined that specific routing domain (e.g. prod, nonprod, etc.) and a Transit Gateway Route Table for that routing domain will be created. Inside each routing domain definition, you can define a map of VPCs with the following attributes: - - `vpc_id` = (Optional|string) VPC ID. *This value is not used in this version of the module, we keep it as placehoder when adding support for centralized VPC endpoints*. - - `transit_gateway_attachment_id` = (Optional|string) Transit Gateway VPC attachment ID. + Variable used to provide the information about the Spoke VPCs to include in the hub and spoke architecture. Information to provide is the following one: + - `routing_domains` = (Optional|list(string)) Definition of the different routing domains for the Spoke VPCs - for example *prod* or *dev*. If this variable is not provided, all the Spoke VPCs will be associated to a common routing domain (*spokes*). + - `number_vpcs` = (Optional|number) Total number of Spoke VPCs that have been attached to the Transit Gateway, regardless of the routing domain. + - `vpc_information` = (Optional|map(string)) Information about the VPCs to include in the architecture. Inside the variable, a map of the following keys is expected: + - `vpc_id` = (Optional|string) VPC ID. *This value is not used in this version of the module, we keep it as placehoder when adding support for centralized VPC endpoints*. + - `transit_gateway_attachment_id` = (Optional|string) Transit Gateway VPC attachment ID. + - `routing_domain` = (Optional|string) Routing domain to include the VPC (Transit Gateway route table association). This value needs to be included in *var.spoke_vpcs.routing_domains*. To get more information of the format of the variables, check the section "Spoke VPCs" in the README. ``` EOF type = any default = {} + + # ---------------- VALID KEYS FOR var.spoke_vpcs ---------------- + validation { + error_message = "Only valid key values for var.spoke_vpcs: \"routing_domains\", \"number_vpcs\", \"vpc_information\"." + condition = length(setsubtract(keys(var.spoke_vpcs), [ + "routing_domains", + "number_vpcs", + "vpc_information" + ])) == 0 + } + + # ---------------- VALID KEYS FOR var.spoke_vpcs.vpc_information (each key) ---------------- + validation { + error_message = "Only valid key values when defining a VPC in var.spoke_vpcs.vpc_information: \"vpc_id\", \"transit_gateway_attachment_id\", \"routing_domain\"." + condition = alltrue([for vpc in try(var.spoke_vpcs.vpc_information, {}) : length(setsubtract(keys(vpc), [ + "vpc_id", + "transit_gateway_attachment_id", + "routing_domain" + ])) == 0]) + } }