Hybrid cloud task 4, providing NAT gateway to private subnet

Hiteshkoolwal
4 min readOct 18, 2020

In this article, we are going to host a WordPress application on AWS in a secure manner. A WordPress application requires us to run WordPress and MySQL. We are going to create a VPC and two subnets in it. Out of which, one subnet can access the internet through an internet gateway and another subnet is a private subnet that accesses the internet through a NAT gateway and a bastion host. We are going to be running three EC2 instances one running WordPress, another one running MySQL, and one which acts as a bastion host. The EC2 instance running WordPress is publically available through our public subnet. However, our MySQL instance has access only to our private subnet. Our WordPress instance can access our MySQL instance through our private subnet. The bastion host has access to our public subnet and NAT gateway. Our MySQL instance can be remotely accessed through our bastion host. This enhances the security of our application. The use of bastion host enables us to access our MySQL instance but it is going to be extremely difficult to exploit due to the presence of our bastion host. I am going to assume you have already installed AWS CLI and created a profile. We are going to be using terraform to describe, manage, and deploy our infrastructure. First, create a directory for our project and create a file called main.tf in it. The code to be written in main.tf is described in the following steps:-

  • First, configure provider details.
provider "aws" {
region="ap-south-1"
profile="adminuser_profile"
}
  • Then create a VPC.
# Main vpcresource "aws_vpc" "main_vpc" {
cidr_block="192.168.0.0/16"
instance_tenancy="default"
enable_dns_hostnames="true"
tags = {
Name="main_vpc"
}
}
  • Next, create a private and public subnet.
# Private subnet
resource "aws_subnet" "private_subnet" {
vpc_id=aws_vpc.main_vpc.id
cidr_block="192.168.1.0/24"
availability_zone="ap-south-1b"
tags = {
Name="private_subnet"
}
}
# Public subnet
resource "aws_subnet" "public_subnet" {
vpc_id=aws_vpc.main_vpc.id
cidr_block="192.168.0.0/24"
map_public_ip_on_launch="true"
availability_zone="ap-south-1a"
tags = {
Name="public_subnet"
}
}
  • Then create an internet gateway.
# Internet gatewayresource "aws_internet_gateway" "main_ig" {
vpc_id=aws_vpc.main_vpc.id
tags = {
Name="main_ig"
}
}
  • Then create a routeing table with a route and associate it with our public subnet.
# Routing tableresource "aws_route_table" "main_rt" {
vpc_id=aws_vpc.main_vpc.id
route {
cidr_block="0.0.0.0/0"
gateway_id=aws_internet_gateway.main_ig.id
}
tags = {
Name="main_rt"
}
}
# Route table associationresource "aws_route_table_association" "pub_rt_assoc" {
subnet_id=aws_subnet.public_subnet.id
route_table_id=aws_route_table.main_rt.id
}
  • Next, create an RSA key pair for ssh using terraform.
# RSA keypair for sshresource "tls_private_key" "main_key" {
algorithm="RSA"
}
module "key_pair" {
source="terraform-aws-modules/key-pair/aws"
key_name="main_key"
public_key=tls_private_key.main_key.public_key_openssh
}
  • Then create three security groups for our public subnet, bastion host, private subnet.
# Security groups# Security group for public subnetresource "aws_security_group" "pub_sg" {
name="pub_sg"
vpc_id=aws_vpc.main_vpc.id

ingress {
description="Allow ssh on port 22"
from_port=22
to_port=22
protocol="tcp"
cidr_blocks=["0.0.0.0/0"]
}
ingress {
description="Allow http on port 80"
from_port=80
to_port=80
protocol="tcp"
cidr_blocks=["0.0.0.0/0"]
}
egress {
from_port=0
to_port=0
protocol="-1"
cidr_blocks=["0.0.0.0/0"]
}

tags = {
Name="pub_sg"
}
}
# Security group for bastion hostresource "aws_security_group" "bastion_sg" {
name="bastion_sg"
vpc_id=aws_vpc.main_vpc.id

