tgw
AWS Transit Gateway connects your Amazon Virtual Private Clouds (VPCs) and on-premises networks through a central hub. This connection simplifies your network and puts an end to complex peering relationships. Transit Gateway acts as a highly scalable cloud router—each new connection is made only once.
For more on Transit Gateway, see the AWS documentation.
Requirements
In order to connect accounts with Transit Gateway, we deploy Transit Gateway to a central account, typically
core-network, and then deploy Transit Gateway attachments for each connected account. Each connected accounts needs a
Transit Gateway attachment for the given account's VPC, either by VPC attachment or by Peering Connection attachment.
Furthermore, each private subnet in each connected VPC needs to explicitly list the CIDRs for all allowed connections.
Solution
First we deploy the Transit Gateway Hub, tgw/hub, to a central network account. The component prepares the Transit
Gateway network with the following steps:
- Provision Transit Gateway in the network account
 - Collect VPC and EKS component output from every account connected to Transit Gateway
 - Share the Transit Gateway with the Organization using Resource Access Manager (RAM)
 
By using the tgw/hub component to collect Terraform output from connected accounts, only this single component
requires access to the Terraform state of all connected accounts.
Next we deploy tgw/spoke to the network account and then to every connected account. This spoke component connects the
given account to the central hub and any listed connection with the following steps:
- Create a Transit Gateway VPC attachment in the spoke account. This connects the account's VPC to the shared Transit Gateway from the hub account.
 - Define all allowed routes for private subnets. Each private subnet in an account's VPC has it's own route table. This route table needs to explicitly list any allowed connection to another account's VPC CIDR.
 - (Optional) Create an EKS Cluster Security Group rule to allow traffic to the cluster in the given account.
 
Implementation
- Deploy 
tgw/hubto the network account. List every allowed connection: 
# stacks/catalog/tgw/hub
components:
  terraform:
    tgw/hub/defaults:
      metadata:
        type: abstract
        component: tgw/hub
      vars:
        enabled: true
        name: tgw-hub
        tags:
          Team: sre
          Service: tgw-hub
    tgw/hub:
      metadata:
        inherits:
          - tgw/hub/defaults
        component: tgw/hub
      vars:
        # These are all connections available for spokes in this region
        # Defaults environment to this region
        connections:
          - account:
              tenant: core
              stage: network
          - account:
              tenant: core
              stage: auto
            eks_component_names:
              - eks/cluster
          - account:
              tenant: plat
              stage: sandbox
            eks_component_names: [] # No clusters deployed for sandbox
          - account:
              tenant: plat
              stage: dev
            eks_component_names:
              - eks/cluster
          - account:
              tenant: plat
              stage: staging
            eks_component_names:
              - eks/cluster
          - account:
              tenant: plat
              stage: prod
            eks_component_names:
              - eks/cluster
- Deploy 
tgw/spoketo network. List every account connected to network (all accounts): 
# stacks/catalog/tgw/spoke
components:
  terraform:
    tgw/spoke-defaults:
      metadata:
        type: abstract
        component: tgw/spoke
      vars:
        enabled: true
        name: tgw-spoke
        tgw_hub_tenant_name: core
        tgw_hub_stage_name: network # default, added for visibility
        tags:
          Team: sre
          Service: tgw-spoke
# stacks/orgs/acme/core/network/us-east-1/network.yaml
tgw/spoke:
  metadata:
    inherits:
      - tgw/spoke-defaults
  vars:
    # This is what THIS spoke is allowed to connect to
    connections:
      - account:
          tenant: core
          stage: network
      - account:
          tenant: core
          stage: auto
      - account:
          tenant: plat
          stage: sandbox
      - account:
          tenant: plat
          stage: dev
      - account:
          tenant: plat
          stage: staging
      - account:
          tenant: plat
          stage: prod
- Finally, deploy 
tgw/spokefor each connected account and list the allowed connections: 
# stacks/orgs/acme/plat/dev/us-east-1/network.yaml
tgw/spoke:
  metadata:
    inherits:
      - tgw/spoke-defaults
  vars:
    connections:
      # Always list self
      - account:
          tenant: plat
          stage: dev
      - account:
          tenant: core
          stage: network
      - account:
          tenant: core
          stage: auto
Alternate Regions
In order to connect any account to the network, the given account needs:
- Access to the shared Transit Gateway hub
 - An attachment for the given Transit Gateway hub
 - Routes to and from each private subnet
 
