A Simple OpenIKEd site-to-site VPN

How to setup a simple site-to-site OpenIKEd based VPN on OpenBSD

2016

Some time ago I needed to establish a simple and reasonably secure VPN between two of my networks. The built-in IKEv1 and ISAKMP system on OpenBSD felt dated, while OpenVPN seemed very complicated and didn’t take advantage of many of the features of OpenBSD, so I decided instead to use OpenIKEd.

OpenIKEd, or iked(8), is a straightforward VPN daemon which only supports IKEv2. It was written by Reyk Floeter to be a “lean, clean, secure, better configurable and [more] interoperable” alternative to other similar daemons. While this claim is in many ways true, I unfortunately found OpenIKEd to be a bit of a pain to set up. I eventually discovered this was largely due to my firewall configuration, but I was also unable to find any good examples of the specific task I was attempting to carry out. For the benefit of future users, this article details the setup process for a simple site-to-site VPN using OpenIKEd with pre-shared keys on OpenBSD 5.8/5.9.

Using pre-shared keys isn’t the safest way to do this and doesn’t take advantage of OpenIKEd’s support for public key authentication. To improve safety, this template uses pf(4) to restrict connections to only the desired remote hosts and also uses a random 64 character case-sensitive alphanumeric strong password. I recommend making sure the permissions on /etc/iked.conf are set to root:wheel 400.

In this example, the two networks are located at 1.2.3.4 and 5.6.7.8 each with internal networks of 10.10.0.1/24 and 10.100.0.1/24 respectively.

hostname.if(5)

First we need to enable an interface for the VPN. We do this by making a hostname.if(5) file which is located in /etc. For encrypted interfaces we use the name enc followed by a number; because this is the first encrypted interface, we use the name enc0. In other words, we create the file /etc/hostname.enc0 and fill it with the word ‘up’. You can do this with your favourite text editor, or simply:

# echo up > /etc/hostname.enc0

sysctl(8)

OpenIKEd requires some changes to the kernel state. These changes need to be set in /etc/sysctl.conf in order to keep the setting persistent across boots. A sane yet thorough configuration is as follows:

in /etc/sysctl.conf on both 1.2.3.4 & 5.6.7.8
net.inet.ip.forwarding=1			# (default:0)    Enables IP forwarding
net.inet.ah.enable=0				# (default:1)    Enables AH
net.inet.esp.enable=1				# (default:1)    Enables ESP
net.inet.esp.udpencap=1				# (default:1)    Enables UDP encapsulation
net.inet.esp.udpencap_port=4500		# (default:4500) Sets port for UDP encapsulation
net.inet.ipcomp.enable=0			# (default:0)    Enables IPsec compression

I would note that this procedure involves disabling the authentication headers as they are not required.

To set these states you can reboot or, to avoid rebooting, run each of the entries through the sysctl(8) command. To use the sysctl command you must enter each key/value pair into the shell as root in a manner similar to the following:

# sysctl net.inet.ip.forwarding=1

This will set only ‘net.inet.ip.forwarding’ to 1; to update all the states you will have to do this for all key/value pairs. Afterwards you can verify that the values have actually been set by running:

$ sysctl net.inet.ip.forwarding net.inet.ah net.inet.esp net.inet.ipcomp

pf(4)

As a VPN by definition needs to do networking, you will have to modify the firewall to allow packets to enter and leave. Due to the wide variety of firewall configurations this example setup is based on a ‘block all’ approach and only covers the broad elements. It is very easy to make a mistake here so make sure the traffic destined for these rules isn’t short circuited before by other existing rules in your setup.

To simplify your configuration you will need to establish a table containing the WAN addresses of your peers. This will allow for the addition of other VPN’s simply by adding them to the table. This can be done as follows:

in /etc/pf.conf on 1.2.3.4
table <vpn_peers> const { 5.6.7.8 }
in /etc/pf.conf on 5.6.7.8
table <vpn_peers> const { 1.2.3.4 }

