RoadWarrior styled VPN on OpenBSD with IKED and OpenVPN

Background

For my personal bussiness I run dedicated server on which I have deployed several virtual machines - each for the separate task. Traffic from the host (further referred as “server”) is routed to the internal network (10.0.0.0/24) through OpenBSD’s Packet Filter (PF).

Originally I wanted to use just native iked daemon (supports IKEv2) to manage whole VPN configuration. Unfortunately I realised that my home Synology Disk Station doesn’t support the newer IKE implementation so I decided to run two independant solutions for that:

  • iked (172.24.24.0/24) for all my devices
  • OpenVPN (192.168.8.0/24) for Synology

You could confidently skip the following section if you want to gain similar result on simple environment with not routed traffic.

Network setup

Server configuration

On server, following piece of /etc/pf.conf ensures that all related traffic is routed to your $vpn host (in my case 172.24.24.1).

NOTE: This section describes the configuration of host server (specific for my use-case). If you wish to run just simple configuration with single server, follow up in next section!

# VPN
vpn="172.24.24.1"
pass out quick proto {esp ah} from any to any keep state
pass out quick proto udp from any to any port {500, 4500} keep state
pass in quick proto {esp ah} from any to any keep state rdr-to $vpn
pass out quick modulate state
pass in quick proto udp from any to self port {1194, isakmp, ipsec-nat-t} keep state rdr-to $vpn

You would need also to enable IP forwarding for ipv4 / ipv6, IPCOMP, ESP, … You could see that rad (ipv6 autoconf) is disabled in my scenario - this configuration is being propagated directly through iked daemon (see below).

# /etc/sysctl.conf
net.inet.ip.forwarding=1
net.inet6.ip6.forwarding=1
net.inet.ipcomp.enable=1 # 1=Enable the IPCOMP protocol
net.inet.esp.enable=1 # 0=Disable the ESP IPsec protocol
net.inet.esp.udpencap=1 # 0=Disable ESP-in-UDP encapsulation
net.inet6.ip6.forwarding=1 # 1=Permit forwarding (routing) of IPv6 packets
net.inet6.ip6.accept_rtadv=0 # 1=Permit IPv6 autoconf, 0=refuse autoconf
net.inet6.ip6.redirect=0
net.inet6.ip6.ifq.maxlen=1024

VM configuration

First thing, you would need to ensure is that, IP forwarding, IPCOMP, IPSec (and it’s encapsulation in UDP), ESP, … are allowed.

# /etc/sysctl.conf
      

net.inet.ip.forwarding=1
net.inet6.ip6.forwarding=1
net.inet.ipcomp.enable=1 # 1=Enable the IPCOMP protocol
net.inet.esp.enable=1 # 0=Disable the ESP IPsec protocol
net.inet.esp.udpencap=1 # 0=Disable ESP-in-UDP encapsulation
net.inet6.ip6.forwarding=1 # 1=Permit forwarding (routing) of IPv6 packets
net.inet6.ip6.accept_rtadv=0 # 1=Permit IPv6 autoconf, 0=refuse autoconf
net.inet6.ip6.redirect=0
net.inet6.ip6.ifq.maxlen=1024
net.inet.gre.allow=1
net.pipex.enable=1
net.inet.udp.recvspace=262144
net.inet.udp.sendspace=262144
net.inet.tcp.recvspace=262144
net.inet.tcp.sendspace=262144

Then you will need to configure Packet Filter to handle all the traffic. In my scenario I have the following configuration:

  • vio0 is the internal network interface (172.24.24.1) - this interface is used just for VPN traffic – all VPN clients has assigned IPs from 172.24.24.0/24 (and related IPv6) pool
  • vio1 is the external network interface (10.0.0.x/24) - this is specific for my use-case when I wanted to have separated traffic from/to another VMs in my setup
  • enc0 is the virtual device used for encryption of incomming / outgoing traffic with the clients
  • tun0 is the virtual TUN interface (router) used for OpenVPN clients - I’m using OpenVPN to connect my Synology NAS device (as it doesn’t support IKEv2 so far). Again, due to “separation of duties” it has assigned IP from another pool (192.168.8.1/24).
# /etc/pf.conf      

internal="vio0"
external=vio1

  
set skip on { lo, tun, enc, enc0 }
  
#block return # block stateless traffic
pass # establish keep-state
  
  
# NAT
match out on $external from vio0:network to vio1:network nat-to ($external) static-port
match out on $internal from vio0:network to any nat-to ($internal) 
match out on $external from tun0:network to vio1:network nat-to ($external)
match out on $internal from tun0:network to vio0:network nat-to ($internal)
pass out modulate state
pass out inet6 modulate state
  
# VPN
pass quick proto udp from any to self port {isakmp, ipsec-nat-t} keep state
#pass on pppac0 from any to self keep state (if-bound)
pass on enc0 from any to any keep state (if-bound)
pass on tun0 from any to any keep state (if-bound)
  
  
# OpenVPN
match out inet from 192.168.8.0/24 \
 to any nat-to (tun0) static-port source-hash
pass in quick proto udp from any to port 1194
  
  
#ipv6
icmp_types = "{ echoreq, unreach }"
#IPv6 - pass in/out all IPv6 ICMP traffic
pass quick proto icmp6 all
pass inet proto icmp all icmp-type $icmp_types keep state
  
  
# By default, do not permit remote connections to X11
block return in on ! lo0 proto tcp to port 6000:6010
  
