Terraform: AWS VPC

VPC. Stands for Virtual Private Cloud. It allows one to create their own private network within AWS cloud platform.

Terraform is an automation tool used to provision infrastructure/resources on our cloud environments. It is one of the IaC (Infrastructure as Code) tools. The others include:

  • Ansible
  • CloudFormation (AWS proprietary).

This article guides the user through creating a Virtual Private Network on the AWS Cloud Platform. It leverages on the use of Terraform to automate the resources provisioning.

We will create:

  • A VPC
  • Public and Private Subnets
  • Internet Gateway
  • NAT Gateways
  • Public and Private Route Tables
  • Route Table Associations
  • Security Group

Requirements/Prerequisites

  • An AWS account
  • A user that has generated his/her AWS access key ID and secret access key and permissions to create AWS resources.
  • 2 allocated Elastic IP’s for our NAT Gateways
  • Have Terraform installed on your respective PC.

Step 1: Create Terraform VPC Module

Assuming you have installed Terraform on your respective PC.

Create a folder on your PC, and within that folder create 4 files. N/B: they can customize The folder name to a user’s specific requirement.

  • provider.tf
  • vars.tf
  • version.tf
  • vpc.tf

To create the folder run the below commands on your PC terminal.

sudo mkdir modules
cd mkdir modules
sudo mkdir services
cd services
sudo mkdir vpc
cd vpc
touch vpc.tf outputs.tf  

On, the vpc.tf file add the below code:

#The VPC
resource "aws_vpc" "test-vpc" {
   cidr_block = "192.168.0.0/16"
   instance_tenancy = "default"
   enable_dns_support = "true"
   enable_dns_hostnames = "true"
   tags = {
     Name = "test-vpc"
     createdBy = "MaureenBarasa"
     Owner = "DevSecOps"
     Project = "test-terraform"
     environment = "test"
   }
}

#The Subnets
resource "aws_subnet" "test-public-subnet-1" {
   vpc_id = "${aws_vpc.test-vpc.id}"
   cidr_block = "192.168.0.0/28"
   map_public_ip_on_launch = "true"
   availability_zone = "eu-central-1a"
   tags = {
     Name = "test-public-subnet-1"
     createdBy = "MaureenBarasa"
     Owner = "DevSecOps"
     Project = "test-terraform"
     environment = "test"
   }
}
resource "aws_subnet" "test-public-subnet-2" {
   vpc_id = "${aws_vpc.test-vpc.id}"
   cidr_block = "192.168.0.16/28"
   map_public_ip_on_launch = "true"
   availability_zone = "eu-central-1b"
   tags = {
     Name = "test-public-subnet-2"
     createdBy = "MaureenBarasa"
     Owner = "DevSecOps"
     Project = "test-terraform"
     environment = "test"
   }
}
resource "aws_subnet" "test-private-subnet-1" {
   vpc_id = "${aws_vpc.test-vpc.id}"
   cidr_block = "192.168.1.0/24"
   map_public_ip_on_launch = "false"
   availability_zone = "eu-central-1a"
   tags = {
     Name = "test-private-subnet-1"
     createdBy = "MaureenBarasa"
     Owner = "DevSecOps"
     Project = "test-terraform"
     environment = "test"
   }
}
resource "aws_subnet" "test-private-subnet-2" {
   vpc_id = "${aws_vpc.test-vpc.id}"
   cidr_block = "192.168.2.0/24"
   map_public_ip_on_launch = "false"
   availability_zone = "eu-central-1b"
   tags = {
     Name = "test-private-subnet-2"
     createdBy = "MaureenBarasa"
     Owner = "DevSecOps"
     Project = "test-terraform"
     environment = "test"
   }
}
resource "aws_subnet" "test-private-subnet-3" {
   vpc_id = "${aws_vpc.test-vpc.id}"
   cidr_block = "192.168.3.0/24"
   map_public_ip_on_launch = "false"
   availability_zone = "eu-central-1a"
   tags = {
     Name = "test-private-subnet-3"
     createdBy = "MaureenBarasa"
     Owner = "DevSecOps"
     Project = "test-terraform"
     environment = "test"
   }
}
resource "aws_subnet" "test-private-subnet-4" {
   vpc_id = "${aws_vpc.test-vpc.id}"
   cidr_block = "192.168.4.0/24"
   map_public_ip_on_launch = "false"
   availability_zone = "eu-central-1b"
   tags = {
     Name = "test-private-subnet-4"
     createdBy = "MaureenBarasa"
     Owner = "DevSecOps"
     Project = "test-terraform"
     environment = "test"
   }
}
#The Internet Gateway
resource "aws_internet_gateway" "test-igw" {
   vpc_id = "${aws_vpc.test-vpc.id}"
   tags = {
     Name = "test-igw"
     createdBy = "MaureenBarasa"
     Owner = "DevSecOps"
     Project = "test-terraform"
     environment = "test"
   }
}

