Securing your VPC using Public & Private Subnets using Terraform
Tags: cloud, aws, terraform, networkengineer
Published: 2023-03-10 13:07:36

In this lab, two instances are created, one is launched in a first subnet which is public, while the other is launched the second subnet which is private.

The private instance must access the internet using a Network Address Translation (NAT) gateway deployed into the first subnet.

No default security group, NACL, or route table is used in this demonstration. This means any non-default being used must be explicitly associated with its resource. For example, a non-default route table must be explicitly associated with a subnet, etc.

Also, in this lab, we will explore the stateful nature of a Security Group, at Algorithm no 12.

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

## Algorithm of the Infrastructure
1. Create a VPC
2. Create an Internet Gateway
3. Create a first subnet
4. Create a first NACL for the first subnet
5. Specify the first route table
   - Add a route destined to all IP addresses, and a target to the Internet Gateway.
6. Create a security group in the VPC for the first subnet
   - Add an ingress rule for SSH
   - Add an egress rule for SSH
7. Create an instance for the first subnet
8. Create an elastic IP
9. Create a NAT Gateway in the first subnet and attach the elastic IP to it
10. Create a second subnet
11. Create a second NACL for the second subnet
12. Create a second security group
   - Add an ingress rule for SSH
   - Add an egress rule for all ICMP
13. Create a second route table
    - Add a route destined for the NAT Gateway
14. Create a second instance

## Create a VPC
CIDR = 10.0.0.0/16
```terraform
resource "aws_vpc" "VPC_A" {
  cidr_block       = "10.0.0.0/16"
  instance_tenancy = "default"

  tags = {
    Name = "VPC_A"
  }
}
```
## Create an 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 subnet
```terraform
resource "aws_subnet" "VPC_A_Public_Subnet_A" {
  vpc_id                  = aws_vpc.VPC_A.id
  cidr_block              = "10.0.20.0/24"
  availability_zone       = "us-west-2a"
  map_public_ip_on_launch = "true"

  tags = {
    Name = "VPC_A_Public_Subnet_A"
  }
}
```

## Create a first NACL for the first subnet
```terraform
resource "aws_network_acl" "VPC_A_Public_NACL_A" {
vpc_id = aws_vpc.VPC_A.id
subnet_ids = [aws_subnet.VPC_A_Public_Subnet_A.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" = "VPC_A_Public_NACL_A"
  }
}

resource "aws_network_acl_association" "VPC_A_Public_NACL_A_Association" {
  network_acl_id = aws_network_acl.VPC_A_Public_NACL_A.id
  subnet_id      = aws_subnet.VPC_A_Public_Subnet_A.id
}
```

## Specify the first route table
```terraform
resource "aws_route_table" "VPC_A_Public_RT_A" {
  vpc_id = aws_vpc.VPC_A.id

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

  tags = {
    Name = "VPC_A_Public_RT_A"
  }
}

resource "aws_route_table_association" "VPC_A_Public_RT_A_Association_A" {
  subnet_id      = aws_subnet.VPC_A_Public_Subnet_A.id
  route_table_id = aws_route_table.VPC_A_Public_RT_A.id
}
```

## Create a security group in the VPC for the first subnet
```terraform
resource "aws_security_group" "SG_bastion" {
  vpc_id      = aws_vpc.VPC_A.id
  description = " SG for bastion host. SSH access only"

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

  egress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
```
## Create an instance for the first subnet
```terraform
resource "aws_instance" "Bastion" {
  ami               = "ami-0b029b1931b347543"
  instance_type     = "t2.micro"
  availability_zone = "us-west-2a"
  key_name          = ""
  tenancy           = "default"
  subnet_id         = aws_subnet.VPC_A_Public_Subnet_A.id
  security_groups   = ["${aws_security_group.SG_bastion.id}"]
}
```

## Create an elastic IP
```terraform
resource "aws_eip" "EIP_for_NAT_GW" {
  vpc = true
}
```

