DEV Community

Charles Uneze
Charles Uneze

Posted on

Configuring a Transit Gateway Between 3 VPCs using Terraform

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

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

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

Create first VPCs Internet Gateway

resource "aws_internet_gateway" "VPC_A_IGW" {
  vpc_id = aws_vpc.VPC_A.id

  tags = {
    "name" = "VPC_A_IGW"
  }
}
Enter fullscreen mode Exit fullscreen mode

Create a first and second subnet in the first VPC

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

Specify the default NACL in the first VPC

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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
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"]
  }
}
Enter fullscreen mode Exit fullscreen mode

Launch an instance in the first subnet, for the first VPC

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

Create a Transit gateway

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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.
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"
  }
}
Enter fullscreen mode Exit fullscreen mode

Create a transit gateway attachment to the two subnets associated to the first VPC.

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

Create a first and second subnet in the second VPC

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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.
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"
  }
}
Enter fullscreen mode Exit fullscreen mode

Specify the default NACL in the second VPC

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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
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"]
  }
}
Enter fullscreen mode Exit fullscreen mode

Launch an instance in the first subnet, for the second VPC

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

Create a transit gateway attachment to the two subnets associated to the second VPC.

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

Create a first and second subnet in the third VPC

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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.
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"
  }
}
Enter fullscreen mode Exit fullscreen mode

Specify the default NACL in the third VPC

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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
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"]
  }
}
Enter fullscreen mode Exit fullscreen mode

Launch an instance in the first subnet, for the third VPC

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

Create a transit gateway attachment to the two subnets associated to the third VPC.

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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.
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
}
Enter fullscreen mode Exit fullscreen mode

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.
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
}
Enter fullscreen mode Exit fullscreen mode

Create an IAM Role for flow logs

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
}
Enter fullscreen mode Exit fullscreen mode

Create an IAM Role policy for flow logs

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
}
Enter fullscreen mode Exit fullscreen mode

Create a Cloudwatch log group

resource "aws_cloudwatch_log_group" "Transit_Gateway_Log_Group" {
  name              = "Transit_Gateway_Log_Group"
}
Enter fullscreen mode Exit fullscreen mode

Create a flow log for the first VPC

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)