Configuring a Transit Gateway Between 3 VPCs using Terraform
Tags: cloud, terraform, aws, networkenginner
Published: 2023-03-11 14:06:45

In this post, we will be configuring 3 intra-VPC communication using Terraform.
We will also be gathering packet data using flow-logs and Cloudwatch.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p1b7cnf9tjnazst5ckw3.png)

# Algorithm of the Infrastructure
1. Create a first, second, and third VPC
2. Create first VPCs Internet Gateway
3. Create a first and second subnet in the first VPC
4. Specify the default NACL in the first VPC
5. Specify the default security group in the first VPC, and modify.
   - Add an ingress rule for SSH and ICMP
   - Add an egress rule for all protocols
6. Launch an instance in the first subnet, for the first VPC
7. Create a Transit gateway
8. Specify the default route table for the first VPC
   - Add a route destined to all IP addresses, and a target to the Internet Gateway.
   - Add a route destined to the second VPC, and a target to the Transit Gateway.
   - Add a route destined to the third VPC, and a target to the Transit Gateway.
9. Create a transit gateway attachment to the two subnets associated to the first VPC.
10. Create a first and second subnet in the second VPC
11. Specify the default route table for the second VPC
   - Add a route destined to the first VPC, and a target to the Transit Gateway.
   - Add a route destined to the third VPC, and a target to the Transit Gateway.
12. Specify the default NACL in the second VPC
13. Specify the default security group in the second VPC, and modify.
   - Add an ingress rule for SSH and ICMP
   - Add an egress rule for all protocols
14. Launch an instance in the first subnet, for the second VPC  
15. Create a transit gateway attachment to the two subnets associated to the second VPC.
16. Create a first and second subnet in the third VPC
17. Specify the default route table for the third VPC
   - Add a route destined to the first VPC, and a target to the Transit Gateway.
   - Add a route destined to the second VPC, and a target to the Transit Gateway.
18. Specify the default NACL in the third VPC
19. Specify the default security group in the third VPC, and modify.
   - Add an ingress rule for SSH and ICMP
   - Add an egress rule for all protocols
20. Launch an instance in the first subnet, for the third VPC
21. Create a transit gateway attachment to the two subnets associated to the third VPC.
22. Create a Transit gateway route table for the second and third VPC
   - Add a route destined to all IP addresses, which uses the first VPCs transit gateway attachment as an ingress.
   - Associate the first and second transit gateway attachment to the route table so they are both used individually as egresses.
23. Create a Transit gateway route table for the first VPC
   - Add two routes destined to the second and third VPC, which uses the second and third VPCs transit gateway attachment as an ingress.
   - Associate the first transit gateway attachment to the route table, so its used as an egress.
24. Create an IAM Role for flow logs
25. Create an IAM Role policy for flow logs
26. Create a Cloudwatch log group
27. Create a flow log for the first VPC

okay, this is a long algorithm, but this is basically how the infrastructure will be built.
Also, one thing I learnt about writing code for this infrastructure is the naming convention. I struggled with what to name my subnets. I decided that it was best it reflects the VPC name and its AZ, for example, `public_subnet_1_for_VPC_A_AZ_2A`
## Create a first, second, and third VPC
```terraform
resource "aws_vpc" "VPC_A" {
  cidr_block       = "192.168.0.0/16"
  instance_tenancy = "default"

  tags = {
    Name = "VPC_A"
  }
}

resource "aws_vpc" "VPC_B" {
  cidr_block       = "10.0.0.0/16"
  instance_tenancy = "default"

  tags = {
    Name = "VPC_B"
  }
}

resource "aws_vpc" "VPC_C" {
  cidr_block       = "10.1.0.0/16"
  instance_tenancy = "default"

  tags = {
    Name = "VPC_C"
  }
}
```
## Create first VPCs Internet Gateway
```terraform
resource "aws_internet_gateway" "VPC_A_IGW" {
  vpc_id = aws_vpc.VPC_A.id

  tags = {
    "name" = "VPC_A_IGW"
  }
}
```
## Create a first and second subnet in the first VPC
```terraform
resource "aws_subnet" "public_subnet_1_for_VPC_A_AZ_2A" {
  vpc_id                  = aws_vpc.VPC_A.id
  cidr_block              = "192.168.1.0/24"
  availability_zone       = "us-west-2a"
  map_public_ip_on_launch = "true"

  tags = {
    Name = "public_subnet_1_for_VPC_A_AZ_2A"
  }
}

resource "aws_subnet" "public_subnet_2_for_VPC_A_AZ_2B" {
  vpc_id            = aws_vpc.VPC_A.id
  cidr_block        = "192.168.2.0/24"
  availability_zone = "us-west-2b"
  tags = {
    Name = "public_subnet_2_for_VPC_A_AZ_2B"
  }
}
```
## Specify the default NACL in the first VPC
```terraform
resource "aws_default_network_acl" "Default_VPC_A_NACL" {
  default_network_acl_id = aws_vpc.VPC_A.default_network_acl_id

  ingress {
    protocol   = -1
    rule_no    = 100
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 0
    to_port    = 0
  }

  egress {
    protocol   = -1
    rule_no    = 100
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 0
    to_port    = 0
  }

  tags = {
    "name" = "Default_VPC_A_NACL"
  }
}
```
## Specify the default security group in the first VPC, and modify.
   - Add an ingress rule for SSH and ICMP
   - Add an egress rule for all protocols
