A simplified way to securely move all the bits.

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.