Tech Chorus

Run Your Own OpenVPN Server

written by Sudheer Satyanarayana on 2019-06-30

Introduction

The article explains how to run your own OpenVPN server. We will setup one Certificate Authority Server and an OpenVPN server. We will also generate certificates for the clients. We will also learn how to manage revocation of client certificates using the Ansible roles.

Use the Ansible roles gavika.openvpn and gavika.easy_rsa to install and configure your OpenVPN server.

You can install the OpenVPN server on any public cloud or hosting provider or on-premise servers. The Ansible roles are designed to install the OpenVPN server and a Certificate Authority server.

At the moment these Ansible roles support Ubuntu 18.04 and CentOS 7.

System Architecture And Requirements

In order to run your OpenVPN server via these Ansible roles, you will need three machines:

  1. Controller machine. This is the machine from which you execute the Ansible playbooks. This could be your laptop or a machine in the cloud. You will designate a directory on this machine as a temporary pool of files.
  2. Certificate Authority server. You will create your own CA machine that signs the certificate requests. You will need SSH access to this machine from the controller machine. You will only need to turn this server on when required. It is recommended to shut down the CA server when not in use to improve the security. Also, saves cost.
  3. OpenVPN server. You will create your OpenVPN server on this machine. You will need SSH access to this machine from the controller machine. You will also need to ensure that UDP port 1194 is open on this machine. The Ansible playbook takes care of enabling the port on the machine itself. You are responsible to open the ports on the network firewall(such as AWS Security Groups, on-premise hardware or software firewall). You will have adjust your network firewalls too in case you change the defaults in Ansible playbook or inventory.

In addition to SSH access, the servers require a user with administrative privileges via sudo. Typically, cloud images of servers provide such user accounts on the server. On AWS, for the Ubuntu images, the user is typically called ubuntu. On CentOS the user is typically called centos. If you do not have such a username, create one. There's an Ansible role to create administrative user accounts too.

Once you have provisioned the servers, proceed to create the Ansible playbooks.

Installing The Ansible Roles

Our roles require Ansible 2.8 or higher. Ensure that the required version of Ansible is installed. If not, follow the instructions to install Ansible.

Create a directory to store the playbooks and inventory.

mkdir my-openvpn-server-orchestration
cd my-openvpn-server-orchestration

I create a directory called my-openvpn-server-orchestration. You can name it whatever you want.

Next step is to install the Ansible roles from Ansible Galaxy.

ansible-galaxy install gavika.easy_rsa
ansible-galaxy install gavika.openvpn

If your target OS is CentOS, install the centos_base role too:

ansible-galaxy install bngsudheer.centos_base

Preparing Ansible Inventory

Create the file inventory.yml and add the following contents:

all:
  hosts:
    placeholder
  children:
    ca_server:
      hosts:
        dev-ca-01.example.com:
          ansible_become: true
          ansible_user: ubuntu
          ansible_host: 192.168.1.10
          easy_rsa_ca_server_mode: true
          ansible_python_interpreter: /usr/bin/python3
    openvpn_server:
      hosts:
        dev-vpn-01.example.com:
          ansible_python_interpreter: /usr/bin/python3
          ansible_become: true
          ansible_user: ubuntu
          ansible_host: 192.168.1.11
          openvpn_server_ip_address: 192.168.1.11

I prefer to use YAML formatted Ansible inventory file. Your mileage may vary. If you are using INI format for your inventory file, make sure to port the format as required.

dev-ca-01.example.com is our CA server and dev-vpn-01.example.com. We are specifying the IP addresses of these hosts, in case the DNS is not setup yet. If the DNS resolves to the correct IP addresses, you can remove the ansible_host key. Specifying ansible_host is especially useful in test environments where there is no proper DNS system.

In this example we are using Ubuntu 18.04 for both the CA and OpenVPN servers. Ansible connects to these servers with the username ubuntu. We also tell Ansible to use the Python interpreter from the location usr/bin/python3. If Python 2 is installed on the servers, you don't have to mention the interpreter path. We also mention in our inventory that Ansible should use sudo via ansible_become.

