PIA on a Pi

Posted Jun 26, 2017

Here’s a short guide to setting up a VPN on Linux — specifically Private Internet Access on a Raspberry Pi, because I’m mostly writing this as a reminder to myself — with a kill switch to prevent non-VPN traffic. I’m targeting a Pi 3 with Raspbian Jessie here, but the procedure should be similar for other machines and distros as long as packages like OpenVPN and UFW are available.

OpenVPN setup

Install OpenVPN.

# apt-get install openvpn

Download the VPN provider’s OpenVPN configuration files. For PIA, this usually means this one.

$ curl -O https://www.privateinternetaccess.com/openvpn/openvpn.zip
$ unzip -d openvpn openvpn.zip

OpenVPN on Linux uses .conf for config files instead of .ovpn, so rename them accordingly.

$ rename 's/ovpn/conf/' openvpn/*.ovpn

To enable automatic authentication when connecting, put your VPN username and password in a file next to the configs.

$ echo 'CoolGuy123
s3cr3t' > openvpn/auth.txt

It’s not very secure, so add a little protection by limiting access to the owner.

$ chmod 600 openvpn/auth.txt

Next, make the configurations refer to this file by appending some directives at the end of each. Here I’ll also throw in a couple of quality tweaks such as keepalive, logging to make troubleshooting easier, and automatic execution of a script called update-resolv-conf, which might be necessary for DNS resolution to work correctly when turning the VPN on and off. On Debian, this script is included in the OpenVPN install.

$ echo 'auth-user-pass auth.txt
keepalive 10 60
log-append /var/log/openvpn.log
script-security 2
up /etc/openvpn/update-resolv-conf
down /etc/openvpn/update-resolv-conf' | tee -a openvpn/*.conf

From here, it might make sense to continue as root. Move everything into place by copying the auth file, configurations, certificate and key file to /etc/openvpn.

# cp openvpn/* /etc/openvpn

To make OpenVPN automatically connect with a certain configuration, set the AUTOSTART directive in /etc/default/openvpn to the configuration filename without the extension.

# echo 'AUTOSTART="Germany"' >> /etc/default/openvpn

Note 2020-03-14: since this post was first published, there’s a better way to run OpenVPN as a service. Instead of using /etc/default/openvpn, move all the configuration files one level down into /etc/openvpn/client and enable the service by calling systemctl enable openvpn-client@Germany. I’ve tested this on Raspbian Buster, but I’m unsure if it works on older versions as well.

It should now be possible to start up OpenVPN and have it connect.

# systemctl daemon-reload
# systemctl restart openvpn

To verify this, wait a few seconds and run ifconfig. There should be an interface called tun0 or similar (for tunnel), which is the VPN interface. To test connectivity, call an IP echo service such as ipinfo.io, which should report an IP and a country matching your configuration.

$ curl ipinfo.io

UFW kill switch

At this point, if the VPN connection drops or OpenVPN stops for some reason, your machine will talk freely over your regular, non-VPN interface. Depending on the use case, it might be preferable for it to stop communicating completely instead, like if you’re downloading warez concerned about your privacy.

To ensure this, we’re going to set up a firewall to deny everything but the VPN handshake on the regular interfaces eth0 and wlan0 while placing no restrictions on tun0. I’m going to use UFW because it’s fairly intuitive compared to iptables.

# apt-get install ufw

Next we fill in the firewall rules. First, allow everything on OpenVPN’s network interface.

# ufw allow in on tun0
# ufw allow out on tun0

OpenVPN obviously needs to be able to connect on the regular interface, and with the default PIA configuration files, this means allowing

  1. DNS resolution on port 53 to find e.g. germany.privateinternetaccess.com
  2. The actual VPN port specified by the configuration files, i.e. 1198
# ufw allow out on eth0 from any to any port 53
# ufw allow out on wlan0 from any to any port 53
# ufw allow out on eth0 from any to any port 1198
# ufw allow out on wlan0 from any to any port 1198

You might also want to have the machine reachable from the local network to SSH into it and such, so allow everything to those IPs. My local network has IPs in the 192.168.0.* range so I’ll be using that.

# ufw allow in on eth0 from 192.168.0.0/24 to any
# ufw allow in on wlan0 from 192.168.0.0/24 to any
# ufw allow out on eth0 from any to 192.168.0.0/24
# ufw allow out on wlan0 from any to 192.168.0.0/24

Finally, block everything else and enable the firewall.

# ufw deny in on eth0
# ufw deny in on wlan0
# ufw deny out on eth0
# ufw deny out on wlan0
# ufw enable

Now test this by stopping OpenVPN and sending a request:

# systemctl stop openvpn
# curl --connect-timeout 5 ipinfo.io

Because DNS is allowed outside the VPN, ipinfo.io is resolved, but the request times out after 5 seconds because port 80 is blocked. Turning OpenVPN on again restores connectivity.

And that’s basically it. Easy, right? Not really, but it works fairly well.