ingress {
description = "ssh on port 22"
from_port=22
to_port=22
protocol="tcp"
cidr_blocks=["0.0.0.0/0"]
}
ingress {
from_port=0
to_port=0
protocol="-1"
cidr_blocks=["192.168.1.0/24"]
}
egress {
from_port=0
to_port=0
protocol="-1"
cidr_blocks=["0.0.0.0/0"]
}
tags = {
Name="bastion_sg"
}
}
# Security group for private subnetresource "aws_security_group" "priv_sg" {
name="priv_sg"
vpc_id=aws_vpc.main_vpc.id

ingress {
description="TLS on port 3306"
from_port=3306
to_port=3306
protocol="tcp"
security_groups=[aws_security_group.pub_sg.id]
}
ingress {
from_port=0
to_port=0
protocol="-1"
security_groups=[aws_security_group.bastion_sg.id]
}
egress {
from_port=0
to_port=0
protocol="-1"
cidr_blocks=["0.0.0.0/0"]
}
tags = {
Name="priv_sg"
}
}
  • Then create three EC2 instances that are our WordPress instance, MySQL instance, and bastion host instance.
# EC2 instances# EC2 instance for worpressresource "aws_instance" "wp_instance" {
ami="ami-004a955bfb611bf13"
instance_type="t2.micro"
subnet_id=aws_subnet.public_subnet.id
vpc_security_group_ids=[aws_security_group.pub_sg.id]
key_name="main_key"
tags = {
Name="wp_instance"
}
}
# EC2 instance for bastion hostresource "aws_instance" "bastion_instance" {
ami="ami-0732b62d310b80e97"
instance_type="t2.micro"
subnet_id=aws_subnet.public_subnet.id
key_name="main_key"
vpc_security_group_ids=[aws_security_group.bastion_sg.id]

tags = {
Name="bastion_instance"
}
}
# EC2 instance for mysqlresource "aws_instance" "mysql_instance" {
ami="ami-08706cb5f68222d09"
instance_type="t2.micro"
subnet_id=aws_subnet.private_subnet.id
vpc_security_group_ids=[aws_security_group.priv_sg.id]
key_name="main_key"
tags = {
Name="mysql_instance"
}
}
  • Next, create an IP and a NAT gateway.
# EIPresource "aws_eip" "nat" {
vpc=true
}
# NAT gatewayresource "aws_nat_gateway" "nat_gw" {
depends_on=[
aws_instance.mysql_instance,
]
allocation_id=aws_eip.nat.id
subnet_id=aws_subnet.public_subnet.id

tags = {
Name="nat_gw"
}
}
  • Then create a private route table and associate it with our private subnet.
# Private route tableresource "aws_route_table" "private_rt" {
depends_on=[
aws_nat_gateway.nat_gw,
]
vpc_id=aws_vpc.main_vpc.id

route {
cidr_block="0.0.0.0/0"
gateway_id=aws_nat_gateway.nat_gw.id
}
tags = {
Name = "private_rt"
}
}
# Private route table associationresource "aws_route_table_association" "priv_rt_assoc" {
depends_on = [
aws_route_table.private_rt
]
subnet_id=aws_subnet.private_subnet.id
route_table_id=aws_route_table.private_rt.id
}
  • Next, create a local-exec for writing the ssh key to a local pem file.
resource "null_resource" "write_key" {
depends_on = [
tls_private_key.main_key
]
provisioner "local-exec" {
command="echo '${tls_private_key.main_key.private_key_pem}' > main_key.pem"
}
}
  • Then we run “terraform init” command.
  • Then we run the “terraform plan” command.
  • Then run “terraform apply -auto-approve” command. This command may take a few minutes to run.
  • We can see our application and resources utilized.
  • Run “chmod 600 main_key.pem” to change the permission of the ssh key. Follow it by SCP-ing the key into the bastion instance. Then ssh into the bastion instance and follow the steps shown to connect to the MySQL instance (if you are unable to find the IP of MySQL instance consider using a tool like Nmap). We can see that this lets us access our MySQL instance. Now ping google.com to check if the internet is working. Type exit to end ssh session (We must do it twice as we are in a nested ssh session).
  • Now type “terraform destroy -auto-approve” to destroy our infrastructure.

Thank you for reading this article.

--

--