OpenBSD FAQ - Virtual Private Networks (VPN) [FAQ Index]



Introduction

OpenBSD comes with iked(8), a modern, privilege-separated IKEv2 server. It can act both as responder, e.g. a server receiving connection requests, or initiator, e.g. a client initiating a connection to a responder. The ikectl(8) utility is used to control the server, which gets its configuration from the iked.conf(5) file.

The ikectl(8) utility also allows you to maintain a simple X.509 certificate authority (CA) for IKEv2 peers.

An IKEv1 server (isakmpd(8)) is also available and, coupled with npppd(8), it allows you to build an IKEv1/L2TP VPN where IKEv2 can't be deployed.

Native WireGuard support is also available via the wg(4) device. As the manual explains, it can be configured the same way as all other network interfaces in OpenBSD.

Authentication

iked(8) supports the following methods of authentication: By default, an RSA public key is generated at boot in /etc/iked/local.pub, with the private key being stored in /etc/iked/private/local.key.

Configuring an IKEv2 Server

Building Site-to-site VPNs

This can be achieved by exchanging the default-provided RSA public keys: /etc/iked/local.pub on the first system ("server1") should be copied to /etc/iked/pubkeys/fqdn/server1.domain on the second system ("server2"). Then, /etc/iked/local.pub on the second system should be copied to /etc/iked/pubkeys/fqdn/server2.domain on the first. Replace "serverX.domain" with your own FQDN.

From that point, let's assume that server1 has a public IP of 192.0.2.1 and an internal network on 10.0.1.0/24, and that server2 has a public IP of 198.51.100.1 and an internal network on 10.0.2.0/24.

To enable the initiator to reach the responder, the isakmp UDP port should be open on the responder. If one of the peers is behind NAT, the ipsec-nat-t UDP port should also be open on the responder. If both peers have public IPs, then the ESP protocol should be allowed.

pass in log on $ext_if proto udp from 198.51.100.1 to 192.0.2.1 port {isakmp, ipsec-nat-t} tag IKED
pass in log on $ext_if proto esp from 198.51.100.1 to 192.0.2.1 tag IKED
An example /etc/iked.conf configuration for server1 (acting as the responder) might look like this:
ikev2 'server1_rsa' passive esp \
        from 10.0.1.0/24 to 10.0.2.0/24 \
        local 192.0.2.1 peer 198.51.100.1 \
        srcid server1.domain
And a simple config for server2 acting as the initiator:
ikev2 'server2_rsa' active esp \
        from 10.0.2.0/24 to 10.0.1.0/24 \
        peer 192.0.2.1 \
        srcid server2.domain
Using iked -dv can help you understand the exchange. In this example, the responder is behind NAT:
server1# iked -dv
...
ikev2_recv: IKE_SA_INIT request from initiator 198.51.100.1:500 to 192.0.2.1:500 policy 'server1_rsa' id 0, 510 bytes
ikev2_msg_send: IKE_SA_INIT response from 192.0.2.1:500 to 198.51.100.1:500 msgid 0, 451 bytes
ikev2_recv: IKE_AUTH request from initiator 198.51.100.1:4500 to 192.0.2.1:4500 policy 'server1_rsa' id 1, 800 bytes
ikev2_msg_send: IKE_AUTH response from 192.0.2.1:4500 to 198.51.100.1:4500 msgid 1, 720 bytes, NAT-T
sa_state: VALID -> ESTABLISHED from 198.51.100.1:4500 to 192.0.2.1:4500 policy 'server1_rsa'
On the initiator side:
server2# iked -dv
...
ikev2_msg_send: IKE_SA_INIT request from 0.0.0.0:500 to 192.0.2.1:500 msgid 0, 510 bytes
ikev2_recv: IKE_SA_INIT response from responder 192.0.2.1:500 to 198.51.100.1:500 policy 'server2_rsa' id 0, 451 bytes
ikev2_msg_send: IKE_AUTH request from 198.51.100.1:4500 to 192.0.2.1:4500 msgid 1, 800 bytes, NAT-T
ikev2_recv: IKE_AUTH response from responder 192.0.2.1:4500 to 198.51.100.1:4500 policy 'server2_rsa' id 1, 720 bytes
sa_state: VALID -> ESTABLISHED from 192.0.2.1:4500 to 198.51.100.1:4500 policy 'server2_rsa'
The IPsec flows can be viewed with ipsecctl(8):
server1# ipsecctl -sa
FLOWS:
flow esp in from 10.0.2.0/24 to 10.0.1.0/24 peer 198.51.100.1 srcid FQDN/server1.domain dstid FQDN/server2.domain type use
flow esp out from 10.0.1.0/24 to 10.0.2.0/24 peer 198.51.100.1 srcid FQDN/server1.domain dstid FQDN/server2.domain type require
flow esp out from ::/0 to ::/0 type deny

