On Part One we saw how to create the base aws network stack using Terraform, on this post we are gonna deploy a Linux instance that will be used to establish Inter-Region IPSEC Tunnels using LibreSwan.
AWS Inter-Region Traffic
In November 2017 AWS Announced the Support for Inter-Region Peering, this allow VPCs in different regions to communicate between each other using AWS’s own Backbone, however, not all Regions currently support the Inter-Region Peering Feature, and to work around that the solution is the good old IPSEC VPN Tunnels.
The reason we use VPN Instances instead of the native AWS VPN Gateway is because aws-vgw works on passive mode only, so its not possible to initiate a Tunnel between 2 aws-vgws.
VPN Instance Module
We start by defining a new Module called vpn-instance, this module will hold the template to create our Linux Instance on the Public Subnet, and we will use user-data to run a bootstrap script on the Instance, so it defines a few settings and install LibreSwan on the first boot. LibreSwan is a nice and simple Linux IPSEC implementation that should do a good job to demonstrate our use-case. Lets get started.
“../modules/vpn-instance”
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#The Variables defined here reflect the Parameters that we send when calling a Module on a new Project; | |
#These variables will store those parameters and use on the resources in this Module. | |
variable "instance_name" { | |
default = "" | |
} | |
variable "ami_name" { | |
default = "" | |
} | |
variable "subnet_id" { | |
default = "" | |
} | |
variable "sg_id" { | |
default = "" | |
} | |
variable "key_name" { | |
default = "" | |
} | |
variable "type" { | |
default = "" | |
} | |
#Here we define a template-file that cloud-init will run on the instance when we Bootstrap it. | |
data "template_file" "script" { | |
template = "${file("init_config.sh")}" | |
} | |
resource "aws_instance" "instance" { | |
ami = "${var.ami_name}" | |
instance_type = "${var.type}" | |
subnet_id = "${var.subnet_id}" | |
key_name = "${var.key_name}" | |
associate_public_ip_address = "true" | |
vpc_security_group_ids = ["${var.sg_id}"] | |
source_dest_check = false | |
tags { | |
Name = "${var.instance_name}" | |
} | |
user_data = "${data.template_file.script.rendered}" | |
} | |
#We assigned an EIP so if the Instance is re-created it will keep the same Public IP Address. | |
resource "aws_eip" "main" { | |
vpc = true | |
depends_on = ["aws_instance.instance"] | |
} | |
#Here we asociate the EIP to the Instance that we are creating. | |
resource "aws_eip_association" "main" { | |
instance_id = "${aws_instance.instance.id}" | |
allocation_id = "${aws_eip.main.id}" | |
} |
We can then create our new project and make use of our previous created network-stack and the vpn-instance Modules.
“../projects/eu-central-1/main.tf”
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
provider "aws" { | |
region = "eu-central-1" | |
} | |
#Creates the SSH Key-Pair that will be used to create Instances. Create a ssh-key pair on your laptop and store it on the local | |
#Folder, the resource below will use it to create the ec2 instance. | |
resource "aws_key_pair" "setup_key" { | |
key_name = "lab-keys" | |
public_key = "${file("./ssh-key.pub")}" | |
} | |
#Create the VPC and the Base Networking Stack | |
module "network-stack" { | |
#configuration parameters | |
source = "../../modules/network-stack" | |
vpc_cidr = "10.240.0.0/24" | |
vpc_name = "netoops-lab" | |
subnet-public-a = "10.240.0.0/26" | |
subnet-public-b = "10.240.0.64/26" | |
subnet-private-a = "10.240.0.128/26" | |
subnet-private-b = "10.240.0.192/26" | |
} | |
module "vpn-instance" { | |
#configuration parameters | |
source = "../../modules/vpn-instance" | |
instance_name = "vpn01a" | |
#ami can be obtained on AWS MarkePlace, we are using CentOS7. | |
ami_name = "ami-337be65c" | |
type = "c3.large" | |
#These Values come from the Module network-stack, when defining Output Variables. | |
subnet_id = "${module.network-stack.subnet_pub_a_id}" | |
sg_id = "${module.network-stack.sg_id}" | |
key_name = "${aws_key_pair.setup_key.key_name}" | |
} |
Now we need to define our user-data script, I call it init_config.sh, and we will define what we want to run on our Instance at Lunch time.
“../projects/eu-central-1/init_config.sh”
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
echo "Debug file…" | |
cat >/var/tmp/something_happened.txt | |
echo "Upodating and Installing libreswan…" | |
sudo yum update | |
sudo yum -y install libreswan | |
echo "Enabling IPSEC…" | |
sudo systemctl start ipsec | |
sudo systemctl enable ipsec | |
echo "Adjusting Sysctl…" | |
cat << EOF > /etc/sysctl.d/vpn.conf | |
# Disable Source verification and send redirects to avoid Bogus traffic within OpenSWAN | |
net.ipv4.conf.all.rp_filter=0 | |
net.ipv4.conf.default.rp_filter=0 | |
net.ipv4.conf.eth0.rp_filter=0 | |
net.ipv4.conf.lo.rp_filter=0 | |
net.ipv4.conf.all.send_redirects=0 | |
net.ipv4.conf.default.send_redirects=0 | |
net.ipv4.conf.eth0.send_redirects=0 | |
net.ipv4.conf.lo.send_redirects=0 | |
net.ipv4.conf.all.accept_redirects=0 | |
net.ipv4.conf.default.accept_redirects=0 | |
net.ipv4.conf.eth0.accept_redirects=0 | |
net.ipv4.conf.lo.accept_redirects=0 | |
# Enable IP forwarding in order to route traffic through the instances | |
net.ipv4.ip_forward=1 | |
# Set thresholds for when to have gc aggressively clean up arp table | |
net.ipv4.neigh.default.gc_thresh1 = 2048 | |
net.ipv4.neigh.default.gc_thresh2 = 4096 | |
net.ipv4.neigh.default.gc_thresh3 = 8192 | |
# Adjust to arp table gc to clean-up more often | |
net.ipv4.neigh.default.gc_interval = 30 | |
EOF | |
sudo systemctl restart systemd-sysctl |
Done. All wee need to do now is run terraform init / terraform get / terraform apply, wait a few minutes and our eu-central-1 vpc will be up and running with a Linux Instance Ready to be used as a VPN Endpoint.
Now we go back to our eu-west-1 project, and all we need to do is add the module vpn-instance snippet on the main.tf, terraform get / terraform apply and it should deploy the instance also on eu-west-1. We only need to change the ami-id for one that is available in euw1.
“../projects/eu-west-1/main.tf”
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
provider "aws" { | |
region = "eu-west-1" | |
} | |
resource "aws_key_pair" "setup_key" { | |
key_name = "lab-keys" | |
public_key = "${file("./ssh-key.pub")}" | |
} | |
module "network-stack" { | |
#configuration parameters | |
source = "../../modules/network-stack" | |
vpc_cidr = "10.250.0.0/24" | |
vpc_name = "netoops-lab" | |
subnet-public-a = "10.250.0.0/26" | |
subnet-public-b = "10.250.0.64/26" | |
subnet-private-a = "10.250.0.128/26" | |
subnet-private-b = "10.250.0.192/26" | |
} | |
module "vpn-instance" { | |
#configuration parameters | |
source = "../../modules/vpn-instance" | |
instance_name = "vpn01a" | |
#ami can be obtained on AWS MarkePlace, we are using CentOS7. | |
ami_name = "ami-6e28b517" | |
type = "c3.large" | |
#These Values come from the Module network-stack, when defining Output Variables. | |
subnet_id = "${module.network-stack.subnet_pub_a_id}" | |
sg_id = "${module.network-stack.sg_id}" | |
key_name = "${aws_key_pair.setup_key.key_name}" | |
} |
Note that only 3 Resources were added? In the previous post we had already deployed all the network-stack, now we are only adding the Resources defined on the module vpn-instance.
Terraform and Configuration Management
Terraform is a great tool for creating Infrastructure, but it is not a configuration management tool. If we go back to our user-data file and try to add or remove something, Terraform will detect the change and when you apply it the instance will be destroyed and re-created. We don’t want that every time we add a new VPN Peer, so we should use a ConfigManager such as Ansible to define our LibreSwan Configurations.
I haven’t played much with Ansible yet, but as the main goal of this blog is to help-me learn and document what I am learning, on the Next Blog post I should have an Ansible environment ready to deploy those VPN Configs, and use Terraform to define and control the AWS Route Tables.
The complete Lab can be found on my GitHub.
That’s all folks.
1 Comment