```terraform
resource "aws_default_security_group" "SG_bastion" {
  vpc_id = aws_vpc.VPC_A.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = -1
    to_port     = -1
    protocol    = "icmp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
```
## Launch an instance in the first subnet, for the first VPC
```terraform
resource "aws_instance" "Bastion" {
  ami               = "ami-0b029b1931b347543"
  instance_type     = "t2.micro"
  tenancy           = "default"
  availability_zone = "us-west-2a"
  key_name          = ""
  subnet_id         = aws_subnet.public_subnet_1_for_VPC_A_AZ_2A.id
  security_groups   = ["${aws_default_security_group.SG_bastion.id}"]

  tags = {
    "name" = "Bastion"
  }
}
```
## Create a Transit gateway
```terraform
resource "aws_ec2_transit_gateway" "TGW_Lab" {
  description                     = "the labs transit gateway"
  default_route_table_association = "disable"
  default_route_table_propagation = "disable"

  tags = {
    Name = "TGW_Lab"
  }
}
```
## Specify the default route table for the first VPC
   - Add a route destined to all IP addresses, and a target to the Internet Gateway.
   - Add a route destined to the second VPC, and a target to the Transit Gateway.
   - Add a route destined to the third VPC, and a target to the Transit Gateway.
```terraform
resource "aws_default_route_table" "VPC_A_RT" {
  default_route_table_id = aws_vpc.VPC_A.default_route_table_id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.VPC_A_IGW.id
  }

  route {
    cidr_block         = "10.0.0.0/16"
    transit_gateway_id = aws_ec2_transit_gateway.TGW_Lab.id
  }

  route {
    cidr_block         = "10.1.0.0/16"
    transit_gateway_id = aws_ec2_transit_gateway.TGW_Lab.id
  }

  tags = {
    Name = "VPC_A_RT"
  }
}
```
## Create a transit gateway attachment to the two subnets associated to the first VPC.
```terraform
resource "aws_ec2_transit_gateway_vpc_attachment" "TGA_VPC_A" {
  subnet_ids         = [aws_subnet.public_subnet_1_for_VPC_A_AZ_2A.id, aws_subnet.public_subnet_2_for_VPC_A_AZ_2B.id]
  transit_gateway_id = aws_ec2_transit_gateway.TGW_Lab.id
  vpc_id             = aws_vpc.VPC_A.id

  tags = {
    Name = "TGA_VPC_A"
  }
}
```
## Create a first and second subnet in the second VPC
```terraform
resource "aws_subnet" "public_subnet_1_for_VPC_B_AZ_2A" {
  vpc_id            = aws_vpc.VPC_B.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = "us-west-2a"
  tags = {
    Name = "public_subnet_1_for_VPC_B_AZ_2A"
  }
}

resource "aws_subnet" "public_subnet_2_for_VPC_B_AZ_2B" {
  vpc_id            = aws_vpc.VPC_B.id
  cidr_block        = "10.0.3.0/24"
  availability_zone = "us-west-2b"
  tags = {
    Name = "public_subnet_2_for_VPC_B_AZ_2B"
  }
}
```
## Specify the default route table for the second VPC
   - Add a route destined to the first VPC, and a target to the Transit Gateway.
   - Add a route destined to the third VPC, and a target to the Transit Gateway.
```terraform
resource "aws_default_route_table" "VPC_B_RT" {
  default_route_table_id = aws_vpc.VPC_B.default_route_table_id

  route {
    cidr_block         = "192.168.0.0/16"
    transit_gateway_id = aws_ec2_transit_gateway.TGW_Lab.id
  }

  route {
    cidr_block         = "10.1.0.0/16"
    transit_gateway_id = aws_ec2_transit_gateway.TGW_Lab.id
  }

  tags = {
    Name = "VPC_B_RT"
  }
}
```
## Specify the default NACL in the second VPC
```terraform
resource "aws_default_network_acl" "Default_VPC_B_NACL" {
  default_network_acl_id = aws_vpc.VPC_B.default_network_acl_id

  ingress {
    protocol   = -1
    rule_no    = 100
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 0
    to_port    = 0
  }

  egress {
    protocol   = -1
    rule_no    = 100
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 0
    to_port    = 0
  }

  tags = {
    "name" = "Default_VPC_B_NACL"
  }
}
```
## Specify the default security group in the second VPC, and modify.
   - Add an ingress rule for SSH and ICMP
   - Add an egress rule for all protocols