# Port build user does not need network
block return out log proto {tcp udp} user _pbuild

IKEv2 server configuration

The VPN server configuration consists of several steps:

  1. configure the flows
  2. create PKI (Public Key Infrastructure) which will handle the certificates for server and clients
  3. optionally, configure the DNS forwarder used for connected clients

Configure the flow

# /etc/iked.conf


ikev2 "securenet" passive ipcomp esp \
  from 0.0.0.0/0 to dynamic \
  from ::0/0 to dynamic \
  peer any \
  ikesa enc aes-256 prf hmac-sha2-512 auth hmac-sha2-512 group modp4096 \
  childsa enc aes-256-gcm group modp4096 \
  childsa enc aes-256 group modp4096 \
  srcid vpn.sukany.cz \
  ikelifetime 1440m \
  lifetime 1440m bytes 16G \
  config address 172.24.24.0/24 \
  config address 2001:470:5a69:e1::/64 \
  config name-server 172.24.24.1 \
  config name-server 10.0.0.2 \
  tag "IKED" tap enc0

You might noticed that I propagating two DNS servers to the clients:

  • 172.24.24.1 - the local DNS forwarder which contains specific DNS configuration for easier accessing the devices connected to VPN
  • 10.0.0.2 - DNS slave for zones I manage

You need also enable / start the iked daemon

rcctl enable iked
rcctl start iked

Configure PKI

Create and initialize the Certificate authority. At the end, yo could find the whole CA bundle in /etc/ssl/vpn, where all related keys, certificates, revocation lists, etc are stored.

ikectl ca vpn create
ikectl ca vpn install

Create and install the certificate and key for your VPN server. Note that, name must be the same as srcid you defined in your iked configuration!

ikectl ca vpn certificate vpn.sukany.cz create
ikectl ca vpn certificate vpn.sukany.cz install

By installing the certificate, it’s key and certificate is copied under /etc/iked directory structure. This could be helpful if you don’t want to use unique certificates for all the clients (see below in Client configuration section)

Now you need to create certificates and key pairs for the clients

ikectl ca vpn certificate martin.vpn.sukany.cz create

You can optionaly export the whole bundle for the client - following command creates the archive containing your client’s certificate, key, PKCS12 bundle and certificate of your vpn CA.

ikectl ca vpn certificate martin.vpn.sukany.cz export

Configure local DNS forwarder

For more confortable access to your VPN connected devices, you could setup local DNS forwarder (I’m using unbound which is partof base OpenBSD system). For more details, please read the unbound.conf manual page.

For the illustration, configuration like this might appear in your /var/unbound/etc/unbound.conf:

 private-domain: "int.sukany.cz."
 local-zone: "int.sukany.cz" static
 local-data: "server.int.sukany.cz. IN A 10.0.0.1"
 local-data: "namer.int.sukany.cz. IN A 10.0.0.2"
 local-data: "vpn.int.sukany.cz. IN A 172.24.24.1"
 local-data: "kerberos.int.sukany.cz. IN A 10.0.0.13"
 local-data: "ldap.int.sukany.cz. IN A 10.0.0.13"
 local-data: "server2.int.sukany.cz IN A 172.24.24.2"
 local-data: "nas.int.sukany.cz IN A 192.168.8.2"

IKEv2 client configuration

This section describes how to configure your OpenBSD client.

First thing you need to do is to create virtual network device (loopback is sufficient) which will get assigned the configuration from your server and handle all the traffic.

echo "up" > /etc/hostname.lo1
sh /etc/netstart lo1

and than configure your iked client

# /etc/iked.conf
      

ikev2 "infra" active esp \
  from dynamic to 172.24.24.0/24 \
  from dynamic to 10.0.0.0/24 \
  from dynamic to 192.168.8.0/24 \
  peer 89.221.223.253 \
  ikesa enc aes-256 prf hmac-sha2-512 auth hmac-sha2-512 group modp4096 \
  childsa enc aes-256-gcm group modp4096 \
  srcid server2.sukany.cz \
  request address 172.24.24.2 \
  iface lo1

The configuration is very similar to your server one. Just note that the client act as active, peer represents IP address of your vpn server, srcid matches the client certificate name (this is mandatory!) and you’re requesting the specific IP matching your DNS forwarder configuration.

Now you need to place your certificates / keys in appropriate place. For this example

  • /etc/iked/certs/server2.sukany.cz.crt - certificate
  • /etc/iked/private/server2.sukany.cz.key - private key
  • /etc/iked/ca/ca.crt - Certificate Authority’s certificate

The last step is to enable and run the client

rcctl enable iked
rcctl start iked

If everything is configured accordinghly, you should be able to see that new IP configuration has been assigned to your lo1 interface.

PKI less configuration

You could decide not to use PKI for your setup. In this case, you could use the default RSA pair (/etc/iked/private/local.key and /etc/iked/local.pub) which is generated by OpenBSD during installation.

In this case, you need to copy your public key (local.pub) from server to client (and vice versa). Here you need to pay attention to propper name matching and file placement.

In above scenarion

  • local.pub from server needs to be placed as /etc/iked/pubkeys/fqdn/vpn.sukany.cz on the client
  • local.pub from client needs to be placed as /etc/iked/pubkeys/fqdn/server2.sukany.cz on the server

IKEv2 troubleshooting

In case of issues, you might find useful to look either into the logs (/var/log/daemon) on server’s and client’s side or run the iked on both sides in foreground (iked -dv)