Some while back I finally got a VPN set up which allowed remote access to my home LAN from both my iOS devices and my Windows laptop. This was a major accomplishment, for several reasons:
- I don’t know a lot about how routing works. Like everyone I rely on it all the time…but configuring it has almost always been “plug it into the network, follow the instructions, and pray it works.”
- VPN documentation appears to assume you live and breath routing protocols and terminology. In addition to knowing exactly how certificate signing authorities work, what’s the difference between a certificate and a private key, etc.
- For whatever reason the “usual” help sites — ServerFault.com, SuperUser.com, etc. — are generally not too helpful. In part due, no doubt, to the lack of knowledge mentioned above. It’s hard to ask reasonable/meaningful questions when you don’t quite know what you’re talking about.
So I was pretty happy when, finally, everything worked.
Shortly afterwards I decided to upgrade my Raspberry Pi to a model 4. Because, after all, how hard could that be? The upgrade was pretty straightforward. But one of the casualties was my VPN setup. After six months of on again/off again tinkering I finally got it working again. This article documents what I learned.
My Setup
My home LAN is really simple. It has a couple of wireless access points but is mostly based on Cat6 ethernet wiring. It’s connected to the internet via AT&T fiber. The gateway is an Arris BGW-210 router/modem I bought myself because the AT&T supplied equipment isn’t useful for doing anything other than having a LAN and a wireless access point (e.g., no VPN is possible, so far as I can tell).
My setup is so simple, in fact, I could probably get by without a local DNS. But while most of my hardware is Windows-based I do like to play around with Linux and I hate having to remember the IP addresses of devices. So I set up a Raspberry Pi running Debian Buster to act as the LAN’s local DNS and DHCP server. It gets that functionality from dnsmasq, which is a cool little program. The Pi also runs OpenVPN server to support my VPN needs.
Here’s a diagram of my setup and how I visualize the VPN I want to have:
Copying Over Config Files May Not Be Enough
The part of the story where I’m not sure what went wrong is the first, oldest part. I configured the new Pi by installing all the apps I was using on the old Pi and then copying over the app configuration files. That worked…but I think it bollixed some things in the cryptographic space. Because I was copying over certificates and keys which had been created on a different machine.
What I should’ve done, and eventually did do, was install the apps having cryptographic elements — openssl and OpenVPN — and then re-initialized them on the new machine. While that took some time it had the advantage of maintaining the “uniqueness” of all the cryptographic elements.
BTW, there are at least two ways of installing openssl on Debian via apt-get: the openssl package and the ssl-cert package. The latter is better, IMHO, because it takes care of some additional configuration/setup stuff which will otherwise have you scratching your head about why things aren’t working correctly.
Installing and Configuring OpenVPN
The two reasons I decided to go with OpenVPN are (a) it’s open source and (b) it has both Windows and iOS clients (and others, too, I believe). Theoretically any VPN client ought to be able to work with any VPN server… but you can go crazy getting things configured correctly, as I’ve learned the hard way. Better, if you can, to stay with one vendor end-to-end, server to client. You’ll probably still run into problems (I did, as I’ll explain below). But hopefully they’ll be fewer and at the very least you’ll have just one organization to deal with.
I was lucky to stumble across a great website that walks you through setting up OpenVPN on Debian Buster (thanx, Hitesh!). I’m going to summarize those instructions here because they ought to have a “backup” on the web.
Enable IP Forwarding
Set net.ipv4.ip_forward = 1 in /etc/sysctl.conf, and then update the system by executing:
$ sudo sysctl -p
Install OpenVPN
Copy the contents of /usr/share/easy-rsa to /etc/openssl/easyrsa so you can simplify a bunch of commands you need to run to configure OpenVPN (and isolate the changes you make to the /etc/openssl directory path):
sudo apt-get install openvpn -y sudo cp -r /usr/share/easy-rsa /etc/openvpn/
Move Into the easy-rsa Directory
Almost all of the command line action takes place in /etc/openvpn/easy-rsa. So move there:
cd /etc/openvpn/easy-rsa
Create a certificate authority (CA) for OpenVPN
This will let you sign various cryptographic files so that their chain of security (sort of like a chain of custody in criminal law) points back to a known signing authority. You do this by copying vars.example to vars in the easy-rsa directory and then customizing vars with information identifying your certificate authority. When you’re done, initialize the certificate authority by running:
sudo ./easyrsa init-pki
Build the CA Certificates
sudo ./easyrsa build-ca
You’ll be asked for a Distinguished Name, which is basically the name of the certificate authority you’re creating. Pick something meaningful to you.
Generate Certificate Files
These include a .crt certificate file and a .key private key file for the OpenVPN server:
sudo ./easyrsa gen-req [server name] nopass
Use something for [server name] which will remind you which server the certificate is for (e.g., vpn-server). The nopass argument is important. Without it you’ll have to specify a password…and every time your OpenVPN server fires up you’ll have to be around to enter that password so the certificate file can be read. As that’s extremely inconvenient don’t encrypt the certificate file with a password.
Sign the Server Key File
Specify the same [server name] as you used in the previous step:
sudo ./easyrsa sign-req server [server name]
Verify the Server Certificate File
Use [server name] again. If everything’s okay you should see the specified output.
mark@mycroft:/etc/openvpn/easy-rsa $ sudo openssl verify -CAfile pki/ca.crt pki/issued/[server name].crt pki/issued/[server name].crt: OK
Generate a strong Diffie-Hellman Key
sudo ./easyrsa gen-dh
This will take a very long time on an otherwise-unloaded Raspberry Pi 4 (20 – 30 minutes, as I recall).
Copy the Server Security Files
You want them in the /etc/openssl/server directory. Remember to use the [server name] you defined above.
sudo cp pki/ca.crt /etc/openvpn/server/ sudo cp pki/dh.pem /etc/openvpn/server/ sudo cp pki/private/[server name].key /etc/openvpn/server/ sudo cp pki/issued/[server name].crt /etc/openvpn/server/
Create Client Key Files
This is very similar to what you did for the server files except this time do require a password (by leaving off the command line argument nopass). This is important because the client files are the ones you’ll be copying around — hopefully securely! — to various client devices. Since they’re “in the open”, so to speak, you want them to be secured by a password.
sudo ./easyrsa gen-req client
Sign the Client File
sudo ./easyrsa sign-req client client
Copy the Client Files
You want them in /etc/openssl/client:
sudo cp pki/ca.crt /etc/openvpn/client/ sudo cp pki/issued/client.crt /etc/openvpn/client/ sudo cp pki/private/client.key /etc/openvpn/client/
Create a Transport Layer Security Key
This hardens your VPN connection. It’s optional, and not included in Hitesh’s instructions, but worth doing. Among other things TLS encrypts the traffic passing through the VPN tunnel:
sudo openvpn --genkey --secret ta.key sudo cp ta.key /etc/openvpn/server/
Configure the OpenVPN Server
Create a file called /etc/openvpn/server/server.conf with the following content:
port [your VPN port number] proto udp dev tun ca /etc/openvpn/server/ca.crt cert /etc/openvpn/server/[server name].crt key /etc/openvpn/server/[server name].key dh /etc/openvpn/server/dh.pem server [VPN IP subnet] [VPN IP netmask] push "dhcp-option DNS [your DNS address]" push "dhcp-option DNS [a backup DNS address (optional)]" push "dhcp-option DOMAIN localnet" push "block-outside-dns" cipher AES-256-CBC tls-version-min 1.2 tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-WITH-AES-128-CBC-SHA256 tls-auth ta.key 0 auth SHA512 auth-nocache keepalive 20 60 persist-key persist-tun daemon user nobody group nogroup status /etc/openvpn/openvpn-status.log status-version 3 log-append /var/log/openvpn.log verb 3
I’ll provide some explanatory comments on certain of these lines below. But before I do that I want to highlight a very important omission — relative to Hitesh’s otherwise great instructions — which, until I realized I should omit it, caused me no end of grief:
Do not enable any form of compression for the VPN server if you are planning on accessing it from an iOS device. If you do the VPN will get created…but you won’t be able to use it to do anything. Other than ping devices on your LAN, and the pleasure in doing so wears off very quickly. Nothing else — SSH-based terminal apps, RDP apps — will work. They’ll all fail…without any log message indicating compression was the culprit. This is with OpenVPN iOS client 3.2.x.
Confusingly, the VPN tunnel will work from a Windows device. Apparently the bug either doesn’t exist in the OpenVPN Windows client or is in iOS itself.
The only way I stumbled across this workaround is because once upon a time I had a working VPN setup which I could access from iOS and it didn’t have compression of any kind. So the configuration entry setting up compression provoked a “huh, that’s different” reaction.
Line 1: pick a port number for your VPN. You can use anything over 1024 including the “traditional” 1194. But it may provide a tiny bit of additional security to use a non-traditional port number. That’s what I do.
Line 8: This is where you specify the subnet of addresses your OpenVPN server can hand out. In my configuration line 8 looks like this:
server 192.168.5.0 255.255.255.0
This means all my VPN clients will be on the 192.168.5.x subnet. The OpenVPN server will have two IP addresses, one on my LAN and the other (192.168.5.1) on the VPN subnet.
Lines 10 – 13: These specify various options related to how you want your VPN clients to resolve DNS queries. Recall that I run my own local DNS, via dnsmasq, on my LAN. So I only have one DNS entry, pointing at the dnsmasq server (which happens to be the same Raspberry Pi that my OpenVPN server runs on).
I also, because I resolve local names, have line 12, which basically tells OpenVPN to send only DNS queries related to the localnet domain to my local DNS server (everything else goes to whatever DNS server the client device has access to). “localnet” is the top level domain for my LAN. Yours, if you have one, could be different.
Line 13 is included to deal with a potential information leak in Windows (I don’t know how serious this is, it’s just something I saw in some example online so I included it).
Line 19 specifies the name of the transport layer key file to use to encrypt traffic in the tunnel as it passes through the internet. Recall that this is optional but recommended. The 0 after the file name is important because it indicates we’re defining the server side of the transport encryption system (I don’t know why that’s not implicit in that we’re defining the server configuration file but apparently the 0 is necessary).
Line 30 tells the OpenVPN server where to maintain a file listing the currently active clients.
Tweak Your Firewall/Routing Setup
Before you fire up your OpenVPN server you have to do one more thing: tweak your firewall/routing setup so that the outside world can “see” it. Because my firewall runs on the router/firewall connecting my LAN to the internet I made the tweaks there. That involved creating a port forwarding entry in the router’s firewall:
Your mileage will definitely differ depending on your firewall hardware.
Update Your LAN Clients’ Routing Tables to Find the VPN Subnet
At this point while your VPN clients will be able to find your LAN clients the reverse is not true. You LAN clients won’t be able to see any VPN clients because the VPN clients are on a different subnet. You need to add a static route to each of your LAN clients.
You could do this manually. For example, given my setup, on my Windows LAN clients I could do this in administrator mode:
> route add 192.168.5.0/24 192.168.1.5
This would tell that particular Windows machine that to find VPN clients on the 192.168.5.0/24 subnet you have to go through 192.168.1.5 — the address of my Raspberry Pi and thus my OpenVPN server — as a gateway.
But having to do this for every machine is tedious and error prone. A better way is to configure your DHCP server to do it for you. Since I use dnsmasq to provide DHCP services all I need to do is add one line to the options dnsmasq will send to any client that requests an IP address:
# rest of file omitted for brevity... # set the parameters we want clients to use to access gateway, etc. dhcp-option=option:netmask,255.255.255.0 dhcp-option=option:router,192.168.1.254 dhcp-option=option:domain-name,localnet dhcp-option=option:classless-static-route,192.168.5.0/24,192.168.1.5
Line 7 contains the necessary information. The next time any client asks for or refreshes its IP address it’ll automatically know how to talk to VPN clients. Of course, you have to remember to restart dnsmasq for the change to take effect:
> sudo systemctl restart dnsmasq
Create a Client OVPN File
The last configuration step involves creating a client OVPN file, which configures the OpenVPN client app to work with your particular OpenVPN server. It’s a simple text file:
client dev tun proto udp remote [server to contact to initiate VPN] [your VPN port number] route [your LAN IP range] [your LAN netmask] [the client side IP address of your VPN server] cipher AES-256-CBC auth SHA512 auth-nocache tls-version-min 1.2 tls-cipher TLS-DHE-RSA-WITH-AES-256-GCM-SHA384:TLS-DHE-RSA-WITH-AES-256-CBC-SHA256:TLS-DHE-RSA-WITH-AES-128-GCM-SHA256:TLS-DHE-RSA-$ resolv-retry infinite nobind persist-key persist-tun mute-replay-warnings verb 3 <ca> -----BEGIN CERTIFICATE----- [your certificate authority certificate, /etc/openvpn/server/ca.crt] -----END CERTIFICATE----- </ca> <cert> -----BEGIN CERTIFICATE----- [your private key certificate, /etc/openvpn/client/client.crt] -----END CERTIFICATE----- </cert> <key> -----BEGIN ENCRYPTED PRIVATE KEY----- [your private key key file (password protected), /etc/openvpn/client/client.key] -----END ENCRYPTED PRIVATE KEY----- </key> key-direction 1 <tls-auth> -----BEGIN OpenVPN Static key V1----- [your TLS key, /etc/openvpn/server/ta.key] -----END OpenVPN Static key V1----- </tls-auth>
Line 4: One of the challenges of having a “retail” broadband connection to the internet is your service provider generally has the right to assign a different public IP address to the connection. I had this happen a few times when I used Comcast and its cable TV-based network. I haven’t had it happen with AT&T Fiber but it could.So your VPN client can’t rely on a fixed, static public IP address.
There are a number of companies that offer solutions to this problem. They will assign you, usually for a fee, a virtual server which will always point to your current public IP address. The information is kept up to date by running a daemon on a device on your LAN which periodically sends the current public IP address to their servers. I’ve used several of these services. My current favorite is ZapTo, offered by FreeDNS. The setup is reasonably straightforward and the documentation is decent.
Line 6: this is a routing instruction telling your VPN clients how to access your LAN, which is on a different subnet from them. In my case my VPN subnet is 192.168.5.0/24 while my LAN is 192.168.1.0/24. So my entry looks like this:
route 192.168.1.0 255.255.255.0 192.168.5.1
The last IP address is the gateway your VPN clients will use to access your LAN. It’s the client-side address of the OpenVPN server. Remember, the OpenVPN server has two IP addresses, one facing your LAN and one facing the VPN client subnet. That’s why it can, and should, act as a gateway between the client subnet and the LAN.
The corresponding routing entry for your LAN to access the VPN subnet is created automatically by the OpenVPN server when it starts up. It’s the last entry in the following routing table on my Raspberry Pi:
mark@mycroft:/etc/openvpn $ route Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface default 192.168.1.254 0.0.0.0 UG 202 0 0 eth0 192.168.1.0 0.0.0.0 255.255.255.0 U 202 0 0 eth0 192.168.5.0 0.0.0.0 255.255.255.0 U 0 0 0 tun0
That last entry routes any traffic destined for a 192.168.5.x device to interface tun0, which is created and managed by the OpenVPN server.
Line 27 should be replaced with the contents of the /etc/openvpn/server/ca.key file (which is your certificate authority key).
Line 33 should be replaced with the contents of the /etc/openvpn/client/client.crt file, which is your client certificate.
Line 39 should be replaced with the contents of the /etc/openvpn/client/client.key file, which is your client key.
Line 43 says that we’re going to supply the client side of the optional but recommended transport layer encryption key. Line 47 should be replaced with the contents of the /etc/openvpn/server/ta.key file you created earlier.
Load the Client Configuration File into the OpenVPN Client Software
Save this OVPN file, get it over to your client device (e.g., via a cloud storage account your Pi and your client have access to), and import it into the OpenVPN client software on the client device. That will enable you to launch the OpenVPN client and connect to your VPN.
Fire Up Your OpenVPN Server
Of course, the OpenVPN server will need to be running for this to work. On Debian buster you use these commands to do that:
mark@mycroft:/etc/openvpn $ systemctl start openvpn-server@server mark@mycroft:/etc/openvpn $ systemctl enable openvpn-server@server mark@mycroft:/etc/openvpn $ systemctl status openvpn-server@server ● openvpn-server@server.service - OpenVPN service for server Loaded: loaded (/lib/systemd/system/openvpn-server@.service; enabled; vendor preset: enabled) Active: active (running) since Mon 2020-10-19 15:19:54 PDT; 1 day 18h ago Docs: man:openvpn(8) https://community.openvpn.net/openvpn/wiki/Openvpn24ManPage https://community.openvpn.net/openvpn/wiki/HOWTO Main PID: 2232 (openvpn) Status: "Initialization Sequence Completed" Tasks: 1 (limit: 4915) CGroup: /system.slice/system-openvpn\x2dserver.slice/openvpn-server@server.service └─2232 /usr/sbin/openvpn --status /run/openvpn-server/status-server.log --status-version 2 --suppress-timestamps --config
Line 1 starts the server. Line 2 ensures it will start automatically when your Pi boots. Lines 3 thru 14 confirm that the server is currently running.