In today’s world, employees often need the flexibility to work from remote locations. These remote locations can be a cafe, hotel lobby or any other public place where the employee is using public wifi. Public wifis are usually not secure and data transmitted through them is easily hackable. The best defense to prevent your company data from being exposed is to use secure wifi but that is not always doable. A more practical solution is to use a VPN, which provides a secure connection between employees’ system to the server over the public internet. This makes sure that the data consumed by the client is reliably transferred and invisible on the network. Another use case is when an employee needs to access specific resources which are not available in the country where an employee is residing or traveling. In this case, an employee can access all these resources through the VPN as traffic is routed through it and website providers assume that it is accessed by a VPN server.

Enabling VPN for accessing company resources can help strengthen data security, as now the data is not available on the public internet. VPN helps in manageability and traceability as everyone goes through this VPN server so we can track who all have accessed the resources which help isolate and identify any suspicious activity.

In this article, we are going to set up a VPN using an open source package OpenVPN. We are going to use Ubuntu OS for this setup. This setup requires two packages OpenVPN and easy-rsa. easy-rsa allows us to create our own certificate authority, generate certificates and keys which are used by VPN server and client. This helps us to encrypt, decrypt messages, authenticate client and server to provide secure communication. OpenVPN server allows users with right certificates which help make sure communication is authorized. On the client side, the client also validates that it is connected to the right server using these certificates and keys. We need to activate a firewall which blocks all ports except SSH (22) and VPN(1194). We are going to use ufw tool for this setup. We need to update IPtable rules to NAT the VPN client through the internet. We also need to enable packet forwarding by adding net.ipv4.ip_forward=1 in our “sysctl” config file and changing ufw config to ALLOW forward policy.

Before we go to the actual setup, let’s understand what is “ufw”. ufw stands for uncomplicated firewall which provides a user-friendly framework for managing firewall rules. A firewall is a set of rules for networking where we can define who can connect to our server or with whom our server can connect. We can simply allow/disallow all incoming and outgoing traffic using simple ufw commands. For example, we can enable access to a particular port using below command:

sudo ufw allow ssh

We can add more advanced rules in the before.rules file which ufw uses before running any terminal provided rules. Once rules are set we can check the status of ufw using the command:

sudo ufw status

If we find ufw to be disabled then we can enable it using the command:

sudo ufw enable

Let’s start with the setup of VPN server using ansible. As a first step, we need to update our repositories so we can download and install the latest packages. We need to install OpenVPN and easy-rsa packages.

- name: Update apt packages

become: true

apt:

upgrade: yes - name: Install openvpn

package:

name: "{{ item }}"

state: present

with_items:

- openvpn

- easy-rsa

We need to create a new custom certificate authority which will help in generating a server certificate and key pair along with a Diffie-Hellman parameter and the tls-auth key. We create our CA directory where all certificates/keys are generated.

- name: "Remove CA directory"

become: yes

file:

state: absent

path: "{{ ansible_env.HOME }}/openvpn-ca/" - name: "Create CA dir"

become: yes

command: make-cadir {{ ansible_env.HOME }}/openvpn-ca

We need to set up variables which will be used to set values used during CA creation and generating certificates and keys.



lineinfile:

dest: "{{ ansible_env.HOME }}/openvpn-ca/vars"

regexp: "^{{ item.property | regex_escape() }}="

line: "{{ item.property }}={{ item.value }}"

with_items:

- { property: 'export KEY_NAME', value: '"server"' }

- { property: 'export KEY_COUNTRY', value: '"US"' }

- { property: 'export KEY_PROVINCE', value: '"CA"' }

- { property: 'export KEY_CITY', value: '"SF"' }

- { property: 'export KEY_ORG', value: '"MT"' }

- { property: 'export KEY_EMAIL', value: '"

- { property: 'export KEY_OU', value: '"MT"' }

- { property: 'export KEY_CONFIG', value: '{{ ansible_env.HOME }}/openvpn-ca/openssl-1.0.0.cnf' }

- { property: 'export KEY_DIR', value: '{{ ansible_env.HOME }}/openvpn-ca/keys' } - name: Customize CA variable configurationlineinfile:dest: "{{ ansible_env.HOME }}/openvpn-ca/vars"regexp: "^{{ item.property | regex_escape() }}="line: "{{ item.property }}={{ item.value }}"with_items:- { property: 'export KEY_NAME', value: '"server"' }- { property: 'export KEY_COUNTRY', value: '"US"' }- { property: 'export KEY_PROVINCE', value: '"CA"' }- { property: 'export KEY_CITY', value: '"SF"' }- { property: 'export KEY_ORG', value: '"MT"' }- { property: 'export KEY_EMAIL', value: '" mt@mt.com "' }- { property: 'export KEY_OU', value: '"MT"' }- { property: 'export KEY_CONFIG', value: '{{ ansible_env.HOME }}/openvpn-ca/openssl-1.0.0.cnf' }- { property: 'export KEY_DIR', value: '{{ ansible_env.HOME }}/openvpn-ca/keys' }

Now let’s build our certificate authority using scripts provided in our CA directory.

- name: "Build the certificate authority"

become: yes

shell: >

source vars;

./clean-all;

yes "" | ./build-ca;

args:

chdir: "{{ ansible_env.HOME }}/openvpn-ca/"

executable: /bin/bash

Next step is to generate a certificate for the server. We create Diffie-Hellman parameters which are used to exchange cryptographic keys using a public and insecure channel. To add more security in addition to the certificate, we are going to generate a key to use a shared secret. All this is generated inside the keys folder. We need to move them to the OpenVPN folder and point to them in the config file.

- name: "Build server certificate"

become: yes

shell: >

source vars;

./build-key-server --batch server;

args:

chdir: "{{ ansible_env.HOME }}/openvpn-ca/"

executable: /bin/bash - name: "Build Diffie-Hellman parameters and key generation"

become: yes

shell: >

source vars;

yes "" | ./build-dh;

openvpn --genkey --secret keys/ta.key;

args:

chdir: "{{ ansible_env.HOME }}/openvpn-ca/"

executable: /bin/bash - name: "Copy key and certificates to /etc/openvpn"

become: yes

copy:

remote_src: yes

src: "{{ ansible_env.HOME }}/openvpn-ca/keys/{{ item }}"

dest: "/etc/openvpn/"

with_items:

- "ca.crt"

- "server.crt"

- "server.key"

- "ta.key"

- "dh2048.pem"

We are going to copy the sample configuration file as our OpenVPN config file. Make the required changes in this config file to point to the right certificate, user/group, and DHCP configuration.

- name: "Generate server.conf from sample config"

become: yes

shell: >

gzip -d -c /usr/share/doc/openvpn/examples/sample-config-files/server.conf.gz | sudo tee /etc/openvpn/server.conf > /dev/null

- name: Adjust OpenVPN server configuration

lineinfile:

dest: "/etc/openvpn/server.conf"

regexp: "^{{ item.regex | regex_escape() }}"

line: "{{ item.value }}"

with_items:

- { regex: ';user nobody', value: 'user nobody' }

- { regex: ';group nogroup', value: 'group nogroup' }

- { regex: ';push "redirect-gateway def1 bypass-dhcp"', value: 'push "redirect-gateway def1 bypass-dhcp"' }

- { regex: 'cert server.crt', value: 'cert server.crt' }

- { regex: 'key server.key', value: 'key server.key' }

We are going to enable firewall using ufw with allowing VPN and SSH ports. Update routing rules, enable IP forwarding and accept forwarding policy.

- name: Configuration IP forwarding

become: true

sysctl:

name: net.ipv4.ip_forward

value: 1

state: present - name: Add ufw before content

become: true

blockinfile:

path: /etc/ufw/before.rules

insertbefore: BOF

content: |

# NAT table rules

*nat

:POSTROUTING ACCEPT [0:0]

-A POSTROUTING -s 10.8.0.0/8 -o eth0 -j MASQUERADE

COMMIT

- name: Customize ufw forwarding policy

become: true

lineinfile:

line: "DEFAULT_FORWARD_POLICY=\"ACCEPT\""

path: "/etc/default/ufw"

regexp: "^DEFAULT_FORWARD_POLICY=\"DROP\"" - name: Open ufw ports for openvpn and ssh

become: true

shell: ufw allow openvpn && ufw allow OpenSSH - name: Enable ufw

become: true

shell: ufw --force enable

Once everything is done, we are going to restart the OpenVPN server to bring all updated changes in effect. With this, we have our OpenVPN server up and running.

- name: Start openvpn systemd service

become: true

systemd:

name: openvpn@server

state: started

daemon_reload: yes

enabled: yes

As we have our server ready, we need to create client configuration which can be used to connect to our VPN server and redirect traffic through it. We are going to create an “ovpn” file which can be used by any OpenVPN client tool. As a first step, we need to generate a certificate/key pair.

- name: "Generate client certificate key"

become: yes

shell: source vars; ./build-key --batch {{client_name}}

args:

chdir: "{{ ansible_env.HOME }}/openvpn-ca/"

executable: /bin/bash - name: "Create client certificate configs dir"

become: yes

file:

owner: "{{ ansible_env.USER }}"

group: "{{ ansible_env.USER }}"

path: "{{ ansible_env.HOME }}/openvpn-ca/{{client_name}}"

state: directory

mode: 0700

We are going to copy a sample client conf file which we shall then modify to update the config params like server IP address, client certificate, client key, ta key to use a shared secret with other needed changes.

- name: "Copy client sample configs from remote host itself"

become: yes

copy:

remote_src: yes

src: /usr/share/doc/openvpn/examples/sample-config-files/client.conf

dest: "{{ ansible_env.HOME }}/openvpn-ca/{{client_name}}/{{client_name}}.ovpn" - name: Set the server ip and port

lineinfile:

dest: "{{ ansible_env.HOME }}/openvpn-ca/{{client_name}}/{{client_name}}.ovpn"

regexp: "^{{ item.regex | regex_escape() }}"

line: "{{ item.value }}"

with_items:

- { regex: 'remote my-server-1 1194', value: 'remote {{ groups["openVPN"][0] }} 1194' }

- { regex: ';user nobody', value: 'user nobody' }

- { regex: ';group nogroup', value: 'group nogroup' }

- { regex: 'ca ca.crt', value: '#ca ca.crt' }

- { regex: 'cert client.crt', value: '#cert client.crt' }

- { regex: 'key client.key', value: '#key client.key' }

- { regex: 'tls-auth ta.key 1', value: '#tls-auth ta.key 1' } - name: "Create client ovpn file"

become: yes

shell: "{{ item }}"

with_items:

- echo -e '<ca>' >> {{ ansible_env.HOME }}/openvpn-ca/{{client_name}}/{{client_name}}.ovpn

- cat {{ ansible_env.HOME }}/openvpn-ca/keys/ca.crt >> {{ ansible_env.HOME }}/openvpn-ca/{{client_name}}/{{client_name}}.ovpn

- echo -e '</ca>

<cert>' >> {{ ansible_env.HOME }}/openvpn-ca/{{client_name}}/{{client_name}}.ovpn

- cat {{ ansible_env.HOME }}/openvpn-ca/keys/{{client_name}}.crt >> {{ ansible_env.HOME }}/openvpn-ca/{{client_name}}/{{client_name}}.ovpn

- echo -e '</cert>

<key>' >> {{ ansible_env.HOME }}/openvpn-ca/{{client_name}}/{{client_name}}.ovpn

- cat {{ ansible_env.HOME }}/openvpn-ca/keys/{{client_name}}.key >> {{ ansible_env.HOME }}/openvpn-ca/{{client_name}}/{{client_name}}.ovpn

- echo -e '</key>

<tls-auth>' >> {{ ansible_env.HOME }}/openvpn-ca/{{client_name}}/{{client_name}}.ovpn

- cat {{ ansible_env.HOME }}/openvpn-ca/keys/ta.key >> {{ ansible_env.HOME }}/openvpn-ca/{{client_name}}/{{client_name}}.ovpn

- echo -e '</tls-auth>' >> {{ ansible_env.HOME }}/openvpn-ca/{{client_name}}/{{client_name}}.ovpn

- echo -e 'key-direction 1' >> {{ ansible_env.HOME }}/openvpn-ca/{{client_name}}/{{client_name}}.ovpn

args:

chdir: "{{ ansible_env.HOME }}/openvpn-ca/"

executable: /bin/bash

Once everything is done, we can fetch “ovpn” files to the local instance and share with clients so they can use it to connect with our VPN server.

- name: Fetch client configurations

fetch:

src: "{{ ansible_env.HOME }}/openvpn-ca/{{client_name}}/{{ item|basename }}"

dest: "{{ destination_key }}/"

flat: yes

with_items:

- "{{client_name}}.ovpn"

We can use this file with any VPN client to connect to a VPN server. If you are using a mac then you can use Tunnelblick which is an easy to use VPN client.

The complete code can be found in this git repository: https://github.com/MiteshSharma/OpenVPNAnsible

PS: If you liked the article, please support it with claps 👏. Cheers