OpenVPN on FreeNAS 11.1 with Multi Factor Authentication using Google Authenticator

Drew Foehn
7 min readApr 2, 2019

--

Have you ever wanted to access your FreeNAS server and local network whilst on the road? Are you worried about your password being compromised so want an extra layer of security? Are you comfortable using the shell, understand basic networking, are amenable to getting your hands dirty and being a super user? This guide is for you.

Note; this setup is for a server named ‘freenas’ on a LAN with a subnet of 192.168.1.0/24, domain name *.home and a VPN hosts subnet at 192.168.2.0/24. The freenas server is behind a router that forwards UDP port 443 through to the OpenVPN jail running on UDP port 1194. OpenVPN complains about using a very generic Class C network address so you should probably change your networking setup so that you don’t conflict with the many unsecured dirty wifi networks you’re probably connecting to.

If you wish to change these values, pay attention when creating the openvpn.cfg, client.cfg and ipfw.rules files. I will call it out again later on.

Most of the config is placed in /usr/local/etc. This guide assumes you know what a tcsh prompt looks like, a HEREDOC and how to use basic vi.

Most of these commands can be scripted and could theoretically be made into a shell script. If enough people want it, I might put up a gist.

Disclaimer, i’m not an openvpn config specialist. If there is something not working for your environment I may not be able to help you, I suggest you try Google. I am also not certain this is the most secure way to configure openvpn.

To Begin;

Create a jail, name it openvpn_1.

Use the classic UI if using FreeNAS ≥ 11.2. Click the advanced settings and set jail download path to http://download.freenas.org/jails/11/x64/. I had to do this when using FreeNAS 11.1, it maybe fixed in 11.2.

Login to the jail and update the FreeBSD packages

user@freenas # sudo jexec `jls | awk ‘/openvpn_1/ { print $1 }’` tcsh
root@openvpn_1:/ # pkg update
root@openvpn_1:/ # pkg upgrade -y

This requires that you have an admin user that is capable of sudo’ing to root.

Fix missing libdl error in the FreeNAS 11 jail template

root@openvpn_1:/ # ln -sf /lib/libc.so.7 /usr/lib/libdl.so.1

This is required because the FreeNAS jail template was created just as FreeBSD stopped supporting 11.1 and all packages built moving forward are built against 11.2 which no longer has libdl library built into the libc library. If you don’t do this step you will likely receive this error when running openvpn:

Shared object “libdl.so.1” not found, required by “openvpn”

Install openvpn and google-authenticator packages

root@openvpn_1:/ # pkg install -y openvpn pam_google_authenticator

This uses the FreeBSD packaging system to install the openvpn package, the pam_google_authenticator package and all the required dependencies.

Create OpenVPN config and certs directory

root@openvpn_1:/ # umask 077
root@openvpn_1:/ # mkdir -p /usr/local/etc/openvpn/{certs,client-config,keys}

It is good security practice to secure configuration files that have sensitive information about your network in them. To learn more about umask see the man page.

Create certificate authority (CA), server certificates and copy to certs directory in jail. You can use either the easyrsa package or FreeNAS certs webgui.

This is outside the scope of this guide but for more information see this guide. You want to copy the certs and keys to the folders you created earlier. (/usr/local/etc/openvpn/{certs,keys}). The server configuration below has specific filenames these files should be. The Certificate Authority certificate should be saved to certs/ca.crt. The server certificate should be saved to certs/openvpn-server.crt and private key to keys/openvpn-server.key. The Diffie Hellman key should be saved to certs/dh.pem.

Generate a secure TA key

root@openvpn_1:/ # openvpn --genkey --secret /usr/local/etc/openvpn/keys/ta.key

This is useful in protecting you from man in the middle attacks and is suggested as the new standard in OpenVPN TLS configs

Create a OpenVPN server config file

root@openvpn_1:/ # cat << \CONF > /usr/local/etc/openvpn/openvpn.conf
#server config
port 1194
proto udp
dev tun
mode server
tls-server
ca certs/ca.crt
cert certs/openvpn-server.crt
key keys/openvpn-server.key # This file should be kept secret but shouldn’t be encrypted otherwise openvpn won’t start
dh certs/dh.pem
server 192.168.2.0 255.255.255.0
ifconfig-pool-persist ipp.txt
push “route 192.168.1.0 255.255.255.0”
keepalive 10 120
tls-auth keys/ta.key 0 # This file is secret
remote-cert-tls client
cipher AES-256-CBC
user nobody
group nobody
persist-key
persist-tun
status openvpn-status.log
verb 3
explicit-exit-notify 1
plugin /usr/local/lib/openvpn/plugins/openvpn-plugin-auth-pam.so openvpn
\CONF

Note, config options tls-server, mode serverand plugin for using the openvpn pam auth. If you plan to change the network addresses, make sure you change the line that begins with server and push “route… to the VPN hosts network address and the LAN hosts address.

Create client.cfg

root@openvpn_1:/ # vi /usr/local/etc/openvpn/client-configs/<userid>.cfg
# client config
client
dev tun
proto udp
remote <public-vpn-address> 443
resolv-retry infinite
nobind
persist-key
persist-tun
key-direction 1
tls-client
remote-cert-tls server
cipher AES-256-CBC
dhcp-option DNS <dns-server>
dhcp-option DOMAIN <search-domain> # helpful
redirect-gateway def1
auth-user-pass
verb 3
<ca>
#PASTE certs/ca.crt
</ca>
<cert>
#PASTE USER CERT
</cert>
<key>
#PASTE USER ENCRYPTED OR UNENCRYPTED KEY preferably encrypted
</key>
<tls-auth>
#PASTE contents of keys/ta.key
</tls-auth>