If your OS has another administratnor user, adjust the value of ansible_user. If the target host has Python 2 installed, remove the key ansible_python_interpreter from your inventory.

Notice that the IP address of the OpenVPN server is mentioned in both ansible_host and openvpn_server_ip_address. ansible_host is used to connect to the server via SSH by Ansible. openvpn_server_ip_address is used to generate the client certificate.

Preparing The OpenVPN Server

Create the file openvpn-server.yml with the following contents if your target host is Ubuntu 18.04:

---
- hosts: openvpn_server
  vars:
    openvpn_client_users:
      - janedoe
      - johndoe
    openvpn_generated_configurations_local_pool: true
    easy_rsa_req_country: "IN"
    easy_rsa_req_province: "KA"
    easy_rsa_req_city: "Bangalore"
    easy_rsa_req_org: "My Organization"
    easy_rsa_req_email: "admin@example.com"
    easy_rsa_req_ou: "My Organization Unit"
    easy_rsa_local_pool_directory: /tmp/ca_openvpn_pool_example
  roles:
    - role: gavika.easy_rsa
    - role: gavika.openvpn

If your target host is CentOS, ensure EPEL is enabled. Edit your openvpn-server.yml like below

---
- hosts: openvpn_server
  vars:
    centos_base_enable_epel: true
    openvpn_client_users:
      - janedoe
      - johndoe
    openvpn_generated_configurations_local_pool: true
    easy_rsa_req_country: "IN"
    easy_rsa_req_province: "KA"
    easy_rsa_req_city: "Bangalore"
    easy_rsa_req_org: "My Organization"
    easy_rsa_req_email: "admin@example.com"
    easy_rsa_req_ou: "My Organization Unit"
    easy_rsa_local_pool_directory: /tmp/ca_openvpn_pool_example
  roles:
    - role: bngsudheer.centos_base
    - role: gavika.easy_rsa
    - role: gavika.openvpn

We are specifying that we want to create two client users janedoe and johndoe. We also specify the variables for the EasyRSA Public Key Infrastructure. On the OpenVPN server, we will also setup PKI but not in the CA mode. We use the PKI on this server to generate certificate requests and to store the client configurations. Certificate signing is done on the CA server.

Setting openvpn_generated_configurations_local_pool to true causes the generated client configurations to be copied to the local pool. We also ensure that easy_rsa_local_pool_directory is set to same value as in our ca-server.yml playbook.

In this playbook, we are executing two roles. gavika.easy_rsa to setup PKI and gavika.openvpn to setup OpenVPN server.

Run the playbook:

ansible-playbook -i inventory.yml openvpn-server.yml --private-key /path/to/my/private/key

At this point, you should see the file server.req in the path /tmp/ca_openvpn_pool_example/server/ in the local pool. You should also see janedoe.req and johndoe.req in /tmp/ca_openvpn_pool_example/client/ in the local pool.

Preparing The CA Server

Create the file: ca-server.yml

---
- hosts: ca_server
  vars:
    easy_rsa_req_country: "IN"
    easy_rsa_req_province: "KA"
    easy_rsa_req_city: "Bangalore"
    easy_rsa_req_org: "Example"
    easy_rsa_req_email: "admin@example.com"
    easy_rsa_req_ou: "Example"
    easy_rsa_local_pool_directory: /tmp/ca_openvpn_pool_example
    easy_rsa_ca_server_mode: true
  roles:
    - role: gavika.easy_rsa

We want to run the playbook on the hosts group: ca_server. This is exactly what we have in our inventory. The vars section has a series of variables used in certificates. Adjust them to your liking. Some files have to be transferred between the CA server and the OpenVPN server. For this purpose, we use a directory on the controller machine(the machine on which you execute the Ansible playbooks, probably your laptop or a bastion host or a management host). In our example we use /tmp/ca_openvpn_pool_example as the pool. You are free to choose a different directory.

Setting easy_rsa_ca_server_mode to true ensures we want to make this server a Certificate Authority.

Just like we did for OpenVPN playbook, adjust the CA playbook for CentOS 7:

---
- hosts: ca_server
  vars:
    centos_base_enable_epel: true
    easy_rsa_req_country: "IN"
    easy_rsa_req_province: "KA"
    easy_rsa_req_city: "Bangalore"
    easy_rsa_req_org: "Example"
    easy_rsa_req_email: "admin@example.com"
    easy_rsa_req_ou: "Example"
    easy_rsa_local_pool_directory: /tmp/ca_openvpn_pool_example
    easy_rsa_ca_server_mode: true
  roles:
    - role: bngsudheer.centos_base
    - role: gavika.easy_rsa

Execute the playbook:

ansible-playbook -i inventory.yml ca-server.yml --private-key /path/to/my/private/key

/path/to/my/private/key is your SSH private key used to connect to the CA server.

If the playbook ran successfully, your CA server is setup. At this point you should see the file ca.crt in /tmp/ca_openvpn_pool_example/.

The certificate signing request for the server - server.req will be uploaded to the CA server. The CA server imports the request and signs it. The signed certificate will be copied to the local pool. You should be able to see server.crt in /tmp/ca_openvpn_pool_example/issued/server/ local pool.

Execute the openvpn-server.yml playbook again:

ansible-playbook -i inventory.yml openvpn-server.yml --private-key /path/to/my/private/key

This time, openvpn service will be started. The playbook execution will also copy the generated client configuration files in /tmp/ca_openvpn_pool_example/generated/.

Connect To The OpenVPN Server

The gavika.openvpn role generates three files for each user.

Install the openvpn package on the client machine:

Fedora:

sudo dnf install openvpn

Ubuntu:

sudo apt install openvpn

Example command to connect to the OpennVPN server on a Fedora client:

 sudo openvpn --config /tmp/ca_openvpn_pool_example/generated/janedoe/janedoe-el.ovpn

Example command to connect to the OpennVPN server on an Ubuntu client:

 sudo openvpn --config /tmp/ca_openvpn_pool_example/generated/janedoe/janedoe.ovpn

If you see a message like:

Tue Jul  2 00:34:37 2019 Initialization Sequence Completed

then you have connected successfully. Try browsing the Internet from your browser. Or just check your Internet routed IP address from the command line:

curl http://api.ipify.org

The output should show your OpenVPN server's IP address.

Revoking Certificates

If you want to revoke access to a client, edit your ca-server.yml playbook and include the list of clients to be revoked:

---
- hosts: ca_server
  vars:
    ...
    easy_rsa_revoke_clients:
      - janedoe
  roles:
    - role: gavika.easy_rsa

In this example, we are revoking the certificate for the client janedoe. Next step is to run the CA playbook:

ansible-playbook -i inventory.yml ca-server.yml --private-key /path/to/my/private/key

When the playbook finishes executing, you should see the file crl.pem in /tmp/ca_openvpn_pool_example/crl/ directory of the local pool.

Next, we run the OpenVPN playbook to update the Certificate Revocation List:

ansible-playbook -i inventory.yml openvpn-server.yml --private-key /path/to/my/private/key

After the playbook executes successfully, the client janedoe won't be able to connect to the OpenVPN server.

Routing

You can configure your OpenVPN server to:

  1. route all traffic via the OpenVPN server
  2. route traffic via OpenVPN server to specific IP addresses or networks.

If you want to route traffic to specific networks, change the Ansible variables like below:

openvpn_route_all_traffic: false
openvpn_additional_configs:
   - push: "topology subnet"
   - push: "route 192.168.4.5 255.255.255.255"
   - push: "route 192.168.4.6 255.255.255.255"

Setting openvpn_route_all_traffic to false removes the redirect-gateway field and that def1 and bypass-dhcp flags in the OpenVPN server configuration. openvpn_additional_configs allows you to write additional OpenVPN server configuration. In our example, we set two such additional configuration lines. Each push line ensures that the client uses the OpenVPN connection to reach out to the corresponding IP address. In this example, when the client tries to reach the IP addresses 192.168.4.5 or 192.168.4.6, it uses the OpenVPN connection.

Tags: vpn networking ubuntu centos ansible