resource "aws_nat_gateway" "test-nat-gw1" {
   allocation_id = "eipalloc-0366e0ada668ed06c"
   subnet_id = "${aws_subnet.test-public-subnet-1.id}"
   depends_on = ["aws_internet_gateway.test-igw"]
}

resource "aws_nat_gateway" "test-nat-gw2" {
   allocation_id = "eipalloc-0fb4c214424169528"
   subnet_id = "${aws_subnet.test-public-subnet-2.id}"
   depends_on = ["aws_internet_gateway.test-igw"]
}

#The Public Route Table
resource "aws_route_table" "test-public-rtb" {
   vpc_id = "${aws_vpc.test-vpc.id}"
   route {
     cidr_block = "0.0.0.0/0"
     gateway_id = "${aws_internet_gateway.test-igw.id}"
   }
   tags = {
     Name = "test-public-rtb"
     createdBy = "MaureenBarasa"
     Owner = "DevSecOps"
     Project = "test-terraform"
     environment = "test"
   }
}

# The Public Route Table Associations
resource "aws_route_table_association" "test-public-1a" {
   subnet_id = "${aws_subnet.test-public-subnet-1.id}"
   route_table_id = "${aws_route_table.test-public-rtb.id}"
}
resource "aws_route_table_association" "test-public-1b" {
   subnet_id = "${aws_subnet.test-public-subnet-2.id}"
   route_table_id = "${aws_route_table.test-public-rtb.id}"
}

#The first Private Route Tables
resource "aws_route_table" "test-private1-rtb" {
   vpc_id = "${aws_vpc.test-vpc.id}"
   route {
     cidr_block = "0.0.0.0/0"
     gateway_id = "${aws_nat_gateway.test-nat-gw1.id}"
   }
   tags = {
     Name = "test-private1-rtb"
     createdBy = "MaureenBarasa"
     Owner = "DevSecOps"
     Project = "test-terraform"
     costCenter = "91204"
     environment = "test"
   }

}

# The first Private Route Table Associations
resource "aws_route_table_association" "test-private1-1a" {
   subnet_id = "${aws_subnet.test-private-subnet-1.id}"
   route_table_id = "${aws_route_table.test-private1-rtb.id}"
}

resource "aws_route_table_association" "test-private3-1a" {
   subnet_id = "${aws_subnet.test-private-subnet-3.id}"
   route_table_id = "${aws_route_table.test-private1-rtb.id}"
}

#The Second Private Route Table 
resource "aws_route_table" "test-private2-rtb" {
   vpc_id = "${aws_vpc.test-vpc.id}"
   route {
     cidr_block = "0.0.0.0/0"
     gateway_id = "${aws_nat_gateway.test-nat-gw2.id}"
   }
   tags = {
     Name = "test-private2-rtb"
     createdBy = "MaureenBarasa"
     Owner = "DevSecOps"
     Project = "test-terraform"
     environment = "test"
   }

}

# The Second Private Route Table Associations
resource "aws_route_table_association" "test-private1-1b" {
   subnet_id = "${aws_subnet.test-private-subnet-2.id}"
   route_table_id = "${aws_route_table.test-private2-rtb.id}"
}
resource "aws_route_table_association" "test-private2-1b" {
   subnet_id = "${aws_subnet.test-private-subnet-4.id}"
   route_table_id = "${aws_route_table.test-private2-rtb.id}"
}