Take note of directives key-direction, tls-client and remote-cert-tls server and auth-user-pass. The directiveredirect-gateway def1 will make the VPN connection route all traffic through your home network, you can comment this out if this is not what you want to do. I would keep it because it encrypts all traffic and allows you to feel comfortable accessing unsecured wifi networks. Replace <userid> with a userid you will create later, <public-vpn-address> with address your server resolves to, <dns-server> the local DNS server, <search-domain> so that clients don’t need to add the full local domain “.home” to URL’s.

Create a gauth user that can’t login

root@openvpn_1:/ # pw adduser gauth -w no -s /sbin/nologin -d /usr/local/etc/openvpn/google-authenticator

This user is used to create authentication tokens and for the PAM module to impersonate when checking the login credentials. For more information on using pw check the man page.

Create gauth config directory

root@openvpn_1:/ # mkdir -p /usr/local/etc/openvpn/google-authenticator
root@openvpn_1:/ # chown gauth:gauth /usr/local/etc/openvpn/google-authenticator

Changing the ownership to gauth and group gauth is good security practice and secures our tokens.

Create PAM config

root@openvpn_1:/ # cat << \CONF > /etc/pam.d/openvpn 
auth required /usr/local/lib/pam_google_authenticator.so secret=/usr/local/etc/openvpn/google-authenticator/${USER} user=gauth forward_pass
\CONF

This PAM config describes the module to use when openvpn uses PAM to authenticate. Note the ${USER} is not interpreted by the shell because of the backslash before the HEREDOC statement \CONF. The param user=gauth is telling the PAM module to impersonate gauth when checking the supplied credentials.

Create a VPN user with random password

root@openvpn_1:/ # pw useradd <userid> -s /sbin/nologin -w random -d /

Replace <userid> with the user id you’d like to login to the VPN with. Note the password, you will use this to connect to the VPN with. If you want to make the password something easier to remember you can remove the -w random option.

Create GoogleAuth Token for user

root@openvpn_1:/ # su -m gauth -c “google-authenticator -t -d -r3 -R30 -f -l \”OpenVPN Login\” -s /usr/local/etc/openvpn/google-authenticator/<userid>”

Replace <userid> with user id you created in the last step. The output of this command will give you a string URL and a QR Code a user can use to add the MFA seed to the Google Authenticator app.

Everything else can be created with default permissions

root@openvpn_1:/ # umask 022

This makes all new files created in this session to have the permissions 755 (+rwX,go+rX user read/write and execute if a dir, group and all others read/execute if a dir)

Update firewall rules to NAT all 192.168.2.0 traffic

root@openvpn_1:/ # cat << \RULES > /usr/local/etc/ipfw.rules
#!/bin/sh
EPAIR=$(/sbin/ifconfig -l | cut -d’ ‘ -f2)
ipfw -q -f flush
ipfw -q nat 1 config if ${EPAIR}
ipfw -q add nat 1 all from 192.168.2.0/24 to any out via ${EPAIR}
ipfw -q add nat 1 all from any to any in via ${EPAIR}
TUN=$(/sbin/ifconfig -l | cut -d’ ‘ -f3)
ifconfig ${TUN} name tun0
\RULES

Again, if looking to change the network the VPN hosts are on, make sure you replace the 192.168.2.0 with whatever network you want to use, in CIDR notation.

Add init variables for openvpn

root@openvpn_1:/ # cat << \CONF >> /etc/rc.conf
openvpn_enable=”YES”
openvpn_if=”tun”
openvpn_configfile=”/usr/local/etc/openvpn/openvpn.conf”
openvpn_dir=”/usr/local/etc/openvpn/”
cloned_interfaces=”tun”
gateway_enable=”YES”
firewall_enable=”YES”
firewall_script=”/usr/local/etc/ipfw.rules”
\CONF

This file is used to load configuration variables into the init scripts and controls what services init.d starts.

Update syslogd config so openvpn logs to /var/log/messages, on the second to last line

root@openvpn_1:/ # vi /etc/syslog.conf
!openvpn
*.* /var/log/openvpn.log

Make sure you add this line on the second to last line. This will allow you to see status of openvpn connections in the FreeNAS web-frontend.

Update syslog rotator

root@openvpn_1:/ # mkdir -p /usr/local/etc/newsyslog.conf.d
root@openvpn_1:/ # cat << \CONF > /usr/local/etc/newsyslog.conf.d/openvpn
/var/log/openvpn.log 600 30 * @T00 ZC
\CONF

This makes the newsyslog daemon rotate the logging output of openvpn to keep log files more manageable in size.

Restart jail to automatically start openvpn, firewall and network settings

Copy config out of the jail and send it someplace where you can access it from a client

user@freenas:/ $ sudo tcsh -c “cp /mnt/<storage>/jails/openvpn_1/usr/local/etc/openvpn/client-configs/<userid>.cfg /mnt/<storage>/users/<userid>/”

Replace <storage> with the name of your NAS volume and <userid> as the user who you’re logged in as — this could be different than the user id you created to login to the VPN. You might also need to change the permissions on the copy.

Try connecting with an OpenVPN client using config file. User’s password is entered as:
<password><mfa token>

Tools and commands that can help you diagnose issues:

tail -n /var/log/openvpn.log
/sbin/ifconfig tun0 destroy
pamtester

Google is your friend.

If you made it this far, well done. You should now be able to access your local network securely whilst on the road.

--

--