## Create a NAT Gateway in the second subnet and attach the elastic IP to it
```terraform
resource "aws_nat_gateway" "NAT_GW" {
  subnet_id         = aws_subnet.VPC_A_Public_Subnet_A.id
  connectivity_type = "public"
  allocation_id     = aws_eip.EIP_for_NAT_GW.id

  tags = {
    "Name" = "NAT_GW"
  }
}
```

## Create a second subnet
```terraform
resource "aws_subnet" "VPC_A_Private_Subnet_A" {
  vpc_id                  = aws_vpc.VPC_A.id
  cidr_block              = "10.0.10.0/24"
  availability_zone       = "us-west-2a"
  map_public_ip_on_launch = "false"

  tags = {
    Name = "VPC_A_Private_Subnet_A"
  }
}
```

## Create a second NACL for the second subnet
```terraform
resource "aws_network_acl" "VPC_A_Private_NACL_A" {
vpc_id = aws_vpc.VPC_A.id
subnet_ids = [aws_subnet.VPC_A_Private_Subnet_A.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" = "VPC_A_Private_NACL_A"
  }
}

resource "aws_network_acl_association" "VPC_A_Private_NACL_A_Association" {
  network_acl_id = aws_network_acl.VPC_A_Private_NACL_A.id
  subnet_id      = aws_subnet.VPC_A_Private_Subnet_A.id
}
```

# Create a second security group
Here we will explore the stateful nature of a security group
```terraform
resource "aws_security_group" "SG_Private" {
  name        = "SG_Private"
  description = "Security group for private subnet instances."
  vpc_id      = aws_vpc.VPC_A.id

  ingress {
    description     = "Incoming & Outgoing SSH"
    from_port       = 22
    to_port         = 22
    protocol        = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    description = "Outgoing & Incoming ICMP"
    from_port   = -1
    to_port     = -1
    protocol    = "icmp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    "name" = "SG_Private"
  }
}
```
We can notice that the `ingress` rule is for an SSH protocol, while the `egress` rule is for the ICMP protocol.

On AWS, when a packet ingresses or is directed towards an instance, it can leave the instance even if the  egress rule doesn't match. That is why I described it as “incoming & outgoing SSH”
The Bastion instance can SSH into the private instance. Since the Bastion security group has an egress SSH rule set. Also, since the Private security grphp has an ingress SSH rule set.

Also, the private instance can ping any address on the Internet.
ICMP uses an "echo request" and an "echo reply.”
Since the first outgoing “request” traffic was approved, its incoming “reply” traffic will be approved too. That is why I described it as an “outgoing & incoming ICMP”


## Create a second route table
```terraform
resource "aws_route_table" "VPC_A_Private_RT_A" {
  vpc_id = aws_vpc.VPC_A.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_nat_gateway.NAT_GW.id
  }

  tags = {
    Name = "VPC_A_Private_RT_A"
  }
}

resource "aws_route_table_association" "VPC_A_Private_RT_A_Association" {
  subnet_id      = aws_subnet.VPC_A_Private_Subnet_A.id
  route_table_id = aws_route_table.VPC_A_Private_RT_A.id
}
```


## Create a second instance
```terraform
resource "aws_instance" "VPC_A_Private_Subnet_A_Instance_A" {
  ami               = "ami-0b029b1931b347543"
  instance_type     = "t2.micro"
  availability_zone = "us-west-2a"
  key_name          = ""
  tenancy           = "default"
  subnet_id         = aws_subnet.VPC_A_Private_Subnet_A.id
  security_groups   = ["${aws_security_group.SG_Private.id}"]

  tags = {
    "name" = "VPC_A_Private_Subnet_A_Instance_A"
  }
}
```

After deploying the configurations, configure [SSH Agent forwarding](https://www.howtogeek.com/devops/what-is-ssh-agent-forwarding-and-how-do-you-use-it/) on Putty, then SSH into your first instance, and then into the second instance. Lastly do a ping to any public URL from the second instance and make sure its successful.

## Follow my AWS Advanced Networking Journey on Github
AWS Advanced Networking
https://github.com/Its-All-About-the-Journey/AWS-Advanced-Networking