```terraform
resource "aws_default_security_group" "DB_1" {
  vpc_id = aws_vpc.VPC_B.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = -1
    to_port     = -1
    protocol    = "icmp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
```
## Launch an instance in the first subnet, for the second VPC
```terraform
resource "aws_instance" "DB_1" {
  ami               = "ami-0b029b1931b347543"
  instance_type     = "t2.micro"
  tenancy           = "default"
  availability_zone = "us-west-2a"
  key_name          = ""
  subnet_id         = aws_subnet.public_subnet_1_for_VPC_B_AZ_2A.id
  security_groups   = ["${aws_default_security_group.DB_1.id}"]

  tags = {
    "name" = "DB_1"
  }
}
```  
## Create a transit gateway attachment to the two subnets associated to the second VPC.
```terraform
resource "aws_ec2_transit_gateway_vpc_attachment" "TGA_VPC_B" {
  subnet_ids         = [aws_subnet.public_subnet_1_for_VPC_B_AZ_2A.id, aws_subnet.public_subnet_2_for_VPC_B_AZ_2B.id]
  transit_gateway_id = aws_ec2_transit_gateway.TGW_Lab.id
  vpc_id             = aws_vpc.VPC_B.id

  tags = {
    Name = "TGA_VPC_A"
  }
}
```
## Create a first and second subnet in the third VPC
```terraform
resource "aws_subnet" "public_subnet_1_for_VPC_C_AZ_2A" {
  vpc_id            = aws_vpc.VPC_C.id
  cidr_block        = "10.1.2.0/24"
  availability_zone = "us-west-2a"
  tags = {
    Name = "public_subnet_1_for_VPC_C_AZ_2A"
  }
}

resource "aws_subnet" "public_subnet_2_for_VPC_C_AZ_2B" {
  vpc_id            = aws_vpc.VPC_C.id
  cidr_block        = "10.1.3.0/24"
  availability_zone = "us-west-2b"
  tags = {
    Name = "public_subnet_2_for_VPC_C_AZ_2B"
  }
}
```
## Specify the default route table for the third VPC
   - Add a route destined to the first VPC, and a target to the Transit Gateway.
   - Add a route destined to the second VPC, and a target to the Transit Gateway.
```terraform
resource "aws_default_route_table" "VPC_C_RT" {
  default_route_table_id = aws_vpc.VPC_C.default_route_table_id

  route {
    cidr_block         = "192.168.0.0/16"
    transit_gateway_id = aws_ec2_transit_gateway.TGW_Lab.id
  }

  route {
    cidr_block         = "10.0.0.0/16"
    transit_gateway_id = aws_ec2_transit_gateway.TGW_Lab.id
  }

  tags = {
    Name = "VPC_C_RT"
  }
}
```
## Specify the default NACL in the third VPC
```terraform
resource "aws_default_network_acl" "Default_VPC_C_NACL" {
  default_network_acl_id = aws_vpc.VPC_C.default_network_acl_id

  ingress {
    protocol   = -1
    rule_no    = 100
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 0
    to_port    = 0
  }

  egress {
    protocol   = -1
    rule_no    = 100
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 0
    to_port    = 0
  }

  tags = {
    "name" = "Default_VPC_C_NACL"
  }
}
```
## Specify the default security group in the third VPC, and modify.
   - Add an ingress rule for SSH and ICMP
   - Add an egress rule for all protocols
```terraform
resource "aws_default_security_group" "DB_2" {
  vpc_id = aws_vpc.VPC_C.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = -1
    to_port     = -1
    protocol    = "icmp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
```
## Launch an instance in the first subnet, for the third VPC
```terraform
resource "aws_instance" "DB_2" {
  ami               = "ami-0b029b1931b347543"
  instance_type     = "t2.micro"
  tenancy           = "default"
  availability_zone = "us-west-2a"
  key_name          = ""
  subnet_id         = aws_subnet.public_subnet_1_for_VPC_C_AZ_2A.id
  security_groups   = ["${aws_default_security_group.DB_2.id}"]

  tags = {
    "name" = "DB_2"
  }
}
```
## Create a transit gateway attachment to the two subnets associated to the third VPC.
```terraform
resource "aws_ec2_transit_gateway_vpc_attachment" "TGA_VPC_C" {
  subnet_ids         = [aws_subnet.public_subnet_1_for_VPC_C_AZ_2A.id, aws_subnet.public_subnet_2_for_VPC_C_AZ_2B.id]
  transit_gateway_id = aws_ec2_transit_gateway.TGW_Lab.id
  vpc_id             = aws_vpc.VPC_C.id

  tags = {
    Name = "TGA_VPC_C"
  }
}
```
## Create a Transit gateway route table for the second and third VPC
   - Add a route destined to all IP addresses, which uses the first VPCs transit gateway attachment as an ingress.
   - Associate the first and second transit gateway attachment to the route table so they are both used individually as egresses.