Next we allow the establishment of the actual connection. We are using Encapsulating Security Payload (ESP) which uses the ESP protocol along with UDP on port 500 and 4500. We want each peer to be able to talk with the other using the appropriate protocol and ports on each of their egress interfaces. This can be done as follows:

in /etc/pf.conf on 1.2.3.4 & 5.6.7.8
pass out quick on egress proto esp from (egress:0) to <vpn_peers>                  keep state
pass out quick on egress proto udp from (egress:0) to <vpn_peers> port {500, 4500} keep state

pass  in quick on egress proto esp from <vpn_peers> to (egress:0)                  keep state
pass  in quick on egress proto udp from <vpn_peers> to (egress:0) port {500, 4500} keep state

Lastly we need to allow the traffic that flows through the tunnel to reach its appropriate destination. There are two ways of doing this. The simplest is to just allow all traffic on the enc0 interface. This can be achieved by adding the following line near ‘set skip on lo0’ which is present in most pf configurations.

in /etc/pf.conf on both 1.2.3.4 & 5.6.7.8
set skip on enc0

Alternatively, for finer control you can add a more complicated set of rules which explicitly manages the flow of traffic between the networks. This can be done by creating additional macros, tables, and rules such as:

in /etc/pf.conf on 1.2.3.4
table <remote_nets> const { 10.100.0.0/24 }
net_local				 = "10.10.0.0/8"
in /etc/pf.conf on 5.6.7.8
table <remote_nets> const { 10.10.0.0/24 }
net_local				 = "10.100.0.0/8"
in /etc/pf.conf on both 1.2.3.4 & 5.6.7.8
pass out quick on enc0 proto ipencap from (egress:0)    to <vpn_peers>   keep state (if-bound)
pass out quick on enc0               from (egress:0)    to <vpn_peers>   keep state (if-bound)
pass out quick on enc0               from (egress:0)    to <remote_nets> keep state (if-bound)
pass out quick on enc0               from $net_local    to <vpn_peers>   keep state (if-bound)
pass out quick on enc0               from <allowed_vpn> to <remote_nets> keep state (if-bound)

pass  in quick on enc0 proto ipencap from <vpn_peers>   to (egress:0)    keep state (if-bound)
pass  in quick on enc0               from <vpn_peers>   to (egress:0)    keep state (if-bound)
pass  in quick on enc0               from <remote_nets> to (egress:0)    keep state (if-bound)
pass  in quick on enc0               from <vpn_peers>   to $net_local    keep state (if-bound)
pass  in quick on enc0               from <remote_nets> to <allowed_vpn> keep state (if-bound)

This will pass almost all traffic, though the tables and rules can be easily modified and extended to restrict sources and destinations of traffic. In addition, outbound rules need to be created to allow this traffic to actually travel out an interface to the local network. This is due to the (if-bound) component of the rules. In this template, traffic which has come across the VPN will be allowed out any interface which has been placed in the 'trust' group. (to add an interface to the 'trust' group you must add the line “group trust” to that interfaces hostname.if file).

in /etc/pf.conf on both 1.2.3.4 & 5.6.7.8
pass out quick on trust received-on enc0 keep state

iked(8)

Now we need to actually complete the configuration of iked, which is done in the file /etc/iked.conf. iked.conf can be thought of as having two parts; macros and connections. We place the macros at the top of the file and the connections at the bottom.

in /etc/iked.conf on 1.2.3.4
local_gw	= "1.2.3.4"
remote_gw	= "5.6.7.8"
local_net	= "10.10.0.0/24"
remote_net	= "10.100.0.0/24"
state		= "active"
in /etc/iked.conf on 5.6.7.8
local_gw	= "5.6.7.8"
remote_gw	= "1.2.3.4"
local_net	= "10.100.0.0/24"
remote_net	= "10.10.0.0/24"
state		= "passive"
below macros in /etc/iked.conf on both 1.2.3.4 & 5.6.7.8
ikev2 "siteA-siteB" $state esp \
	from $local_gw to $remote_gw \
	from $local_gw to $remote_net \
	from $local_net to $remote_gw \
	from $local_net to $remote_net \
	local $local_gw peer $remote_net \
	ikesa	auth hmac-sha2-512 enc aes-256 group curve25519 prf hmac-sha2-512 \
	childsa	auth hmac-sha2-512 enc aes-256-gcm group curve25519 \
	psk "bdKTciatMcASbX61LeUJl3DDsr0n726tvfTrwMScGcNSI81xbJsox0qID3PIqC2s" \
	tag "VPN" tap enc0

