OpenVPN on AWS in 4 clicks

Run a personal VPN server in AWS using CloudFormation and OpenVPN

Posted by Mike Apted on April 5, 2017

There's been a sudden general interest in VPNs again with the recent policy developments in the US. There are many important steps users can take to bolster their privacy and you can get a good run down of them in this EFF article.

It is worth re-stating that VPNs are not a magic bullet. They typically just shift the threat downstream. A substantial number of VPN providers are not trustworthy and likely more dangerous than your ISP. Most users will get more bang for their buck using a good ad blocker and HTTPS Everywhere.

That being said, if you don't trust your connection provider (whether if be a coffee shop WiFi or your ISP) and you trust your VPN end point more, you can benefit from using a VPN in that situation. Remember that there will also likely be a performance penalty, but you're not using this to stream Netflix right? Right?

If you are an AWS user and you want to run your own VPN, either full time or on a needs basis, you can do so in a few clicks using CloudFormation and OpenVPN. If you want to just get to the fun part the stack can be launched from this template.

Some notes on costing. This setup uses an EIP (Elastic IP) so you can maintain the same IP between server restarts, allowing you to put the IP in DNS if that makes like easier for you. This will not cost anything when running, but it will generate a small hourly cost (~ $0.005/hour) when not associated to a running instance. This is not that much less than the cost of running a t2.nano instance full time (~ $0.0059/hour) so if that is your instance of choice it is not particularly advantageous to stop the instance when not using. If that level of cost is undesirable than you want to tear down the whole stack when not using and receate when needed. If your VPN needs dictate a larger instance type you can reduce your costs when unused by stopping the instance and incurring the small charge for the EIP.

At the start of our CloudFormation template we have the version, template description and parameters we expect:

---
AWSTemplateFormatVersion: "2010-09-09"

Description: Establishes an OpenVPN server in a public subnet within a new VPC

Parameters:
  InstanceAMI:
    Description: OpenVPN AMI
    Type: String
    Default: ami-bc3566ab

  InstanceType:
    Description: OpenVPN Instance Type
    Type: String
    AllowedValues:
      - t2.nano
      - t2.micro
      - t2.small
      - t2.medium
      - t2.large
    Default: t2.micro

  KeyName:
    Description: SSH Key Name
    Type: AWS::EC2::KeyPair::KeyName

  AdminPassword:
    Description: OpenVPN Admin Password
    Type: String
    NoEcho: true
    MinLength: 8
    MaxLength: 32
    ConstraintDescription: Must be at least 8 chars long

  RouteAllTraffic:
    Description: Should all local traffic go over VPN when connected?
    Type: Number
    AllowedValues:
      - 0
      - 1
    Default: 1

  UseVPNDNS:
    Description: Should client use VPN supplied DNS when connected?
    Type: Number
    AllowedValues:
      - 0
      - 1
    Default: 1

The parameters should be somewhat self explanatory, given the descriptions, but we are asking for the OpenVPN AMI, with a default value for us-east-1, the instance type, a key pair if desired (note the security group in the template does NOT open port 22 so you need to add this if desired), your VPN admin and connection password, and the opton to push config to the client on connect that sends all traffic and DNS through the VPN.

Getting into the Resources section of the template we define our services. First up are our VPC, subnet (public), and associated Internet Gateway and routing tables/routes:

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: "10.0.0.0/16"
      Tags:
      - Key: Name
        Value: OpenVPN

  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: "10.0.0.0/24"
      Tags:
      - Key: Name
        Value: OpenVPN Public Subnet

  InternetGateway:
    Type: AWS::EC2::InternetGateway

  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

  RouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  Route:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref RouteTable
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway

  SubnetRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet
      RouteTableId: !Ref RouteTable

Following that are the Elastic IP, the OpenVPN instance, the security group and IP association:

  IPAddress:
    Type: AWS::EC2::EIP
    Properties:
      Domain: "vpc"

  SecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPC
      GroupDescription: Security group for OpenVPN Server
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: 443
        ToPort: 443
        CidrIp: "0.0.0.0/0"
      - IpProtocol: tcp
        FromPort: 943
        ToPort: 943
        CidrIp: "0.0.0.0/0"
      - IpProtocol: udp
        FromPort: 1194
        ToPort: 1194
        CidrIp: "0.0.0.0/0"

  OpenVPNInstance:
    Type: AWS::EC2::Instance
    DependsOn: IPAddress
    Properties:
      ImageId: !Ref InstanceAMI
      InstanceType: !Ref InstanceType
      KeyName: !Ref KeyName
      SecurityGroupIds:
      - !Ref SecurityGroup
      SubnetId: !Ref PublicSubnet
      Tags:
       - Key: Name
         Value: OpenVPN Instance
      UserData:
        Fn::Base64: !Sub |
          public_hostname=${IPAddress}
          admin_user=openvpn
          admin_pw=${AdminPassword}
          reroute_gw=${RouteAllTraffic}
          reroute_dns=${UseVPNDNS}

  IPAssociaton:
    Type: AWS::EC2::EIPAssociation
    DependsOn: OpenVPNInstance
    Properties:
      AllocationId: !GetAtt IPAddress.AllocationId
      InstanceId: !Ref OpenVPNInstance

We can kick off it's creation either in the CloudFormation web console or on the CLI with:

aws cloudformation create-stack --stack-name OpenVPN --template-body file://openvpn-vpc.yaml --parameters ParameterKey=InstanceType,ParameterValue=t2.nano ParameterKey=AdminPassword,ParameterValue=UseAStrongPasswordPlease

For the web console you would do the following steps, first create a new stack using the template:

You can customize the AMI (based on region, license requirements, etc.) and set your instance type, password and other options:

The next two screens likely involve no required action, unless you want to add additional tags to the resources. You can review and click Create to build the stack:

You can watch the progress in the console as the resources are created, and after a couple minutes you will see the stack change to CREATE_COMPLETE.

At that point you can use the Outputs tab to get the URL for the admin login, or the IP address for adding to DNS, setting up your OpenVPN client, etc.:

That's it. You have a self provisioned OpenVPN server that you can turn on/off, build/teardown at your leisure. If you want to launch it in different AWS regions you will need to ensure you track down the right AMI for that region (this is another post in itself) and the same applies if you want to use an image outside the free license model which allows a max of 2 users to connect simultaneously.

Happy VPNing!