Preliminaries

This tutorial is inspired by the Linode documentation.

First of all, we have to install the OpenVPN package and some extra tools.

sudo apt update
sudo apt upgrade
sudo apt install -y iptables-persistent openvpn easy-rsa
echo 1 > /proc/sys/net/ipv4/ip_forward

Edit vim /etc/sysctl.conf and add net.ipv4.ip_forward = 1

Firewall rules

Lets consider that the ssh server listens the 65522 port and 65494 for the VPN server.

We have to define firewall rules in the file /etc/iptables/rules.v4:

*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
# Forward the VPN traffic to eth0
-A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE

COMMIT

*filter

# Allow all loopback (lo) traffic and reject anything
# to localhost that does not originate from lo.
-A INPUT -i lo -j ACCEPT
-A INPUT ! -i lo -s 127.0.0.0/8 -j REJECT
-A OUTPUT -o lo -j ACCEPT

# Allow ping and ICMP error returns.
-A INPUT -p icmp -m state --state NEW --icmp-type 8 -j ACCEPT
-A INPUT -p icmp -m state --state ESTABLISHED,RELATED -j ACCEPT
-A OUTPUT -p icmp -j ACCEPT

# Allow SSH.
-A INPUT -i eth0 -p tcp -m state --state NEW,ESTABLISHED --dport 65522 -j ACCEPT
-A OUTPUT -o eth0 -p tcp -m state --state ESTABLISHED --sport 65522 -j ACCEPT

# Allow UDP traffic.
-A INPUT -i eth0 -p udp -m state --state NEW,ESTABLISHED --dport 65494 -j ACCEPT
-A OUTPUT -o eth0 -p udp -m state --state ESTABLISHED --sport 65494 -j ACCEPT

# Allow DNS resolution and limited HTTP/S on eth0.
# Necessary for updating the server and keeping time.
-A INPUT -i eth0 -p udp -m state --state ESTABLISHED --sport 53 -j ACCEPT
-A OUTPUT -o eth0 -p udp -m state --state NEW,ESTABLISHED --dport 53 -j ACCEPT
-A INPUT -i eth0 -p tcp -m state --state ESTABLISHED --sport 80 -j ACCEPT
-A INPUT -i eth0 -p tcp -m state --state ESTABLISHED --sport 443 -j ACCEPT
-A OUTPUT -o eth0 -p tcp -m state --state NEW,ESTABLISHED --dport 80 -j ACCEPT
-A OUTPUT -o eth0 -p tcp -m state --state NEW,ESTABLISHED --dport 443 -j ACCEPT

# Allow traffic on the TUN interface.
-A INPUT -i tun0 -j ACCEPT
-A OUTPUT -o tun0 -j ACCEPT

# Log any packets which don't fit the rules above...
# (optional but useful)
-A INPUT -m limit --limit 3/min -j LOG --log-prefix "iptables_INPUT_denied: " --log-level 4
-A FORWARD -m limit --limit 3/min -j LOG --log-prefix "iptables_FORWARD_denied: " --log-level 4
-A OUTPUT -m limit --limit 3/min -j LOG --log-prefix "iptables_OUTPUT_denied: " --log-level 4

# then reject them.
-A INPUT -j REJECT
-A OUTPUT -j REJECT

COMMIT

Be sure to adapt these rules to your needs before applying them.

sudo iptables-restore < /etc/iptables/rules.v4

We can check if the rules are correctly implied:

sudo iptables -L

Disable ipv6

In sysctl

In the file /etc/sysctl.d/99-sysctl.conf, add the following lines:

net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
net.ipv6.conf.eth0.disable_ipv6 = 1

and then apply the new configuration

sudo sysctl -p

... and reject with iptables

Remove ipv6 lines in /etc/hosts:

#::1     localhost ip6-localhost ip6-loopback

Reject ipv6 traffic by editing the file /etc/iptables/rules.v6, it must contains:

*filter

-A INPUT -j REJECT
-A FORWARD -j REJECT
-A OUTPUT -j REJECT

COMMIT

and apply:

sudo ip6tables-restore < /etc/iptables/rules.v6

OpenVPN configuration

Prepare server configuration environment

The OpenVPN configuration file is /etc/openvpn/server.conf.

su
gunzip -c /usr/share/doc/openvpn/examples/sample-config-files/server.conf.gz > /etc/openvpn/server.conf
make-cadir /etc/openvpn/easy-rsa && cd /etc/openvpn/easy-rsa/
ln -s openssl-1.0.0.cnf openssl.cnf
mkdir keys
chmod 700 keys/

Configure certificates, keys, ... path

In /etc/openvpn/server.conf, we have to specify the correct paths for the certificate, keys, etc.:

ca /etc/openvpn/easy-rsa/keys/ca.crt
cert /etc/openvpn/easy-rsa/keys/server.crt
key /etc/openvpn/easy-rsa/keys/server.key
dh /etc/openvpn/dh4096.pem

Configure certificate properties

In the file /etc/openvpn/easy-rsa/vars, we have to specify the certificate properties like:

export KEY_COUNTRY="US"
export KEY_PROVINCE="CA"
export KEY_CITY="SanFrancisco"
export KEY_ORG="Fort-Funston"
export KEY_EMAIL="me@myhost.mydomain"
export KEY_OU="MyOrganizationalUnit"

and source these variables:

cd /etc/openvpn/easy-rsa && source ./vars
./clean-all

Generate Diffie-Hellman PEM

openssl dhparam 4096 > /etc/openvpn/dh4096.pem

Harden OpenVPN

HMAC signature during TLS handshake

In the file /etc/openvpn/server.conf, we have to change the TLS configuration:

tls-auth /etc/openvpn/easy-rsa/keys/ta.key 0

and generate the HMAC key file:

openvpn --genkey --secret /etc/openvpn/easy-rsa/keys/ta.key

Limited user

We have to create a user for our OpenVPN. This user has limited privileges:

adduser --system --shell /usr/sbin/nologin --no-create-home openvpn_server

and specify the user in the OpenVPN configuration:

user openvpn_server
group nogroup

Ciphers and digests

In the file /etc/openvpn/server.conf, we have to specify the ciphers and digest we want to use:

cipher AES-256-CBC
auth SHA512
tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-256-CBC-SHA:TLS-DHE-RSA-WITH-CAMELLIA-256-CBC-SHA:TLS-DHE-RSA-WITH-AES-128-CBC-SHA:TLS-DHE-RSA-WITH-CAMELLIA-128-CBC-SHA

Certificate and Key Pairs

Server side

cd /etc/openvpn/easy-rsa
./build-ca
./build-key-server server

Client side

cd /etc/openvpn/easy-rsa && source ./vars && ./build-key client1