In this three-part series, we will walk through provisioning an OpenVPN endpoint on AWS using CloudFormation to automate the deployment. Our use case will be to configure OpenVPN as a personal internet proxy – with modification, other possibilities include bastion host, VPC peering, and home network extension.
A quick note before we begin – the resources we create in this tutorial may incur charges on your account if you’re not eligible for the free tier of AWS service. In this first guide, the resources will be minimal and only cost a few cents. However, if you’re following along, be sure to delete your stack when you’re finished. Otherwise, you may be charged.
What is CloudFormation?
CloudFormation is an Amazon Web Service that allows you to write code that describes your infrastructure. This is an important service for large organizations or some smaller ones that rely on automation. With CloudFormation, you don’t need to create and configure your infrastructure through the AWS Web Console, which can take hours even if you know exactly what to do. Instead, you can describe the system using YAML (or JSON), upload it to CloudFormation, and within minutes have the infrastructure necessary to run your services. That’s what we’ll be doing in the next few sections.
The Tools We’ll Need
You can use the tools of your choice to edit CloudFormation templates. This is what I use:
- Atom text editor
- Atom plugin – YAML linter
- Features integrated YAML linting with support for the AWS CloudFormation tag expansions.
All you really need is a text editor. If you have one that you’re comfortable with already, it should do the job.
What We’ll Create With CloudFormation
In the first section, we will walk through the creation of the base AWS environment for our OpenVPN endpoint using CloudFormation. This will be a simple VPC having a single subnet with a route to the internet and could be used as a basis for other projects.
Let’s take a look at what the base template looks like. This template contains the AWS resources we need to provision for our OpenVPN server, but none of the OpenVPN configuration (we’ll cover that in the next section).
To get started, copy our base template and paste it into your text editor.
What’s in Our CloudFormation Template
If you’re not familiar with CloudFormation templates, they can be a bit overwhelming. Although each piece is meant to have a descriptive, clear name, there’s a lot of information to digest. Let’s break this template down into its components so we know exactly what we’ll be creating.
- OpenVPNPort – The UDP port number to accept with our Security Group
- SSHKeyName – This is the Key Pair Name string you want to be associated with the EC2 instance. You can use an existing key pair, or create a new one in the EC2 Web Console. If you’re creating a new key pair, you’ll need to do so before it can be used in your template.
- ClientIPCIDR – Clients IP range which will be allowed by the Security Group
- RegionMap – The AMI ID differs per Region, despite the AMIs being copies of the same source image. We will be using a static mapping of AMI IDs that are keyed by region. When the template runs we will use the mapped value matching the AWS region which the template is being run within.
- It is also possible to dynamically query AWS for an AMI ID using Lambda and return the value to the CloudFormation stack via a custom resource. For our use case, this doesn’t bring any value and would add considerable complexity, so we will be using the static map.
- myVPC – Our VPC where most of our resources will be provisioned.
- It contains a CidrBlock property, which sets the addressing. We only need 1 IP address for our OpenVPN server, I just like even numbers and 8-bit subnets.
- MyPublicSubnet – The only subnet we will create within our VPC. Our OpenVPN server will be provisioned within. This subnet will be assigned a default route out to the internet, hence the name.
- This also contains a CidrBlock property that specifies an 8-bit subnet, which provides 256 addresses (251 of which are usable).
- myInternetGateway – We will need our VPC to have access to the internet.
- myRouteTablePublic – The VPC route table we’ll use.
- AttachInternetGateway – Attaches the Internet Gateway to myVPC.
- RouteDefaultPublic – Adds a default route to our VPCs internet gateway. Any packets with a destination IP not falling within the VPC range will be sent to the internet gateway via this route.
- MyPublicSubnetRouteTableAssociation – Associates our route table to our subnet.
- myEIP – Requests a new Elastic IP Address.
- AssociateManagementAccessPort – Binds our Elastic IP Address to an Elastic Network Interface.
- OpenVPNInstanceSG – Creates a security group for the ENI that will be attached to our OpenVPN server.
- This also provides OpenVPN and SSH port access
- myEC2InstanceRole – This is the IAM role which will be associated with our EC2 instance.
- myAccessPolicy – This is the IAM policy that will be attached to our EC2 instance role. The policy grants full access to the S3 bucket created by this stack.
- ec2InstanceProfile – Binding profile for our myEC2InstanceRole to the actual EC2 instance.
- myNetworkInterface – The Elastic Network Interface that will be attached to our EC2 instance. Our Security Group, OpenVPNInstanceSG is also associated with this interface.
- myS3Bucket – This is the S3 bucket where our client profile and secrets will be stored.
- EC2OpenVPNInstance – The EC2 instance that will host OpenVPN. To determine the AMI image to use, the Intrinsic function !FindInMap gets the value of the key matching the AWS::Region pseudo parameter (returns the region the template is being run within). myNetworkInterface and ec2InstanceProfile are associated with the instance. The SSH key ID specified in the parameter SSHKeyName is associated with the instance as well.
Using Our CloudFormation Template
So we’ve got our CloudFormation template in a text editor. Now what? CloudFormation templates need to be uploaded to AWS and run to create a stack, a collection of resources and their configurations. In this section, we’ll do that and then test to make sure that everything is working as expected.
Run the CloudFormation Template
Log in to the AWS Console and select CloudFormation from the list of services. Click Create Stack. Choose Upload a template to Amazon S3. Then, browse to select the local file on your computer and click Next. Use the default values for ClientIPCIDR and OpenVPNPort. Next, select an SSH key pair from the dropdown to associate with the EC2 instance. Click Next and then Create.
SSH into the EC2 Instance
Wait for the stack build to complete (this could take a few minutes). Copy the output values for myS3BucketOut and myEIPOut in the CloudFormation Outputs tab at the bottom of the page. SSH into the instance, replacing the IP address and yourKeyName with your own values:
ssh -i "yourKeyName.pem" firstname.lastname@example.org
Run the command to SSH into the instance. Be sure you run it in the same directory that you have the matching key, or provide a full absolute or relative path to your key file.
NOTE: If you’re using a newly created key for this EC2 instance, you may receive a “Permission Denied” message the first time you try to use it. You can avoid this by changing your key permissions:
sudo chmod 400 yourKeyName.pem
Test S3 Access from EC2
Next, we’ll make sure that our EC2 instance has the proper permissions to create objects in our S3 bucket. Create a new file:
echo "testamundo" > test.txt
Upload the file to S3, replacing the bucket name with your bucket name (found in the CloudFormation Outputs tab):
aws s3 cp testamundo.txt s3://test3-mys3bucket-1num7ykizegrg
List the contents of the S3 bucket, replacing the bucket name with your bucket name:
aws s3 ls s3://test3-mys3bucket-1num7ykizegrg
Test Internet Access from EC2
Finally, we want to make sure our networking is properly configured so that our EC2 instance has a route to and from the Internet. Use the following command to do so:
sudo yum update -y
That’s all for now! We’ve reached the end of part one. So far, we have created a template that sets up a new VPC with a single subnet, with a default route out to the internet. We connected to our EC2 instance to confirm that it has access to the Internet and to our created S3 bucket.
In part two, we’ll extend the CloudFormation template to create a functional OpenVPN server that we can use to keep connections secure, for example, on public WiFi networks.
If you’re completely new to CloudFormation, we may have used some unfamiliar terms and language in our syntax. You can read more in the following links.
Resource Type Reference
You can find detail on all of the resource type definitions used within our template here.
We use pseudo parameters with the Ref function. These are predefined by CloudFormation and used to refer to certain parts of the stack currently being created. You can find more information here.
YouTube Video Walkthrough
I have also created a corresponding video walkthrough for this section. If you would like an added level of detail you can follow along with me using the link below. If you found this content helpful, please subscribe to my channel:
How to roll your own VPN with AWS CloudFormation – Part 1 of 3
You can also connect with me on Twitter and LinkedIn or follow my blog where I share a few posts like this each month.