A simple VPN tunnel with FreeBSD

March 21, 2014

I live in Mexico, but work for a french tech company. As I spend most of my days online, and can’t always encrypt everything, I often use a VPN. This is especially important when working from an untrusted network (my favorite coffee dealer’s public wifi comes to mind), although I tend not to trust any network these days…

I had been (ab) using the company’s own VPN for personal communications for a while, and I’d rather not, so I took some time so setup OpenVPN on a modest Kimsufi box, to finally get my private VPN. Whether you trust OVH’s network is a personal matter, but still, I believe it safer that a random wifi hotspot.

This post highlights the few required steps, to setup your OpenVPN server on FreeBSD (release 10 at the time of this writing). The goal is to:

Installing and configuring OpenVPN

Start by installing the relevant FreeBSD port: using pkg-ng, it’s a simple pkg install openvpn. If you’d rather use ports, chances are you know what to do.

You will have to enable the daemon in your rc.conf:

echo 'openvpn_enable="YES"' >> /etc/rc.conf'

Certificates

Here, I could write a bunch of openssl incantations to generate a CA, server keys, signing requests, etc. Well no, I’m too lazy, and using the easy-rsa package will probably save you some time too: pkg install easy-rsa.

This should have created a /usr/local/share/easy-rsa/, which contains a few shell scripts to handle server and client certificates generation simply. Once this package is installed, it is recommended that you copy the script directory elsewhere, as you will need to customize it, and future package upgrades may overwrite your changes.

In this example, I will copy easy-rsa’s scripts to OpenVPN’s configuration directory:

mkdir -p /usr/local/etc/openvpn
cp -vr /usr/local/share/easy-rsa /usr/local/etc/openvpn/
export EASY=/usr/local/etc/openvpn/easy-rsa

That should settle it, now cd $EASY and edit the vars file in there, in order to fit the following variables to your particular environment:

Then, source this file with . vars, and we will start generating your certificates.

Let’s start by cleaning the keys directory:

./clean-all

Then, we will build your local CA. This step will require that you confirm some of the values you updated earlier in the vars file:

./build-ca

You also need a certificate and key for your server. The build-key-server script is used to handle that part. It takes one parameter that should match your server’s common name ; although, unless you are using a commercial CA to sign your server keys, it is not that important: leaving server is fine here.

./build-key-server server

Finally, generate Diffie-Hellman parameters:

./build-dh

This can take a some time depending on your hardware. Coffee break? :)

Using TLS-Auth

TLS-Auth is a small security improvement that signs (HMAC) SSL/TLS handshake packets with a shared key. It does not cost much, and improves security a little (e.g. it allows OpenVPN to drop weird packets before they reach your super trusty SSL implementation).

However, being a shared secret, you need to distribute the file to all your clients.

openvpn --genkey --secret $EASY/keys/ta.key

Client certificates

To connect to your OpenVPN server, your clients will need to be authenticate with signed certificates themselves. To generate new client certificates, use the build-key script:

./build-key laptop

To connect to your VPN server, you will need to securely transmit these files to your client:

Server configuration

Here is a sample one. Update it to fit your environment, and store it under /usr/local/etc/openvpn/openvpn.conf. Be smart. :)

local $SERVER_IP
port 1194
proto udp
dev tun
server 10.8.0.0 255.255.255.0
ifconfig-pool-persist ipp.txt
# Allow clients to "see" each other.
client-to-client
keepalive 10 120
comp-lzo
max-clients 16
user nobody
group nobody
persist-key
persist-tun

cipher AES-256-CBC
ca $EASY/ca.crt
cert $EASY/server.crt
key $EASY/server.key
dh $EASY/dh2048.pem
tls-auth $EASY/ta.key 0

status /usr/local/etc/openvpn/openvpn-status.log
verb 4
mute 20
mute-replay-warnings

# Uncomment the following if you're running a local DNS cache (such as unbound),
# and want to instruct your clients to use it. In that case, don't forget to
# update Unbound configuration too, to accept requests on the 10.8.0.0/24 
# network.
#push "dhcp-option DNS 10.8.0.1"
push "redirect-gateway"
push "redirect-gateway def1 bypass-dhcp"

You should be set. Start the OpenVPN service with service openvpn start.

Routing and NAT

Chances are your server is not configured to do any routing at all. Maybe it was meant to be a simple server, rather than a router. In that case, enable the “gateway” pseudo-service in /etc/rc.conf:

echo 'gateway_enable="YES"' >> /etc/rc.conf

And activate the following sysctl flag:

sysctl net.inet.ip.forwarding=1

Update PF’s configuration by adding a few lines to /etc/pf.conf, in order to NAT OpenVPN’s tun interface to the rest of the world:

# The ext_if name is probably different on your system...
ext_if = "em0"
vpn_if = "tun0"
vpn_net = "10.8.0.0/24"

nat on ! $vpn_if from $vpn_net to any -> ($ext_if)

Then, restart PF: /etc/rc.d pf restart.

And that should be it for your server: it is running, accepting authenticated clients, and clients’ connections are NAT-ed to the outer network.

Sample client setup

Alright, if you have no clue on how to setup a client, this is the OpenVPN configuration I use on a laptop:

dev tun
remote $SERVER_IP 1194
client
tls-client
verify-x509-name server name
ns-cert-type server
tls-auth ta.key 1
ca ca.crt
cert laptop.crt
key laptop.key
cipher AES-256-CBC
comp-lzo
ping-timer-rem
resolv-retry infinite
persist-tun
persist-key
verb 1
ping 15
ping-restart 30

And that’s about all you need. Simple enough. :)