This connection is an IKEv2 VPN using ESP, tunnelling traffic between each:

  • gateway
    • 1.2.3.45.6.7.8
    • 5.6.7.81.2.3.4
  • gateway and its remote network
    • 1.2.3.410.100.0.0/24
    • 5.6.7.810.10.0.0/24
  • network
    • 10.10.0.0/2410.100.0.0/24
    • 10.100.0.0/2410.10.0.0/24

using the pre-shared key:

bdKTciatMcASbX61LeUJl3DDsr0n726tvfTrwMScGcNSI81xbJsox0qID3PIqC2s

Phase 1 uses HMAC SHA512 for authentication and as its pseudo-random function, AES256 for encryption, and the non-standard Curve25519 for key exchange. Phase 2 also uses HMAC SHA512 for authentication and Curve25519 for key exchange, but uses the superior AES256 GCM for encryption. Lastly all traffic will be tagged “VPN” and traverse the enc0 interface.

ipsecctl(8)

To check that the VPN is established, use ipsecctl(8). To list the current flows and security associations, run

# ipsecctl -s all

If everything is working correctly you should get a response similar to:

FLOWS:
flow esp in from 5.6.7.8 to 1.2.3.4 peer 5.6.7.8 srcid FQDN/a.example.com dstid FQDN/b.example.com type use
flow esp out from 1.2.3.4 to 5.6.7.8 peer 5.6.7.8 srcid FQDN/a.example.com dstid FQDN/b.example.com type require
flow ipcomp in from 5.6.7.8 to 10.10.0.0/24 peer 5.6.7.8 srcid FQDN/a.example.com dstid FQDN/b.example.com type use
flow ipcomp out from 10.10.0.0/24 to 5.6.7.8 peer 5.6.7.8 srcid FQDN/a.example.com dstid FQDN/b.example.com type require
flow ipcomp in from 10.100.0.0/20 to 1.2.3.4 peer 5.6.7.8 srcid FQDN/a.example.com dstid FQDN/b.example.com type use
flow ipcomp out from 1.2.3.4 to 10.100.0.0/20 peer 5.6.7.8 srcid FQDN/a.example.com dstid FQDN/b.example.com type require
flow ipcomp in from 10.100.0.0/20 to 10.10.0.0/24 peer 5.6.7.8 srcid FQDN/a.example.com dstid FQDN/b.example.com type use
flow ipcomp out from 10.10.0.0/24 to 10.100.0.0/20 peer 5.6.7.8 srcid FQDN/a.example.com dstid FQDN/b.example.com type require
flow esp out from ::/0 to ::/0 type deny

SAD:
ipcomp tunnel from 1.2.3.4 to 5.6.7.8 spi 0xdeadbeef
ipcomp tunnel from 5.6.7.8 to 1.2.3.4 spi 0xbeefdead
esp transport from 1.2.3.4 to 5.6.7.8 spi 0xbeaddeef enc aes-256-gcm
esp transport from 5.6.7.8 to 1.2.3.4 spi 0xdeefbead enc aes-256-gcm

Additionally, to control and monitor the VPN’s you can use ikectl(8). Further tools that may be helpful in debugging your configuration are ping(8) and tcpdump(8).

Further Resources

Change Log

  • OpenBSD 6.4, There is an issue with the use of ipcomp which can cause the resultant VPN to be unstable. Not using ip compression fixes this. I have removed its use in this post.

License

Any source in this article is released under the ISC License.