SAD:
esp tunnel from 192.0.2.1 to 198.51.100.1 spi 0xabb5968a auth hmac-sha2-256 enc aes-256
esp tunnel from 198.51.100.1 to 192.0.2.1 spi 0xb1fc90b8 auth hmac-sha2-256 enc aes-256

server2# ipsecctl -sa
FLOWS:
flow esp in from 10.0.1.0/24 to 10.0.2.0/24 peer 192.0.2.1 srcid FQDN/server2.domain dstid FQDN/server1.domain type use
flow esp out from 10.0.2.0/24 to 10.0.1.0/24 peer 192.0.2.1 srcid FQDN/server2.domain dstid FQDN/server1.domain type require
flow esp out from ::/0 to ::/0 type deny

SAD:
esp tunnel from 192.0.2.1 to 198.51.100.1 spi 0xabb5968a auth hmac-sha2-256 enc aes-256
esp tunnel from 198.51.100.1 to 192.0.2.1 spi 0xb1fc90b8 auth hmac-sha2-256 enc aes-256
With that, both internal networks should be able to reach each other. Traffic between them should appear after decapsulation on the enc0 interface, and can be filtered as such. In that example, tag VPN has been added to the policy:
# pfctl -vvsr|grep VPN
@16 pass log on enc0 tagged VPN
# tcpdump -nei pflog0 rnr 16
00:03:26.793522 rule 16/(match) pass in on enc0: 10.0.2.24 > 10.0.1.13: icmp: echo request
Some words of warning: If the VPN endpoints need to reach the remote internal network, or the internal network needs to reach the remote VPN endpoint, additional flows have to be set on both sides: The responder configuration would then look like:
ikev2 'server1_rsa' passive esp \
        from 10.0.1.0/24 to 10.0.2.0/24 \
        from 10.0.1.0/24 to 198.51.100.1 \
        from 192.0.2.1 to 10.0.2.0/24 \
        local 192.0.2.1 peer 198.51.100.1 \
        srcid server1.domain
And the initiator configuration would be:
ikev2 'server2_rsa' active esp \
        from 10.0.2.0/24 to 10.0.1.0/24 \
        from 10.0.2.0/24 to 192.0.2.1 \
        from 198.51.100.1 to 10.0.1.0/24 \
        peer 192.0.2.1 \
        srcid server2.domain

Connecting to an IKEv2 VPN

Connecting to an IKEv2 VPN as a road warrior is similar to the previous case, except that the initiator usually plans to route its internet traffic through the responder, which will apply NAT on it, so that the initiator traffic appears to be coming from the responder's public IP.

Depending on the use case, as all traffic will go through the responder, one must ensure the initiator is configured to use a DNS server it can reach (possibly one on the responder).

With an OpenBSD Client

In our examples, the 10.0.5.0/24 network is used to support the VPN. The actual internal IP address will automatically be installed by iked on the lo1 interface. We'll assume the public IP for the client is 203.0.113.2.

As with the previous example, exchanging the default-provided RSA public keys is enough to set up a simple authentication between the responder and the initiator: /etc/iked/local.pub on the first system ("server1") should be copied to /etc/iked/pubkeys/fqdn/server1.domain on the second system ("roadwarrior"). Then, /etc/iked/local.pub on the roadwarrior system should be copied to /etc/iked/pubkeys/fqdn/roadwarrior on the first. Replace "serverX.domain" with your own FQDN.

