My previous article, related to OpenVPN, concerned the installation of the OpenVPN server in bridge mode. This solution seemed to me the only possible solution when running the OpenVPN server not on the gateway. The disadvantage of this solution is that you need to put the network interface into promiscuous mode, which is usually not allowed in any virtual environment.
This article will describe another solution in which OpenVPN will work in routing mode, but outgoing internal traffic will be SNAT modified, ie, traffic will loks like come from the OpenVPN server itself. This solution is suitable even for AWS EC2 instances with one network interface.
This POC we will do on Fedora 26, just because it is already installed on my test VM. This choice affected additional commands related to selinux.
I generally disable the firewall and will not refer to it during the article.
# systemctl stop firewalld # systemctl disable firewalld
Obviously, install OpenVPN:
# dnf install -y openvpn
At the very beginning, it is very important to determine the authentication of users. In the previous example, there were no real users in the OS, only certificates used as authentication. In this example, I come with the opposite approach. Certificates will be almost not used, but real local users will be created with strong passwords and forbidden login. This will simplify the management of certificates, but will require user management.
As an additional limitation, only users belonging to the ovpn group will be allowed to use VPN.
# groupadd ovpn # useradd -g ovpn -s /sbin/nologin user1 # passwd user1 # useradd -s /sbin/nologin user2 # passwd user2
As already mentioned, a minimal, self-signed certificate will be used instead of a real CA control. This command will generate the certificate /etc/ssl/ovpn-cert.pem, which will also be used as a CA certificate, and an unencrypted private key /etc/ssl/ovpn-nopasskey.pem. Location of files is important to the Fedora/selinux environment. Of course, you can adapt the certificate subject to your own more realistic parameters.
# openssl req -x509 -days 3650 -subj "/O=INFO/OU=VOLEG/CN=OVPN" -newkey rsa:2048 \ -nodes -keyout /etc/ssl/ovpn-nopasskey.pem -out /etc/ssl/ovpn-cert.pem # openssl dhparam -out /etc/ssl/dh2048.pem 2048
You can copy the sample server configuration file and then edit it. Reading the manual is also helpful.
# cp /usr/share/doc/openvpn/sample/sample-config-files/server.conf /etc/openvpn/server/ # vi /etc/openvpn/server/server.conf # man openvpn
Here is the configuration file of my server with comments in it:
# cat /etc/openvpn/server/server.conf # Use another than standart port: port 1234 proto udp # See https://whois.arin.net/rest/net/NET-100-64-0-0-1.html about this subnet. server 100.99.1.0 255.255.255.0 # I've added only my destination server here, # you should add all destination subnets, multiple "push" allowed. push "route 192.168.122.100 255.255.255.255" # Crypto staff. We had generated these files already. tls-server ca /etc/ssl/ovpn-cert.pem cert /etc/ssl/ovpn-cert.pem key /etc/ssl/ovpn-nopasskey.pem dh /etc/ssl/dh2048.pem # Authentication tuning. Read manual. duplicate-cn verify-client-cert optional plugin /usr/lib64/openvpn/plugins/openvpn-plugin-auth-pam.so openvpn # Pretty common staff, read manual. dev tun keepalive 10 120 cipher AES-256-CBC compress lz4 verb 3 explicit-exit-notify 1 max-clients 10 user nobody group nobody persist-key persist-tun # File names and location are hard-coded in selinux rules. ifconfig-pool-persist /etc/openvpn/ipp.txt status /var/log/openvpn-status.log
As you can see from the configuration file, the PAM plugin is used for authentication. Let's create its configuration file /etc/pam.d/openvpn. As you can see, only users from the "ovpn" group are allowed to authenticate by this service.
#%PAM-1.0 auth required pam_env.so auth required pam_succeed_if.so quiet user ingroup ovpn auth sufficient pam_unix.so nullok try_first_pass auth required pam_deny.so account required pam_unix.so account sufficient pam_localuser.soNOTE: Selinux related actions:
# touch /etc/openvpn/ipp.txt /var/log/openvpn-status.log # restorecon -r /etc/ssl /etc/openvpn /var/log/openvpn-status.log /etc/pam.d/openvpn # ### Probably you will need install policycoreutils-python-utils to run next command: # semanage port -a -t openvpn_port_t -p udp 1234 # <- Replace this to port number in your config file.
Enable and start the openvpn server. If your configuration files named as mine: /etc/openvpn/server/server.conf, then do:
# systemctl enable email@example.com # <- Replace this to name of your config.file. # systemctl start firstname.lastname@example.org
For testing, copy the CA certificate to the client and verify the connection using a similar command. Later create a working ".ovpn" file.
# openvpn --remote 192.168.122.114 --port 1234 --dev tun --compress lz4 --client --ca /tmp/ovpn-cert.pem --auth-user-pass
Check that you can connect using user1 but not user2.
First of all, make sure that the forward is enabled on the server. Add the rule to /etc/sysctl.conf and run sysctl -p
# cat /etc/sysctl.conf net.ipv4.ip_forward = 1
You can use firewalld or iptables-services to manage firewall rules. I will not review all other FW rules (for example, you must allow an incoming UDP port for your OpenVPN and connection-related traffic).
Add the following rule to masquerade all traffic from VPN clients. My fedora instance has a network interface named ens3, so it is shown here, for you it can be eth0. The subnet that appears here as a source subnet must match the same one used in the "server" directive in the OpenVPN server configuration file.
# iptables -t nat -I POSTROUTING -o ens3 -s 100.99.1.0/24 -j MASQUERADE
Test connection and ping remote IP addresses.
Install it and run it, answer questions. The result will be at ~/.google_authenticator.
# dnf install -y google-authenticator # google-authenticator
I will use a common authentication file for all users. It's less secure, but easier to manage. Security is always a trade-off between the complexity of management and the level of security. Then I move the resulting file to a shared location:
# mv ~/.google_authenticator /etc/openvpn/gauth.conf # chmod 600 /etc/openvpn/gauth.conf # head -1 /etc/openvpn/gauth.conf ZBHW3JHX644TEPZ3D3QODXV3OU
The result of the last command is the code that you must enter into the Google Authenticator application on the remote device. Alternatively, you can use the CLI "oathtool" for Linux.NOTE: Selinux related actions:
# semanage fcontext -a -t openvpn_etc_rw_t /etc/openvpn/gauth.conf # restorecon /etc/openvpn/gauth.conf
Fix PAM /etc/pam.d/openvpn file to include Google authenticator module:
#%PAM-1.0 auth required pam_env.so auth required pam_succeed_if.so quiet user ingroup ovpn auth requisite pam_google_authenticator.so secret=/etc/openvpn/gauth.conf user=root forward_pass auth sufficient pam_unix.so nullok use_first_pass auth required pam_deny.so account required pam_unix.so account sufficient pam_localuser.so
Google authenticator wants PAM file have more restrictive permissions, then do:
# chmod 600 /etc/openvpn/gauth.conf
Try to connect to openvpn again. You must put your password, combined with the gauth code in the password field:
.. Enter Auth Username: user1 Enter Auth Password: ********XXXXXX ..
A code itself you can get by command:
$ oathtool --totp -b ZBHW3JHX644TEPZ3D3QODXV3OU # <- The long code is from "head -1 /etc/openvpn/gauth.conf
A code will be replaced every 30 second, hurry.
Update: The default openvpn configuration will attempt to renegotiate the session key every hour and force re-authentication. Because of the MFA, the password will be incorrect, then the session will hang. You will be forced to disconnect and reconnect manually. If you want your sessions be longer than one hour, then add reneg-sec 36000 directive to both client and server configuration.