Deploying EC2 with Private and Public Subnet Using Terraform in AWS
Terraform always the simple and easy way for us to deploying our infrastructure over the cloud, in this post, I will deploying 2 EC2 in the public and private subnet and try to access into the private EC2 using the public bastion host.
A bastion host is a special-purpose computer on a network specifically designed and configured to withstand attacks. The computer generally hosts a single application, for example a proxy server, and all other services are removed or limited to reduce the threat to the computer.
This is recommended best practices from AWS as we place our production server in the private subnet without any public accessible access, the only way to access the server is using the bastion host.
In this post, I will create the following infrastructure in my AWS account, than try to access the private EC2 over the jump host on the following post.
- 1 VPC with the CIDR 10.0.0.0/16
- 1 Internet gateway for the public subnet
- 1 Public subnet with the CIDR 10.0.0.0/24
- 1 Public route table to route all traffic to IGW
- 1 Security group enabled port 22 to the EC2 in public subnet
- 1 Private subnet with the CIDR 10.0.1.0/24
- 1 Private route table to route only the local traffic
- 1 Security group enabled all port from local CIDR 10.0.0.0/16 to the EC2 in private subnet
I will create 5 files in order to group all related resources in the same file:
main.tf
provider "aws" {
region = "ap-southeast-1" access_key = "xxxxxxxxxxxxxxxx"
secret_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
I will deploy my resources in the Singapore region, so just set the region in ap-souteeast-1. For the access key and secret key, if you already install and configure your cli credential, basically you may just ignore it, it will get the default setting from your computer
vpc.tf
resource "aws_vpc" "demovpc" {
cidr_block = "10.0.0.0/16"
instance_tenancy = "default"
enable_dns_support = "true"
enable_dns_hostnames = "true"tags = {
Name = "demovpc"
}
}resource "aws_security_group" "allow_ssh" {
name = "allow_ssh"
description = "Allow SSH inbound traffic / Allow all outbound traffic"
ingress {
from_port = 22
to_port = 22
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"]
}vpc_id = aws_vpc.demovpc.id
tags = {
Name = "demovpc_allow_ssh"
}depends_on = [aws_vpc.demovpc]
}resource "aws_security_group" "allow_local" {
name = "allow_local"
description = "Allow all traffic from local vpc"
ingress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["10.0.0.0/16"]
}egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}vpc_id = aws_vpc.demovpc.id
tags = {
Name = "demovpc_allow_all_from_local"
}depends_on = [aws_vpc.demovpc]
}resource "aws_internet_gateway" "IGW_TF" {
vpc_id = aws_vpc.demovpc.id
tags = {
Name = "IGW_TF"
}depends_on = [aws_vpc.demovpc]
}
In the vpc.tf, I created 1 VPC with the logical name is demovpc, and the VPC name in AWS also call demovpc.
1 Internet gateway which will later associate to the public subnet for the incoming and out going traffic from the public internt
2 Security groups being created and 1 is for the public EC2, which allow the port 22 from all IP, another 1 is for the private EC2, which only allow all port access from local CIDR (10.0.0.0/16)
subnet.tf
resource "aws_subnet" "public_subnet" {
availability_zone = "ap-southeast-1a"
cidr_block = "10.0.0.0/24"
map_public_ip_on_launch = "true"
vpc_id = aws_vpc.demovpc.idtags = {
Name = "demo_public_subnet"
}depends_on = [aws_vpc.demovpc]
}resource "aws_subnet" "private_subnet" {
availability_zone = "ap-southeast-1a"
cidr_block = "10.0.1.0/24"
vpc_id = aws_vpc.demovpc.id
tags = {
Name = "demo_private_subnet"
}depends_on = [aws_vpc.demovpc]
}
2 subnet being deployed, 1 is public subnet and 1 is private subnet. For the public subnet I set the map_public_ip_on_launch = “true”which mean every EC2 launch in this subnet will be auto assign the public IP.
Both of the subnet will only be create after the VPC is completely created using the syntax of depends_on=[aws_vpc.demovpc].
The public subnet being assign the CIDR of 10.0.0.0/24 and private subnet is 10.0.1.0/24.
routetable.tf
resource "aws_route_table" "PublicRouteTable" {
vpc_id = aws_vpc.demovpc.id tags = {
Name = "PublicRouteTable"
}
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.IGW_TF.id
} depends_on = [aws_vpc.demovpc, aws_internet_gateway.IGW_TF]
}
resource "aws_route_table" "PrivateRouteTable" {
vpc_id = aws_vpc.demovpc.id
tags = {
Name = "PrivateRouteTable"
}
depends_on = [aws_vpc.demovpc]
}
resource "aws_route_table_association" "PublicRouteTableAssociate" {
subnet_id = aws_subnet.public_subnet.id
route_table_id = aws_route_table.PublicRouteTable.id
depends_on = [aws_subnet.public_subnet, aws_route_table.PublicRouteTable]
}resource "aws_route_table_association" "PrivateRouteTableAssociate" {
subnet_id = aws_subnet.private_subnet.id
route_table_id = aws_route_table.PrivateRouteTable.id
depends_on = [aws_subnet.private_subnet, aws_route_table.PrivateRouteTable]
}
I will create 2 route table in this file and associate the route table to the respective subnet
ec2.tf
resource "aws_instance" "PublicEC2" {
ami = "ami-0f02b24005e4aec36"
availability_zone = "ap-southeast-1a"
instance_type = "t2.nano"
key_name = "keyname"tags = {
Name = "PublicEC2"
}subnet_id = aws_subnet.public_subnet.id
vpc_security_group_ids = [aws_security_group.allow_ssh.id]
depends_on = [aws_vpc.demovpc, aws_subnet.public_subnet]
}resource "aws_instance" "PrivateEC2" {
ami = "ami-0f02b24005e4aec36"
availability_zone = "ap-southeast-1a"
instance_type = "t2.nano"
key_name = "keyname"tags = {
Name = "PrivateEC2"
}subnet_id = aws_subnet.private_subnet.id
vpc_security_group_ids = [aws_security_group.allow_local.id]
depends_on = [aws_vpc.demovpc, aws_subnet.private_subnet]
}
ec2.tf is where I create both of my EC2 instance and place the public EC2 into the public subnet with the security group allow port 22 from all IP.
For the private EC2, I place it directly in the private subnet which will not assign the public IP, and the security group permitted access from all port in the local CIDR of 10.0.0.0/16.
Because the AMI for EC2 will be difference for different region, it’s not recommend to hardcode it, since this is for demo purposes, I just hardcoded it to make the demo simple.
Deploying the infrastructure
$ terraform init
After the script is ready, we need to first initialise the provider and module which in our terraform project using the terraform init. The cli will download all necessary module and dependency to your local machine.
$ terraform plan
It’s always recommend to run the terraform plan to see what resources will be deploy to the cloud, this command only list all resources only, it’s not physically deploy.
$ terraform apply
terraform apply or terraform apply -auto-approvewill only deploy your infrastructure to your aws account.
After run the terraform apply, you should bet this output for 12 resources added, 0 changed and 0 destroyed.
Let’s verify all the resources in AWS console.
After check on all the VPC, Subnet, Security Group, Internet Gateway, Route Table and EC2, everything is up and running as per expected. On the next post, I will share on how to login to the private EC2 using the bastion host/jump host.
Once you finish all the testing, don’t forget to destroy all the resources just to save up your cost. To destroy the resources using terraform, just using the following commend.
$ terraform destroy --auto-approve
Originally published at https://tech.david-cheong.com on June 7, 2020.