The responder iked.conf(5) creates flows from any destination to the dynamic IP leases from the address pool, which will be decided at runtime, and tags the packets with ROADW:

ikev2 'responder_rsa' passive esp \
        from any to dynamic \
        local 192.0.2.1 peer any \
        srcid server1.domain \
        config address 10.0.5.0/24 \
        tag "ROADW"
The responder needs to provide an IP address to the initiator. This is achieved with the config directives. When using the config address option, to dynamic will be replaced with the assigned dynamic IP address.

It also needs to allow IPsec from any host (since clients might connect from anywhere), allow traffic tagged ROADW on enc0 and apply NAT to it:

pass in log on $ext_if proto udp from any to 192.0.2.1 port {isakmp, ipsec-nat-t} tag IKED
pass in log on $ext_if proto esp from any to 192.0.2.1 tag IKED
pass log on enc0 tagged ROADW
match out log on $ext_if inet tagged ROADW nat-to $ext_if
The initiator configures a global flow to send all its traffic to the responder, telling it to identify itself with the key named "roadwarrior":
ikev2 'roadwarrior' active esp \
        from dynamic to any \
        peer 192.0.2.1 \
        srcid roadwarrior \
        dstid server1.domain \
        request address any \
        iface lo1
The initiator uses the request address any option to request a dynamic IP address from the responder. The iface lo1 option specifies the interface on which the received address and corresponding routes will be installed. The responder should have a proper NAT configuration for the road warrior client.

Gracefully stopping the VPN on the initiator can be achieved using ikectl decouple (iked is still running, pending ikectl couple so that it reconnects to the responder) or with ikectl reset sa && rcctl stop iked to permanently stop iked and ensure no flows are left behind.

With an Android Client

The default Android VPN client only supports IKEv1. To use IKEv2, strongSwan is an option.

It is also required to set up a PKI and X.509 certificates so that the initiator can validate the certificate advertised by the responder:

server1# ikectl ca vpn create
server1# ikectl ca vpn install
certificate for CA 'vpn' installed into /etc/iked/ca/ca.crt
CRL for CA 'vpn' installed to /etc/iked/crls/ca.crl
server1# ikectl ca vpn certificate server1.domain create
server1# ikectl ca vpn certificate server1.domain install
writing RSA key
server1# cp /etc/iked/ca/ca.crt /var/www/htdocs/
On the android device, browse to http://192.0.2.1/ca.crt and import the CA certificate in the strongSwan client. From that point, there are several choices to authenticate the initiator to the responder:

Using MSCHAP-V2 for EAP Authentication

A responder config needs to specify a list of username/password, and that it will use eap "mschap-v2" (which is the only EAP method supported for now) as such:
user 'android' 'password'
ikev2 'responder_eap' passive esp \
        from any to dynamic \
        local 192.0.2.1 peer any \
        srcid server1.domain \
        eap "mschap-v2" \
        config address 10.0.5.0/24 \
        config name-server 192.0.2.1 \
        tag "ROADW"
In the strongSwan client, a new profile is configured using: With that, the Android device can connect to the responder, authenticate the responder certificate with the CA cert, authenticate itself to the responder with the EAP login/password, get an address in the 10.0.5.0/24 network, and all its traffic goes through the VPN, using 192.0.2.1 as its DNS server.

Using X.509 Certificate Authentication

For this method, a certificate is generated for the client, installed in iked ca, exported as an archive, and the .pfx file should be made available online so that the client can install it. The .pfx file bundles:
server1# ikectl ca vpn certificate client1.domain create
server1# cp /etc/ssl/vpn/client1.domain.crt /etc/iked/certs/
server1# ikectl ca vpn certificate client1.domain export
server1# tar -C /tmp -xzf client1.domain.tgz *pfx
server1# cp /tmp/export/client1.domain.pfx /var/www/htdocs/client1.domain.pfx
The CA public certificate and client certificate bundle have to be imported in the strongSwan client when configuring the new profile.

The responder config is slightly simpler since there's no need to specify eap nor set a username/password:

ikev2 'responder_x509' passive esp \
        from any to dynamic \
        local 192.0.2.1 peer any \
        srcid server1.domain \
        config address 10.0.5.0/24 \
        config name-server 192.0.2.1 \
        tag "ROADW"
