Run Your Own OpenVPN Server
By Sudheer S
Introduction
The article explains how to run your own OpenVPN server. We will create a 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 22.04
.
System Architecture And Requirements
In order to run your OpenVPN server via these Ansible roles, you will need three machines:
- 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.
- 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.
- 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 to 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 22.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 administrator 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 22.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 set up 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 all set. 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.
<clientname>.ovpn
: general purpose client configuration file<clientname>-el.ovpn
: use this on clients of the EL family such as RHEL, CentOS, Fedora.<clientname>-update-resolv.ovpn
: use this for clients that have anupdate-resolv.conf
file in their/etc/openvpn
directory.
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:
- route all traffic via the OpenVPN server
- 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.