```terraform
resource "aws_ec2_transit_gateway_route_table" "TGW_RTB_VPC_B_C" {
  transit_gateway_id = aws_ec2_transit_gateway.TGW_Lab.id

  tags = {
    "name" = "TGW_RTB_VPC_B_C"
  }
}

resource "aws_ec2_transit_gateway_route" "TGW_RTB_VPC_B_C_Route_1" {
  destination_cidr_block         = "0.0.0.0/0"
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.TGA_VPC_A.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.TGW_RTB_VPC_B_C.id
}

resource "aws_ec2_transit_gateway_route_table_association" "TGW_RTB_VPC_B_C_Association_1" {
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.TGA_VPC_B.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.TGW_RTB_VPC_B_C.id
}

resource "aws_ec2_transit_gateway_route_table_association" "TGW_RTB_VPC_B_C_Association_2" {
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.TGA_VPC_C.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.TGW_RTB_VPC_B_C.id
}
```
## Create a Transit gateway route table for the first VPC
   - Add two routes destined to the second and third VPC, which uses the second and third VPCs transit gateway attachment as an ingress.
   - Associate the first transit gateway attachment to the route table, so its used as an egress.
```terraform
resource "aws_ec2_transit_gateway_route_table" "TGW_RTB_VPC_A" {
  transit_gateway_id = aws_ec2_transit_gateway.TGW_Lab.id

  tags = {
    "name" = "TGW_RTB_VPC_A"
  }
}

resource "aws_ec2_transit_gateway_route" "TGW_RTB_VPC_A_Route_1" {
  destination_cidr_block         = "10.0.0.0/16"
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.TGA_VPC_B.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.TGW_RTB_VPC_A.id
}

resource "aws_ec2_transit_gateway_route" "TGW_RTB_VPC_A_Route_2" {
  destination_cidr_block         = "10.1.0.0/16"
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.TGA_VPC_C.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.TGW_RTB_VPC_A.id
}

resource "aws_ec2_transit_gateway_route_table_association" "TGW_RTB_VPC_A_Association_1" {
  transit_gateway_attachment_id  = aws_ec2_transit_gateway_vpc_attachment.TGA_VPC_A.id
  transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.TGW_RTB_VPC_A.id
}
```
## Create an IAM Role for flow logs
```terraform
resource "aws_iam_role" "role_lab_flow_logs" {
  name = "role_lab_flow_logs"

  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "vpc-flow-logs.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}
```
## Create an IAM Role policy for flow logs
```terraform
resource "aws_iam_role_policy" "IAM_Role_Policy_for_Flow_Log" {
  name = "IAM_Role_Policy_for_Flow_Log"
  role = aws_iam_role.role_lab_flow_logs.id

  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents",
        "logs:DescribeLogGroups",
        "logs:DescribeLogStreams"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}
EOF
}
```
## Create a Cloudwatch log group
```terraform
resource "aws_cloudwatch_log_group" "Transit_Gateway_Log_Group" {
  name              = "Transit_Gateway_Log_Group"
}
```
## Create a flow log for the first VPC
```terraform
resource "aws_flow_log" "flow_log_tgw_lab" {
  iam_role_arn    = aws_iam_role.role_lab_flow_logs.arn
  log_destination = aws_cloudwatch_log_group.Transit_Gateway_Log_Group.arn
  traffic_type    = "ALL"
  vpc_id          = aws_vpc.VPC_A.id

  tags = {
    Name = "flow_log_tgw_lab"
  }
}
```
This is usually the log format when reading a flow log data:
`log_format = "${version} ${account-id} ${interface-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${packets} ${bytes} ${start} ${end} ${action} ${log-status}"`

When you deploy the code and grab the public IP of the bastion host, then SSH into it, then do a `ping to 8.8.8.8` 
When simply go to the Cloudwatch service via the GUI, specifically to "Log Groups," there you will find your flow log data streams for several elastic network interfaces (ENIs). 
Do a `terraform state show aws_instance.Bastion` to get the ENI of your instance. Then go back to cloudwatch and select the ENI. Open the data, and simply search for `8.8.8.8` in the log stream, you will notice that an ICMP session was made.