In the strongSwan client, a new profile is configured, using: Like in the EAP case, the Android device can now connect to the responder and use the VPN.

With a Windows Client

Windows 7 and later provide an IKEv2 initiator that also requires the use of X.509 certificates, which need to be exported as .pfx/.p12 bundles and imported into the local machine (not the user account) certificate store, both for the CA and the client, either using the graphical Microsoft Management Console (type mmc in a command line and add the Certificates snap-in as a computer account) or the certutil command with Windows 10. Import ca.crt in the certificate authority store, and ClientIP.p12 in the personal store. The StrongSwan project has a good documentation on this topic, with screenshots.

Windows doesn't easily allow setting the srcid parameter for the client, so the CN field of the client certificate has to match the client FQDN sent to the responder, or its IP by default. It is also required that srcid on the responder matches the responder FQDN (or its IP, if not using FQDN) - otherwise one might experience a dreaded error 3801. The Libreswan project has valuable details on those requirements.

Once the certificates are imported, configure a new VPN connection with:

The responder configuration file will be similar to the Android case.
user 'windows' 'password'
ikev2 'responder_eap' passive esp \
        from any to dynamic \
        local 192.0.2.1 peer any \
        srcid server1.domain.fqdn \
        eap "mschap-v2" \
        config address 10.0.5.0/24 \
        config name-server 192.0.2.1 \
        tag "ROADW"
By default, all the windows traffic will now go through the IKEv2 VPN.

At the time of writing, current versions of Windows use weak encryption by default (3DES/SHA1). This can be corrected with the PowerShell command Set-VpnConnectionIPsecConfiguration.

Connecting to an IKEv1/L2TP VPN

Sometimes, one doesn't control the VPN server, and is only given the choice to connect to an IKEv1 server. In that case, the xl2tpd third-party package is needed to act as an L2TP client.

It is first required to enable isakmpd(8) and ipsec services so that the daemon is started and the ipsec.conf(5) configuration file loaded at boot:

# rcctl enable ipsec
# rcctl enable isakmpd
# rcctl set isakmpd flags -K
The following ipsec.conf(5) configuration should allow to connect to an IKEv1 server at A.B.C.D with a provided PSK, only allowing the UDP port 1701 for L2TP:
ike dynamic esp transport proto udp from egress to A.B.C.D port l2tp \
        psk mekmitasdigoat
Starting isakmpd(8) and loading ipsec.conf(5) using ipsecctl(8) should allow you to visualize configured Security Associations (SAs) and flows:
# rcctl start isakmpd
# ipsecctl -f /etc/ipsec.conf
# ipsecctl -sa
FLOWS:
flow esp in proto udp from A.B.C.D port l2tp to W.X.Y.Z peer A.B.C.D srcid my.client.fqdn dstid A.B.C.D/32 type use
flow esp out proto udp from W.X.Y.Z to A.B.C.D port l2tp peer A.B.C.D srcid my.client.fqdn dstid A.B.C.D/32 type require

SAD:
esp transport from A.B.C.D to W.X.Y.Z spi 0x0d16ad1c auth hmac-sha1 enc aes
esp transport from W.X.Y.Z to A.B.C.D spi 0xcd0549ba auth hmac-sha1 enc aes
If this is not the case, it might be required to tweak the phase 1 (Main) and phase 2 (Quick) parameters, when both parties exchange crypto parameters to agree on the best combination available. Ideally, those parameters should be provided by the remote server admin, and should be used in ipsec.conf(5):
ike dynamic esp transport proto udp from egress to A.B.C.D port l2tp \
        main auth "hmac-sha1" enc "3des" group modp1024 \
        quick auth "hmac-sha1" enc "aes" \
        psk mekmitasdigoat
Once the IKEv1 tunnel is up and running, the L2TP tunnel needs to be configured. OpenBSD doesn't provide an L2TP client by default, so installing xl2tpd is required.
# pkg_add xl2tpd
Refer to /usr/local/share/doc/pkg-readmes/xl2tpd for instructions on how to properly setup the L2TP client.