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 to \
  local egress \
  psk foobarbazcafe \
  config address \
  config netmask \
  config name-server \
  config protected-subnet \
  tag ipsec_$id

My understanding of the relevant bits of the configuration:

“from to”: From (Any roving IP out on the Internet) to hosts in, 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“: 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 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, This allows NAT’d traffic from road warrior clients out the egress interface of the OpenBSD host.