However, sharing the Transit Gateway hub via RAM is only supported in the same region as the primary hub. Therefore, we must instead deploy a new hub in the alternate region and create a Transit Gateway Peering Connection between the two Transit Gateway hubs.
Furthermore, since this Transit Gateway hub for the alternate region is now peered, we must create a Peering Transit Gateway attachment, opposed to a VPC Transit Gateway Attachment.
Cross Region Deployment
- 
Deploy
tgw/hubandtgw/spokeinto the primary region as described in Implementation - 
Deploy
tgw/hubandtgw/cross-region-hubinto the new region in the network account. See the following configuration: 
# stacks/catalog/tgw/cross-region-hub
import:
  - catalog/tgw/hub
components:
  terraform:
    # Cross region TGW requires additional hub in the alternate region
    tgw/hub:
      vars:
        # These are all connections available for spokes in this region
        # Defaults environment to this region
        connections:
          # Hub for this region is always required
          - account:
              tenant: core
              stage: network
          # VPN source
          - account:
              tenant: core
              stage: network
              environment: use1
          # Github Runners
          - account:
              tenant: core
              stage: auto
              environment: use1
            eks_component_names:
              - eks/cluster
          # All stacks where a spoke will be deployed
          - account:
              tenant: plat
              stage: dev
          - account:
              tenant: plat
              stage: staging
          - account:
              tenant: plat
              stage: prod
    # This alternate hub needs to be connected to the primary region's hub
    tgw/cross-region-hub-connector:
      vars:
        enabled: true
        primary_tgw_hub_region: us-east-1
- Deploy a 
tgw/spokefor network in the new region. For example: 
# stacks/orgs/acme/core/network/us-west-2/network.yaml
tgw/spoke:
  metadata:
    inherits:
      - tgw/spoke-defaults
  vars:
    peered_region: true # Required for alternate region spokes
    connections:
      # This stack, always included
      - account:
          tenant: core
          stage: network
      # VPN
      - account:
          tenant: core
          environment: use1
          stage: network
      # Automation runners
      - account:
          tenant: core
          environment: use1
          stage: auto
        eks_component_names:
          - eks/cluster
      # All other connections
      - account:
          tenant: plat
          stage: dev
      - account:
          tenant: plat
          stage: staging
      - account:
          tenant: plat
          stage: prod
- Deploy the 
tgw/spokecomponents for all connected accounts. For example: 
# stacks/orgs/acme/plat/dev/us-west-2/network.yaml
tgw/spoke:
  metadata:
    inherits:
      - tgw/spoke-defaults
  vars:
    peered_region: true # Required for alternate region spokes
    connections:
      # This stack, always included
      - account:
          tenant: plat
          stage: dev
      # TGW Hub, always included
      - account:
          tenant: core
          stage: network
      # VPN
      - account:
          tenant: core
          environment: use1
          stage: network
      # Automation runners
      - account:
          tenant: core
          environment: use1
          stage: auto
        eks_component_names:
          - eks/cluster
- Update any existing 
tgw/spokeconnections to allow the new account and region. For example: 
# stacks/orgs/acme/core/auto/us-east-1/network.yaml
tgw/spoke:
  metadata:
    inherits:
      - tgw/spoke-defaults
  vars:
    connections:
      - account:
          tenant: core
          stage: network
      - account:
          tenant: core
          stage: corp
      - account:
          tenant: core
          stage: auto
      - account:
          tenant: plat
          stage: sandbox
      - account:
          tenant: plat
          stage: dev
      - account:
          tenant: plat
          stage: staging
      - account:
          tenant: plat
          stage: prod
      # Alternate regions     <-------- These are added for alternate region
      - account:
          tenant: core
          stage: network
          environment: usw2
      - account:
          tenant: plat
          stage: dev
          environment: usw2
      - account:
          tenant: plat
          stage: staging
          environment: usw2
      - account:
          tenant: plat
          stage: prod
          environment: usw2
