In the first part in our series on using AWS CloudFormation, we created a base template that created a VPC with a single subnet, and an EC2 instance inside of it.
In part two, we will walk through the creation of the installation and configuration of easy-rsa and OpenVPN. The end result will be a functional OpenVPN server that can connect to any OpenVPN client after downloading the ovpn profile from the generated S3 bucket.
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. These resources should be minimal, but if you’re following along, be sure to delete your stack when you’re finished, otherwise, you may be charged.
Before we start, let’s discuss OpenVPN and how you might use this project in the real world.
What Can OpenVPN Be Used For?
You’re probably wondering why we’re choosing OpenVPN as the topic for our project. Multiple use cases are possible, but let’s keep things simple and focus on just one, the personal internet proxy.
A personal internet proxy is a useful tool when it comes to privacy because it gives you added control and protection on open and untrusted networks. For example, you could use OpenVPN on your mobile device or laptop on any public Wi-Fi network, assuming that network does not filter your outbound VPN. When you’re connected to your VPN, the network traffic between your client device and the OpenVPN server is encrypted. Your request is then proxied via the server to the final destination on the internet.
Personally, I use this VPN solution fairly regularly. Because it can be auto-deployed and destroyed, it’s easy to only pay for the resources when you actually need them (though, it does take a few minutes to provision). Also, you should be aware of the AWS data transfer out costs for your region if you are going to be moving large amounts of data via upload or download, especially for streaming music or video.
Let’s talk a bit more about some advantages to rolling your own AWS VPN server.
Secure Your Local Area Communication
Consider the threats you face at the local area network (LAN) level. First is Wi-Fi monitor mode. Any device running Linux on the local network, combined with a wireless network adapter that supports monitor mode, can be configured to collect all Wi-Fi traffic in range. If you’re connected to a secured Wi-Fi access point (AP), the communication between your client and the AP is already encrypted. However, if you are connected to on an open AP, your traffic is clear and can be inspected by anyone.
The Wi-Fi AP itself, or device behind it, could log your traffic. There is also application-level security (HTTPS, for example), which will encrypt your traffic on a service-by-service basis, assuming it is supported by the service you’re using. Keep in mind that despite the application-level encryption, all of your DNS queries will be in the clear unless you’re using a secure DNS service. This leads us to our second point…
You Have Full Control over Your DNS
Your client requests will be using the DNS servers you’ve configured on your OpenVPN server (18.104.22.168, 22.214.171.124, or others of your choosing), rather than the DNS forced upon you by the local network.
IP Address-Based Blacklisting Bypass
If someone abuses a site or service, sometimes the IP addresses for that network will be blacklisted or blocked by that service. When you’re using a shared network, you might find yourself blocked from something you need to access based on the actions of another individual, unrelated to you. The remote service will see your IP address as the EIP assigned to the AWS instance, which you can re-generate on-demand. This can be a handy workaround.
IP Address-Based Geolocation
Some web services provide different user experiences and content based on the user’s geographical location. You can deploy the template into any of the 14 AWS regions, and while not always consistent, you may get an IP address to which a geo-IP dataset will resolve that geo you deployed into. If the client device does not support or offer GPS, the service might attempt to estimate your location based on your IP address via a dataset. Be aware that to better enforce geo-restriction of their content, some services may block your request to get content if that request originates from any known AWS IP.
What’s in Our CloudFormation Template
To follow along, you’ll need to use our extended template. Copy the code and paste it into a text editor of your choosing (we’ll explain further down what it does).
This template extends the base template we worked through in the first part of the series. In this iteration, we’ll be adding code to install and configure OpenVPN, as well as generate an S3 bucket to store its client profile.
Let’s review what sections have been added to our base template.
UserData – User data is passed into the instance, executed as a shell script, and run only once on first boot. Cfn-init is used to handle our instance configuration, which we invoke on our configSet myCfnConfigSet. After instance configuration has completed, we emit a signal to the CloudFormation stack via cfn-signal. By doing this, the CloudFormation stack will not be in a CREATION_COMPLETE state until our OpenVPN service has been fully configured and is actually ready for us to use.
CreationPolicy – Places a constraint on the EC2OpenVPNInstance resource; it will remain in a pending create state until either of its parameter conditions are satisfied. We have specified the policy to wait for 10 minutes, or until the instance sends us a signal, whichever occurs first.
Cfn-init will walk through the steps in our configSet myCfnConfigSet. You can think of the configSet as a list of actions that will be taken on the server as part of its configuration. If you have ever done Linux administration work, some of the code in the configSet will look familiar (it’s okay if you haven’t – we’ll explain it as we go). I have divided the configuration into logical sections to make it easier to read and understand.
configure_cfn – Generates configuration for cfn-hup to watch for stack updates and apply them in place to our instance. While not technically required for our template, this can be a useful feature to leverage as it speeds up the testing and debugging process.
install_software – Installs the latest version of openvpn via the yum package manager. Next, install easy-rsa using the EPEL repo which needs to be enabled via a command. Finally, copy the installed easy-rsa files into a new location which we will use as our working directory.
generate_secrets – First, we use easy-rsa to generate our certificate authority (CA) and encryption keys. I couldn’t figure out a better way to handle sourcing files with cfn-init, so I am just doing it inline with each command. Easy-rsa provides us with handy scripts to use, however we need to modify them with sed to remove the –interactive flag used on the underlying commands. Finally, we use openvpn to generate a static pre-shared TLS key which the client will use to authenticate with the openvpn server.
generate_client – Generates the openvpn client configuration file. Here, we dynamically include the elastic IP of the OpenVPN server and our parameter specified port. We specify that the client computer should use the VPN tunnel as its default route for all traffic. Also of interest is our inclusion of the certificate, encryption key, and TLS authentication key, which we expect to be in the same directory. Next, we generate a shell script that will concatenate the client configuration with the cert and encryption key to generate the ovpn profile. Finally, we generate the client encryption key using easy-rsa and run our profile script to create the complete ovpn file for clients to load.
configure_server – Generates the configuration file for our OpenVPN server, of particular interest are the paths to files in our easy-rsa working directory and our parameter configured port. We then enable ipforwarding in Linux, add a route from our VPN tunnel to our public-facing interface eth0, and start the OpenVPN server.
upload_files – Creates a zip archives of the client files and upload them to our generated S3 bucket. Also, upload the cfn-init log file to S3 which gives us a nice log of everything which was done in this section.
Using Our Updated CloudFormation Template
In this section, we’ll get to the fun part – running our template and testing to make sure it works. To begin, run the template by uploading it to CloudFormation. This should take several minutes. We explained how to run a template in the first part of this series, and the process is the same here.
Once the stack has reached the CREATE_COMPLETE state, download and read through the cfn-init log file in your S3 bucket. The log file contains the output of all of the commands executed by cfn-init. You don’t need to take any actions here, just be aware of this log as it will be helpful for debugging purposes if you want to make any changes to the template in the future.
Next, we’ll be explaining how to connect with an OpenVPN client device. An explanation of how to install and configure a client is beyond the scope of this guide, but if you’re not familiar with the process, you can find more information here.
Download openVPNClientFiles.zip from your S3 bucket and unzip the archive. Load the profile into your OpenVPN client. Connect with your OpenVPN client and then, from a web browser, try to hit an IP echo service site (such as whatsmyipaddress.org). Your public IP address should match your instance’s assigned EIP.
That’s the end of part two! So far, we have created a template that configures an OpenVPN server we can connect to with any OpenVPN client. There is still one problem remaining – if you try to delete this CloudFormation stack, the deletion process will fail. This will happen because CloudFormation will not delete an S3 bucket that contains objects; a non-empty S3 bucket must be persisted using a deletion policy that allows the S3 bucket and contents to persist after deleting the stack.
However, we do not want this to happen. The S3 bucket contains our OpenVPN client configuration, which has no value to us after the stack has been deleted. We just want the bucket to be deleted along with the other AWS resources. We can accomplish this behavior using a custom resource and a Lambda function, which we will detail in the third and final part of our series. Stay tuned!
Throughout this guide, we assumed you had some basic knowledge about OpenVPN and how to set it up. If you haven’t used it before – don’t worry!
Here are some sample configuration files that will, hopefully, help you understand the software a little better: