OpenVPN server configuration tutorial on Linux Centos 7 with two factor authentication

Description

     In this lesson, we'll look at the most common installation of an OpenVPN server on Linux Centos 7.
We will try two ways of traffic routing:
- Routing  all traffic from the client vi the OpenVPN server
- Routing certain routes from the client vi the OpenVPN server.
In addition, we will enable:
- Two-factor authentication so that clients connect not only using certificate, but also to the user/password.
- Provide additional security by authenticating packets on the TLS control channel.
This instruction assumes that the administrator has basic skills on Linux CentOS with the existing installation of the Linux Operating System Centos 7. Do not forget that before using this manual you should study the legislation of the countries where you are using a client or server connection and do not violate it in any way. There are hosting services that offer a minimal hardware configuration of VPS for $ 3 / month. Be sure to check that your VPS has TUN / TAP interfaces available, OpenVPN wouldn't work without that.

Checking Your System

Let's check the version of our Linux Operating System and see that we have the version of Centos 7.4 installed:

[root@centos7 ~]# cat /etc/redhat-release
CentOS Linux release 7.4.1708 (Core)

First, let us try to install the openvpn package {more...}

First, let us try to install the openvpn package with the Yum package manager:

[root@centos7 ~]# yum install openvpn

And then we see the error:

No package openvpn available. Error: Nothing to do

This means that we do not have the necessary repositories, which contain the OpenVPN distribution. 

{less}

Installing OpenVPN

For the installation, we need to connect an additional EPEL (Extra Packages for Enterprise Linux) repository. This repository is managed by the Fedora Project - this is the Global Partnership of the Free Software Community. It contains software distributions that are not available in standard CentOS Linux repositories.

Enter the command:

[root@centos7 ~]# yum install epel-release -y

If an error occurs: {more...}

If an error occurs:

No package epel-release available.

you can install the package manually using the command:

[root@centos7 ~]# rpm -ivh https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm

{less}

    Installing OpenVPN and Easy RSA tool, necessary for generating SSL keys for encrypting traffic in the VPN:

[root@centos7 ~]# yum install openvpn easy-rsa -y
OpenVPN configuration

There are a lot of configurations examples, scripts, etc. in the /usr/share/doc/openvpn-х.х.х/sample/ directory. There are a lot of useful and additional options that you can experiment with later. We will create configuration that is secure, but compatible with the old client applications. We will make two configuration files tcpserver.conf and later udpserver.conf. Run a separate process for each configuration file - below we'll look at how to do it. We plan to create an automatic adaptive VPN, which will listen on two different ports. Priority for us will be the protocol UDP - due to the fact that it is more resistant to DoS-attacks and in order to reduce the time of delivery of packages, because if you plan to use VoIP over a VPN, an excessive delay added by the TCP protocol will reduce the quality of the voice connection. TCP will be used as a backup option.

And which ports is it better to start the server on? {more...}

  "And which ports should I run the server on?" - you ask? I spent a long time studying this issue and concluded that it's best to use TCP 443 and UDP 443 ports. TCP 443 will work in 99% cases only if you do not stumble on the DPI firewall (Deep Packet Inspection), which will block the connection, because OpenVPN Does not use native SSL/TLS handshake. If you use HTTPS service on the same server, it is not necessary to hang these services on different ip addresses - you can use the port-share function in OpenVPN. With this option, you can hang OpenVPN on port 443, and HTTPS, for example, on port 8443.

port-share 127.0.0.1 8443

In this case, the HTTPS service should listen on port 8443 to ip address 127.0.0.1, and Firewall on the server itself should allow connections to the address 127.0.0.1 on port 8443. More in detail, we will return later. OpenVPN will listen to all connections on port 443, and if it understands that traffic is not intended for OpenVPN, it will redirect it to port 8443. At the same time, for the external user everything will be transparent: your HTTPS site will be accessible at the usual port 443. By the way , I would recommend that you do so in any case by hanging at least a simple page on HTTPS, since some DPI send test SSL handshake packets, which in this case will fall on the HTTPS service. In this case, DPI will receive the correct answer, and will have to skip traffic. More details about this option I will discuss in my next articles. As for UDP, if you have the time and desire, you can add several configuration files on the most common ports: 53, 69, 123, 1194, but none of these ports have such a high percentage of the likelihood of a lack of filtering on the Internet, as for TCP 443. But the combination of different ports the probability of reaching your server still increases. To use UDP 443 I recommend, in particular, in view of all the growing popularity of the QUIC development protocol of Google - this means that over time this port should also be increasingly available on the Internet. At least, I hope so.

{less}

Create the file:

vi /etc/openvpn/tcpserver.conf

Below is the file configuration with comments:

Full config is below: {more...}


Full config is below (please note that DH keys filename depends on the key size you choose later):
;local a.b.c.d
proto tcp
port 443
;port-share 127.0.0.1 8443
mssfix 1300
tun-mtu 1400
dev tun0
tls-server
;tls-version-min 1.2
;tls-auth mykeys/ta.key 0
;tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384
comp-lzo no
;comp-lzo yes
;compress lz4-v2
;push "compress lz4-v2"
cipher AES-256-CBC
auth SHA512
ca mykeys/ca.crt
cert mykeys/ovpn.crt
key mykeys/ovpn.key
dh mykeys/dh4096.pem
server 192.168.2.0 255.255.255.0
log-append /var/log/openvpn/openvpn.log
verb 0
status /var/log/openvpn/openvpn-status.log
push "redirect-gateway def1 bypass-dhcp"
push "dhcp-option DNS 8.8.4.4"
push "dhcp-option DNS 8.8.8.8"
user nobody
group nobody
;explicit-exit-notify 1
client-to-client
duplicate-cn
max-clients 100
keepalive 10 120
persist-key
persist-tun

{less}

;local a.b.c.d Uncomment by removing the ";" and write down your ip address, or leave this line unchanged so that OpenVPN listens on all ip addresses.
proto tcp
port 443
Setting on port 443.
;port-share 127.0.0.1 8443 If we want to share external connections on 443 port with the SSH or HTTPS WEB service (we'll take a closer look in other articles). The second service should listen on port 8443 to the address 127.0.0.1. The address can be changed to an external one.
mssfix 1300
tun-mtu 1400
I recommend to reduce the size of the TCP MSS and/or MTU on the TUN interface to ensure that the VPN works on a poor Internet connection. If  bandwidth is more important to you and your client and server Internet connections are fine - you can not enable these options.
dev tun0 Set the tunnel number, because we will have 2 TUN interfaces, and we want to know exactly which tunnel number for which type of traffic - TCP or UDP.
tls-server
;tls-version-min 1.2
;tls-auth mykeys/ta.key 0
;tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384
Turn on TLS and strengthen the TLS control channel: enable additional PSK authentication to increase the security of the TLS channel control, protecting against DOS. On the server side, the value should be set "0". Three last options switching off for a while, in roder to simplify troubleshooting if needed.
comp-lzo no
;comp-lzo yes
;compress lz4-v2
;push "compress lz4-v2"
I recommend that you turn off the compression, if you are not certain that you need it and it is obviously useful. Just in case, I give these options here. The configuration with the client must be identical. lzo is an old algorithm for old clients. lz4 is a newer algorithm, fsater and less CPU intensive.
cipher AES-256-CBC
auth SHA512
Set the encryption protocol and enable packet authentication. This is the maximum possible algorithms that are compatible with client software. For newer client software, I recommend using AES-256-GCM encryption.
ca mykeys/ca.crt
cert mykeys/ovpn.crt
key mykeys/ovpn.key
dh mykeys/dh4096.pem
We set path to keys and certificates. 

DH keys filename depends on the key size you choose later.

server 192.168.2.0 255.255.255.0 We set the subnet range from which the addresses to clients will be issued.
log-append /var/log/openvpn/openvpn.log
verb 0
Enabling logging, if necessary, and setting the desired level.
status /var/log/openvpn/openvpn-status.log Logging current connections status.

 

push "redirect-gateway def1 bypass-dhcp"

Push the client to route all traffic via the VPN server side. If this option is disabled and only specific routes are forced to the client, client will still be able to route all traffic through the server, unless explicitly forbidden in iptables.
push "dhcp-option DNS 8.8.4.4"
push "dhcp-option DNS 8.8.8.8"
Pushing Google DNS servers.
user nobody
group nobody
Starting service from unprivileged user.
;explicit-exit-notify 1 For TCP, you need to comment out this option, because with TCP this does not work, and the service are not going to start. The option is necessary to inform the client when service is being restarted, so that the client could automatically reconnect. In TCP, this is implemented using the protocol itself. Let's leave it here for UDP.


client-to-client
duplicate-cn
max-clients 100

We allow traffic between customers.
We allow the use of the same keys for several users (you can disable if you really plan each user to issue their keys).

The maximum number of connections is not more than 100.
keepalive 10 120 Ping every 10 seconds, and if nothing is received within 120 seconds, then the remote host is unavailable.
persist-key
persist-tun
Do not reread the key files when restarting and do not rediscover the TUN / TAP device during the restart.
Generating keys and certificates

Creating directories to store scripts and generate the keys in it:

mkdir /etc/openvpn/scriptkeys

Copy the scripts to generate into our new directory:

cp /usr/share/easy-rsa/2.0/* /etc/openvpn/scriptkeys/

Open the file:

vi /etc/openvpn/scriptkeys/vars

Making changes:

export KEY_SIZE=4096
export KEY_COUNTRY="RU"
export KEY_PROVINCE="MO"
export KEY_CITY="Dubna
export KEY_ORG="MyCompany"
export KEY_EMAIL="email@mycompany.com" export KEY_OU="MyOrgUnit"

Specify the keys folder, key size and data that will be displayed in the certificate.
You can set key size to 3072 or leave default (2048). Default is enough and 3072 is a good security and server computations load ratio.

export KEY_NAME="ovpn"

Specify the same name as given in the configuration of our files - a link to the file * .crt and * .key.

export KEY_CN=openvpn.example.com

Domain or subdomain of your server.

We are jumping to the generation of keys and certificates. Go to our directory and specify the source to fill in the standard fields:

cd /etc/openvpn/scriptkeys/
source ./vars

Clean all issued certificates:

./clean-all

We create a certificate of the Certification Center (CA):

./build-ca

Generate the key and certificate for the server:

./build-key-server ovpn

Answer additional questions:

A challenge password []:
An optional company name []:
Leave empty.
Sign the certificate? [y/n]:y Confirm the certificate sign.

We generate keys for exchange by the Diffie-Hellman algorithm (takes much time):

./build-dh

We need to generate the keys and certificate for the clients:

./build-key client

Answer additional questions:

Common Name (eg, your name or your server's hostname) [client]: Different names for different clients.
A challenge password []: An optional company name []: Leave empty.
Sign the certificate? [y/n]:y Confirm the certificate sign.
1 out of 1 certificate requests certified, commit? [y/n]y Confirm.

Create a directory and copy the necessary keys (mind you key size and filename here):

mkdir /etc/openvpn/mykeys
cd /etc/openvpn/scriptkeys/keys/
cp ca.crt ovpn.crt ovpn.key dh4096.pem client.crt client.key /etc/openvpn/mykeys

Create a folder for the openvpn logs:

mkdir /var/log/openvpn

We enable and start the service, check that there are no errors at startup:

systemctl enable openvpn@tcpserver.service
systemctl start openvpn@tcpserver.service

Service starting errors you can check by commands:

openvpn@tcpserver.service
journalctl -xe
cat /var/log/openvpn/openvpn.log

We need to install a package of network utilities (in particular, we need netstat utility):

yum install net-tools -y

Checking that the service listens on port 443:

netstat -tulpan | grep 443

The output should be something like this:

tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 2744/openvpn

Everything, our service works! We can duplicate the configuration for udp:

cp /etc/openvpn/tcpserver.conf /etc/openvpn/udpserver.conf
vi  /etc/openvpn/udpserver.conf

Need to make 4 changes (change tcp to udp, tun0 to tun1, assign another network, enable the option mentioned above):

proto udp
dev tun1
server 192.168.3.0 255.255.255.0
explicit-exit-notify 1

Enable and start the service by udp, check that there are no errors at startup:

systemctl enable openvpn@udpserver.service
systemctl start openvpn@udpserver.service

Checking that the service listens on port 443 for both protocols:

netstat -tulpan | grep 443

The output should be something like this:

tcp    0      0 0.0.0.0:443      0.0.0.0:*       LISTEN      2744/openvpn
udp   0     0 0.0.0.0:443       0.0.0.0:*                          3400/openvpn

If you don't see both ports are being listened then do:

Turn off SElinux:

vi /etc/sysconfig/selinux

Change the SELINUX value to:

SELINUX=disabled

reboot and check again:

netstat -tulpan | grep 443

Now check that we have correctly created tun interfaces and that the correct mtu is configured on them:

ifconfig

The output should be something like this (output excluding standard interfaces):

tun0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST> mtu 1400
inet 192.168.2.1 netmask 255.255.255.255 destination 192.168.2.2
inet6 fe80::dcaa:3783:b687:71ad prefixlen 64 scopeid 0x20


unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 100 (UNSPEC)
RX packets 647 bytes 42049 (41.0 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 3 bytes 144 (144.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

tun1: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST> mtu 1400
inet 192.168.2.1 netmask 255.255.255.255 destination 192.168.2.2
inet6 fe80::ba0d:e3d8:a7cf:941b prefixlen 64 scopeid 0x20


unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 txqueuelen 100 (UNSPEC)
RX packets 19533 bytes 1249608 (1.1 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 3 bytes 144 (144.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

Standard CentOS 7 FirewAll configuration

In our example, we'll use the classic and more familiar iptables.

systemctl stop firewalld
systemctl mask firewalld
Stop and turn off the firewalld service.
yum install iptables-services -y
systemctl enable iptables
systemctl start iptables
Install andstart iptables.

Open the configuration file of iptables to allow traffic on ports 443 UDP / TCP:

vi /etc/sysconfig/iptables

-Add the 2-4th rule.
- 3rd priavlo can not be included, if we did not touch the default rule "-A INPUT -i lo -j ACCEPT".
- 4th turn on if the port-share option is used - in this case we specify the port that should accept the unrecognized OpenVPN packages.
- Turn off the FORWARD rule by the symbol #:

-A INPUT -p tcp --dport 443 -j ACCEPT
-A INPUT -p udp --dport 443 -j ACCEPT
#-A INPUT -i lo -s 127.0.0.1 -d 127.0.0.1 -p tcp dport 843 -j ACCEPT

#-A FORWARD -j REJECT --reject-with icmp-host-prohibited

Restart iptables and check that we have configured everything properly:

systemctl restart iptables.service
iptables -vnL | grep 443

The output should be something like this:

0      0      ACCEPT      tcp     --      *     *     0.0.0.0/0      0.0.0.0/0     tcp dpt:443
0      0      ACCEPT      udp    --      *     *     0.0.0.0/0      0.0.0.0/0     udp dpt:443

We check that the tcp 443 port on the server is open, try to telnet from the workstation (the Windows component or a separate application must be installed):

telnet 443

Now you need to allowIP traffic forwarding on our CentOS 7, open the file:

vi /etc/sysctl.conf

We add a line:

net.ipv4.ip_forward = 1

Not finished yet with iptables. Now you need to go back to the iptables configuration file and add a line in the NAT section so that all traffic from the client is NAT'ed to the address of our server (if there is no NAT section, then you need to create it at the end of the file after the COMMIT line of the filter section):

*nat
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A POSTROUTING -s 192.168.2.0/23 -o eth0 -j MASQUERADE
COMMIT

eth0 -is your interface name.

If we have several addresses on the interface, and we want to NAT to a specific address, then instead of the above line, add:

-A POSTROUTING -s 192.168.2.0/23 -o eth0 -j SNAT --to

Restart iptables and check the output with the command:

systemctl reload iptables.service
iptables -vnL -t nat

The output should be something like this:

Сhain PREROUTING (policy ACCEPT 6 packets, 349 bytes)
pkts bytes target prot opt in out source destination

Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination

Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 MASQUERADE all -- * eth0 192.168.2.0/23 0.0.0.0/0

One step left until the configuration is complete. Restart the network service to activate the settings (ideally - restart the server to verify that everything works after the reboot):

systemctl restart network.service
Windows 7 client configuration:

OpenVPN GUI should be run as administrator, otherwise, it will not be able to add the necessary routes. The configuration file must be put in the directory: C:Program FilesOpenVPNconfig. Here it is created by default. Configurationcan be changedby right-clicking on the OpenVPN system tray icon-> edit configuration. In same directory you also need to put following files by connecting to your server, for example, using WinSCP (via SFTP to the port on which SSH is listening):
- /etc/openvpn/mykeys/ca.crt
- /etc/openvpn/mykeys/client.crt
- /etc/openvpn/mykeys/client.key
Add the following lines:

Full config is below: {more...}

Full config is below:
client
tls-client
verb 3
dev tun
mssfix 1300
tun-mtu 1400
persist-key
persist-tun
cipher AES-256-CBC
auth SHA512
comp-lzo no
remote 443 udp
remote 443 tcp-client
redirect-gateway def1
ping-restart 10
ca "ca.crt"
cert "client.crt"
key "client.key"
nobind
;auth-user-pass pass.txt
;key-direction 1
;tls-version-min 1.2
;tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384

{less}

client
tls-client
verb 3
dev tun
mssfix 1300
tun-mtu 1400
persist-key
persist-tun
cipher AES-256-CBC
auth SHA512
comp-lzo no
These options you already know (except that here we configure the client, not server).
remote  443 udp
remote 443 tcp-client
redirect-gateway def1
First we connect on UDP, if unsuccessfully, then by TCP.

Default gateway is VPN server.

ping-restart 10 If the server is unavailable by UDP for 10 seconds, a connection is made over TCP.
ca "ca.crt"
cert "client.crt"
key "client.key"
If the keys and certificates files are in the config directory, you do not need to write the full path, by default they are taken from this directory.
nobind The IP stack allocates a dynamic port for the response packets.
;auth-user-pass pass.txt
;key-direction 1
;tls-version-min 1.2
;tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384
Add and comment authentication by username and password. Temporarily turn off TLS authentication with additional options.
Andoid client configuration:

Install the OpenVPN client from the Play Market, add a new profile:
- In the section "Remote servers" we set our server 2 times: UDP + TCP, also in this section for each server we install MTU 1400 and MSS 1300.
- In the "Authentication" section, select the "TLS Certificates" authentication mode;
-add certificates: client.crt - certificate, client.key - private key, ca.crt - certification authorities (you can copy files to Android directly from the server by installing  FTP serveron Android device, or via USB, after downloading by connecting with WinSCP) ;
- add in the section "Cryptography": encryption - cipher AES-256-CBC, authentication algorithm - auth SHA512;
- in the "Options" section, add restart on ping 10, data compression LZO none;
- in the "Custom options" section add tls-version-min 1.2 and tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384;
- start connection.

Screenshot Screenshot

Screenshot Screenshot

  Open ripe.net website and check the ip address assigned:

Screenshot

 

 

 

 

 

 

Advanced security settings

Let's try to add the second factor, adding user authentication, open the configuration files on the server and add the plugin:

plugin /usr/lib64/openvpn/plugins/openvpn-plugin-auth-pam.so login

Restart both services:

systemctl restart openvpn@udpserver.service
systemctl restart openvpn@tcpserver.service

Create user account and assign him a password:

useradd testuser
passwd testuser

Turn off warning messages in the logs regarding replay packets in the UDP server configuration. Periodically, administrators are faced with such messages, which may be caused by duplicate packets, for example, on virtualization:

mute-replay-warnings

Try to connect from our Android client - we do not pass authorization. Now, to connect, you need to select the "TLS + password certificate" authenticate mode in the application in the "Authentication" section and, if you want,  you can save the user name and password here. If you have specified everything correctly, then everything should work just fine.

Now we will generate a pre-shared key on the server for packet authentication:

cd /etc/openvpn/mykeys/
openvpn --genkey --secret ta.key

You need to restrict access to the keys:

cd /etc/openvpn/mykeys/
chmod 600 *.key

Turn on the server options that I mentioned earlier:

tls-auth mykeys/ta.key 0
tls-version-min 1.2
tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384

Again restart both services:

systemctl restart openvpn@udpserver.service
systemctl restart openvpn@tcpserver.service

In the Android client software, under the "Authentication" section, add checkbox TLS authentication with direction "1" and import the key. Try to reconnect - everything should work.

Let's try to connect with the advanced security settings using the Windows client.
To add a second factor and TLS authentication you need to configure it in the configuration:

auth-user-pass pass.txt

pass.txt should be located in the config directory, and the file should contain only 2 lines: login and password (without additional directives).

 

key-direction 1
tls-version-min 1.2
tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384


# # 2048 bit OpenVPN static key #
-----BEGIN OpenVPN Static key V1-----
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-----END OpenVPN Static key V1-----

There is a small trick here. The tls key could not be reade from the file, it must be set inline, framed in directives. And direction of the key is indicated by slightly different directive.
Routing only specific routes through the OpenVPN server

On the server we change
-for UDP configuration:

;push "redirect-gateway def1 bypass-dhcp"
push "route 192.168.2.0 255.255.255.0"

-for TCP configuration:

;push "redirect-gateway def1 bypass-dhcp"
push "route 192.168.3.0 255.255.255.0"

Restart both services:

systemctl restart openvpn@udpserver.service
systemctl restart openvpn@tcpserver.service

On the Android client in the "Routing" section,  remove  "Redirect  gateway" checkbox (if checked).
On the Windows client, you just need to comment out the line:

;redirect-gateway def1

After connecting to the Android client, you can check the routes in the "Routing" section, on Windows you can check the "route print" command from the command line.

I hope that you liked the instructions  and they were easy to understand, I want to thank you for your attention. You can leave your comments and questions below! I will try to answer them with pleasure!

 

If using materials reference to the site is obligatory!