A while back, I wrote a post about setting up an L2TP/IPSec VPN on my home firewall/router. It required two daemons and a bunch of configuration that had hard coded IP addresses. While this solution used firmly-established practices (L2TP/IPSec), it felt too brittle. What happens when my dynamic IP address changes? Now I need to update config files, restart daemons, etc. There had to be a better way. Enter IKEv2. IKEv2 is a successor implementation to Internet Security Association and Key Management Protocol (ISAKMP)/Oakley, IKE version 1. One of the main reasons iked(8) is so great, is not having to use the accompanying ipsecctl binary to manage iked’s configuration. From OpenIKED Asia BSDCON 2013:
“iked is able to load and understand the grammer of /etc/iked.conf configuration directly without the need for an additional tool like ipsecctl.” I lost count how many times I restarted the isakmpd daemon, but forgot to reload my rules config, leading to failed testing for hours on end.
But back to my implementation: My server runs OpenBSD 6.0-stable on a dynamic IP on a Verizon Fios Internet connection. My VPN clients include an OSX-10.12 running Macbook Pro along with an Android phone running Nougat (7.1). I discovered OSX 10.12 supports IKEv2 natively. I poked a bit around my Android phone, and it seems that the native VPN client only supports supports user/name passwords and certificates, and not pre-shared keys. As an alternative to support pre-shared keys, I’ve installed the StrongSwan app on my phone, but haven’t explored yet. My goals of the VPN configuration are:
- Route all road-warrior traffic over the tunnel. I don’t want to do split tunneling.
- Hand out addresses to road warriors in a particular subnet.
Onto the /etc/iked.conf(5) on the OpenBSD server:
ikev2 “road_warrier” passive ipcomp esp \ from 0.0.0.0/0 to 10.253.0.0/24 \ local egress \ psk foobarbazcafe \ config address 10.253.0.0/24 \ config netmask 255.255.255.0 \ config name-server 10.253.0.1 \ config protected-subnet 0.0.0.0/0 \ tag ipsec_$id
My understanding of the relevant bits of the configuration: “from 0.0.0.0/0 to 10.253.0.0/24”: From 0.0.0.0/0 (Any roving IP out on the Internet) to hosts in 10.253.0.0/24, negotiate an IPsec IKEv2 flow. “local egress”: Specifies the local address of the VPN endpoint. This is where I cringed the most, since this is where the hard-coded IP of my previous config came from. Since I only wanted to listen on the IP address attached to the outside world, I took a chance and put ’egress’ in here. It looks undocumented in the man page, but it works! “config address 10.253.0.0/24”: Assign dynamic addresses from this subnet to VPN clients. “config netmask”: The subnet mask of the internal network. I assumed this meant the VPN client subnet, since it was unclear which ‘internal network’ it refers to. “config name-server”: The DNS server within the internal network. “config protected-subnet”: This turned out to be the magic configuration option, which I found woefully undocumented online. This is the destination subnet of traffic which will flow over the IPSec tunnel. I want to send everything over the VPN, so 0.0.0.0/0 covers all traffic. And finally, pf.rules:
pass in log on $ext_if inet proto udp from any to ($ext_if) port {500, 4500} pass in log on $ipsec_if keep state (if-bound) # tagged ipsec pass out on $ext_if inet from <ipsec_roadwarrior_net> to any nat-to ($ext_if)
$ext_if: The interface attached to my external Internet connection. I assume I could just as easily put ’egress’ in this place. I’ve not tested that yet. $ipsec_if: The virtual interface containing the gateway for the IPSec tunnel. <ipsec_roadwarrior_net>: The subnet that my IPSec road warrior clients are given IP addresses on. In this example, 10.253.0.0/24. This allows NAT’d traffic from road warrior clients out the egress interface of the OpenBSD host.