Destruction
When destroying Transit Gateway components, order of operations matters. Always destroy any removed tgw/spoke
components before removing a connection from the tgw/hub component.
The tgw/hub component creates map of VPC resources that each tgw/spoke component references. If the required
reference is removed before the tgw/spoke is destroyed, Terraform will fail to destroy the given tgw/spoke
component.
Atmos Workflows make applying and destroying Transit Gateway much easier! For example, to destroy components in the correct order, use a workflow similar to the following:
# stacks/workflows/network.yaml
workflows:
  destroy/tgw:
    description: Destroy the Transit Gateway "hub" and "spokes" for connecting VPCs.
    steps:
      - command: echo 'Destroying platform spokes for Transit Gateway'
        type: shell
        name: plat-spokes
      - command: terraform destroy tgw/spoke -s plat-use1-sandbox --auto-approve
      - command: terraform destroy tgw/spoke -s plat-use1-dev --auto-approve
      - command: terraform destroy tgw/spoke -s plat-use1-staging --auto-approve
      - command: terraform destroy tgw/spoke -s plat-use1-prod --auto-approve
      - command: echo 'Destroying core spokes for Transit Gateway'
        type: shell
        name: core-spokes
      - command: terraform destroy tgw/spoke -s core-use1-auto --auto-approve
      - command: terraform destroy tgw/spoke -s core-use1-network --auto-approve
      - command: echo 'Destroying Transit Gateway Hub'
        type: shell
        name: hub
      - command: terraform destroy tgw/hub -s core-use1-network --auto-approve
FAQ
tgw/spoke Fails to Recreate VPC Attachment with DuplicateTransitGatewayAttachment Error
╷
│ Error: creating EC2 Transit Gateway VPC Attachment: DuplicateTransitGatewayAttachment: tgw-0xxxxxxxxxxxxxxxx has non-deleted Transit Gateway Attachments with same VPC ID.
│ 	status code: 400, request id: aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
│
│   with module.tgw_spoke_vpc_attachment.module.standard_vpc_attachment.aws_ec2_transit_gateway_vpc_attachment.default["core-use2-network"],
│   on .terraform/modules/tgw_spoke_vpc_attachment.standard_vpc_attachment/main.tf line 43, in resource "aws_ec2_transit_gateway_vpc_attachment" "default":
│   43: resource "aws_ec2_transit_gateway_vpc_attachment" "default" {
│
╵
Releasing state lock. This may take a few moments...
exit status 1
This is caused by Terraform attempting to create the replacement VPC attachment before the original is completely destroyed. Retry the apply. Now you should see only "create" actions.
Changelog
Upgrading to v1.276.0
Components PR #804
Affected Components
tgw/hubtgw/spoketgw/cross-region-hub-connector
Summary
This change to the Transit Gateway components, PR #804, added support for cross-region connections.
As part of that change, we've added environment to the component identifier used in the Terraform Output created by
tgw/hub. Because of that map key change, all resources in Terraform now have a new resource identifier and therefore
must be recreated with Terraform or removed from state and imported into the new resource ID.
Recreating the resources is the easiest solution but means that Transit Gateway connectivity will be lost while the changes apply, which typically takes an hour. Alternatively, removing the resources from state and importing back into the new resource ID is much more complex operationally but means no lost Transit Gateway connectivity.
Since we use Transit Gateway for VPN and GitHub Automation runner access, a temporarily lost connection is not a
significant concern, so we choose to accept lost connectivity and recreate all tgw/spoke resources.
Steps
- Notify your team of a temporary VPN and Automation outage for accessing private networks
 - Deploy all 
tgw/hubcomponents. There should be a hub component in each region of your network account connected to Transit Gateway - Deploy all 
tgw/spokecomponents. There should be a spoke component in every account and every region connected to Transit Gateway 
Tips
Use workflows to deploy tgw across many accounts with a single command:
atmos workflow deploy/tgw -f network
# stacks/workflows/network.yaml
workflows:
  deploy/tgw:
    description: Provision the Transit Gateway "hub" and "spokes" for connecting VPCs.
    steps:
      - command: terraform deploy tgw/hub -s core-use1-network
        name: hub
      - command: terraform deploy tgw/spoke -s core-use1-network
      - command: echo 'Creating core spokes for Transit Gateway'
        type: shell
        name: core-spokes
      - command: terraform deploy tgw/spoke -s core-use1-corp
      - command: terraform deploy tgw/spoke -s core-use1-auto
      - command: terraform deploy tgw/spoke -s plat-use1-sandbox
      - command: echo 'Creating platform spokes for Transit Gateway'
        type: shell
        name: plat-spokes
      - command: terraform deploy tgw/spoke -s plat-use1-dev
      - command: terraform deploy tgw/spoke -s plat-use1-staging
      - command: terraform deploy tgw/spoke -s plat-use1-prod