A simple VPN tunnel with FreeBSDMarch 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:
- setup a VPN on a dedicated host,
- use RSA certificates to authenticate server and clients,
- use a shared secret on top of it all to mitigate potential DoS attacks,
- use Diffie-Hellman for key exchange,
- route all client traffic through the VPN once the tunnel is up.
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
echo 'openvpn_enable="YES"' >> /etc/rc.conf'
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
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:
KEY_CN: CN stands for “Common Name” and must match your server’s hostname,
- and set
KEY_SIZEto at least 2048.
Then, source this file with
. vars, and we will start generating your
Let’s start by cleaning the
Then, we will build your local CA. This step will require that you confirm some
of the values you updated earlier in the
You also need a certificate and key for your server. The
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
Finally, generate Diffie-Hellman parameters:
This can take a some time depending on your hardware. Coffee break? :)
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
To connect to your OpenVPN server, your clients will need to be authenticate
with signed certificates themselves. To generate new client certificates, use
To connect to your VPN server, you will need to securely transmit these files to your client:
- The client’s certificate and private key:
- and the server’s certificate and private TLS Authentication key:
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
echo 'gateway_enable="YES"' >> /etc/rc.conf
And activate the following sysctl flag:
Update PF’s configuration by adding a few lines to
/etc/pf.conf, in order to
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. :)