#The Security Group
resource "aws_security_group" "test-ec2-sg" {
  name        = "test-ec2-sg"
  description = "Allow TLS inbound traffic"
  vpc_id = "${aws_vpc.test-vpc.id}"

  ingress {
    description = "ssh"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["42.54.23.12/32"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
     Name = "test-ec2-sg"
     createdBy = "MaureenBarasa"
     Owner = "DevSecOps"
     Project = "test-terraform"
     environment = "test"
   }
}

The file defines all the resources you need to provision with Terraform.

The user will have to go through the above template and customize the template to their specific requirements. Important aspects to customize will be:

  • The CIDR Blocks for the VPC and Subnets.
  • The Names and Tags for all the resources to be created.
  • The Elastic IP allocation ID’s for the NAT Gateway creation (Use the allocation ID’s for your accounts elastic IP’s).

Add the below code on your outputs.tf file. The file defines the output variables. Defining outputs is important because we can call these variables from within another terraform module using outputs.

output "vpc_public_subnet1" {
  description = "IDs of the VPC's public subnet"
  value       = "${aws_subnet.test-public-subnet-1.id}"
}

output "vpc_public_subnet2" {
  description = "IDs of the VPC's public subnet"
  value       = "${aws_subnet.test-public-subnet-2.id}"
}

output "vpc_private_subnet1" {
  description = "IDs of the VPC's private subnet"
  value       = "${aws_subnet.test-private-subnet-1.id}"
}

output "vpc_private_subnet2" {
  description = "IDs of the VPC's private subnet
  value       = "${aws_subnet.test-private-subnet-2.id}"
}

output "vpc_security_group_id" {
  description = "IDs of the VPC's security groups"
  value       = "${aws_security_group.test-ec2-sg.id}"
}

Step 2: Create the Terraform Root Module

Create another folder called Terraform-vpc and within that folder add 3 files.

  • main.tf
  • version.tf
  • vars.tf

On the vars.tf file add the below code. The file contains variables that describe the users AWS credentials, ec2 key-pair name and the region that the user would like to launch his resources on.

variable "AWS_ACCESS_KEY" {
  default = "**************"
}

variable "AWS_SECRET_KEY" {
  default = "***************"
}

variable "AWS_REGION" {
  default = "eu-central-1"
}

variable "key_name" {
  default = "test-key"
}

Replace the AWS access and secret key with the user generated AWS access key ID and AWS secret access key, respectively. The user should also change the region default value to the specific AWS region they would like to provision the resources.

On the version.tf file add the below code.

terraform {
  required_version = ">= 0.12" 
}

Finally, on the main.tf file, Add the below code. The main.tf file calls the terraform modules that will be invoked to create the AWS resources.

terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
    }
  }
}

provider "aws" {
  access_key = var.AWS_ACCESS_KEY
  secret_key = var.AWS_SECRET_KEY
  region = "eu-central-1"
}

module "vpc" {
  source  = "/Users/maureenbarasa/modules/services/vpc"
}

The main.tf file calls the vpc module required to provision the Virtual Private Network.

N/B: The respective module source should reflect the user’s respective module directories.

Create the Infrastructure

To create the infrastructure on your account, on your root module directory, run the below commands:

terraform init
terraform plan
terraform apply

The terraform init command initializes our working directory. We should run this command whenever we write a new terraform configuration.

The terraform plan creates a preview of the actions that terraform will perform i.e. the resources that terraform will create.

After reviewing the actions that Terraform will perform, we can now run terraform apply to create the resources. It will prompt you to approve by typing yes.

To destroy everything created with the template, run the below command. The command will preview all resources that will be destroyed. It will also prompt the user to approve by typing yes.

terraform destroy

Important Links

We can find all the Terraform code on the below GitHub Repository:

https://github.com/MaureenBarasa/IaC-Terraform.git

Other Links Include:

https://www.terraform.io/docs/commands/init.html

https://www.terraform.io/docs/commands/apply.html

Happy Building!!!

One thought on “Terraform: AWS VPC

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s