Site-to-site IPsec
OpenIKED is OpenBSD's native VPN solution. It is included with the base system, so no installation will be necessary. iked(8) handles IPsec flows and security associations (SAs). Using ipsec(4), we can provide users with a VPN.
Pros:
- Clean
- Secure
- Interoperable
- Simple to Configure
In this guide, we configure two gateways to create a virtual tunnel across the Internet. A new virtual private network can then be created out of both of the networks connected to the gateways.
This setup allows us to provide a secure tunnel between two trusted networks across an untrusted Internet. In this guide, we assume that both devices are gateways for local area networks, both of them have public IP addresses, and at least one of them is running OpenBSD.
This is based on the VPN guide from the OpenBSD FAQ
192.0.2.1 198.51.100.1 <-----IPsec-----> initlan <--> init resp <--> resplan 10.0.1.2 10.0.1.1 10.0.2.1 10.0.2.2
Note: If you are trying to tunnel traffic directly from end-user devices (like phones, desktops, or laptops) through a server to the Internet, you will instead want to use a road warrior configuration.
Connecting Two Gateways
We start with two servers: init
, which will initiate the connection to
resp
, which will respond.
In this example, init
has IP address 192.0.2.1
and resp
has address
198.51.100.1
. init
and resp
both act as gateways for their
respective LANs (10.0.1.0/24
and 10.0.2.0/24
). We will set up a
site-to-site vpn so devices on either subnet appear to be in the same network.
Exchanging Public Keys
iked can authenticate peers with RSA and ECDSA public keys, EAP MSCHAPv2, and X.509 certificates. In this site-to-site configuration, we will use public keys because the transfer of keys is quick and simple, allowing us to skip the need for certificates.
By default, an ECDSA public key is generated in /etc/iked/local.pub
. We
need to copy this public key to the peer's /etc/iked/pubkeys/
folder.
First, copy the public key from init
to resp
:
init$ cat /etc/iked/local.pub | ssh 198.51.100.1 'doas tee /etc/iked/pubkeys/fqdn/init.example.com'
Next, copy the public key from resp
to init
:
resp$ cat /etc/iked/local.pub | ssh 192.0.2.1 'doas tee /etc/iked/pubkeys/fqdn/resp.example.com'
Replace init.example.com
and resp.example.com
with init's and resp's
fully qualified domain names.
If you lack shell or doas access on both servers, you may need to find some other method to transfer the public keys.
Configuring iked.conf
Configure iked.conf(5) for both
resp
and init
:
init# cat /etc/iked.conf init="192.0.2.1" resp="198.51.100.1" initlan="10.0.1.0/24" resplan="10.0.2.0/24" ikev2 'init' active esp \ from $initlan to $resplan \ from $initlan to $resp \ local $init peer $resp \ srcid init.example.com resp# cat /etc/iked.conf init="192.0.2.1" resp="198.51.100.1" initlan="10.0.1.0/24" resplan="10.0.2.0/24" ikev2 'resp' passive esp \ from $resplan to $initlan \ from $resplan to $init \ local $resp peer $init \ srcid resp.example.com
iked(8) refuses to start if permissions are too loose:
init# chmod 0600 /etc/iked.conf resp# chmod 0600 /etc/iked.conf
Appropriate sysctls should be enabled:
init# cat /etc/sysctl.conf net.inet.ip.forwarding=1 net.inet.esp.enable=1 net.inet.ah.enable=1 resp# cat /etc/sysctl.conf net.inet.ip.forwarding=1 net.inet.esp.enable=1 net.inet.ah.enable=1
(Both esp and ah sysctls are enabled by default, so they can normally be omitted)
resp
will need to open the isakmp UDP port and possibly the ipsec-nat-t UDP
port if the init
is behind NAT. It also needs to allow the esp protocol. In
/etc/pf.conf?, these rules may be needed:
init="192.0.2.1" resp="198.51.100.1" pass in log on $ext_if proto udp from $init to $resp port {isakmp, ipsec-nat-t} tag IKED pass in log on $ext_if proto esp from $init to $resp tag IKED
Make sure to define a macro for ext_if
to refer to the actual external interface.
Afterwards, reload packet filter:
resp# pfctl -f /etc/pf.conf
Running iked
First, we run iked in the foreground to check security associations (SAs) can be exchanged properly:
init# iked -dv ... spi=0x30d3299fdb266d78: send IKE_SA_INIT req 0 peer 198.51.100.1:500 local 192.0.2.1:500, 518 bytes spi=0x30d3299fdb266d78: recv IKE_SA_INIT res 0 peer 198.51.100.1:500 local 192.0.2.1:500, 235 bytes, policy 'init' spi=0x30d3299fdb266d78: send IKE_AUTH req 1 peer 198.51.100.1:500 local 192.0.2.1:500, 491 bytes spi=0x30d3299fdb266d78: recv IKE_AUTH res 1 peer 198.51.100.1:500 local 192.0.2.1:500, 369 bytes, policy 'init' spi=0x30d3299fdb266d78: ikev2_childsa_enable: loaded SPIs: 0x6a275df5, 0x9cfd8ba5 (enc aes-128-gcm esn) spi=0x30d3299fdb266d78: ikev2_childsa_enable: loaded flows: ESP-10.0.1.0/24=10.0.2.0/24(0), ESP-10.0.1.0/24=198.51.100.1/32(0) spi=0x30d3299fdb266d78: established peer 198.51.100.1:500[FQDN/init.example.com] local 192.0.2.1:500[FQDN/resp.example.com] policy 'init' as initiator (enc aes-128-gcm group curve25519 prf hmac-sha2-256) resp# iked -dv ... spi=0x30d3299fdb266d78: recv IKE_SA_INIT req 0 peer 192.0.2.1:500 local 198.51.100.1:500, 518 bytes, policy 'resp' spi=0x30d3299fdb266d78: send IKE_SA_INIT res 0 peer 192.0.2.1:500 local 198.51.100.1:500, 235 bytes spi=0x30d3299fdb266d78: recv IKE_AUTH req 1 peer 192.0.2.1:500 local 198.51.100.1:500, 491 bytes, policy 'resp' spi=0x30d3299fdb266d78: send IKE_AUTH res 1 peer 192.0.2.1:500 local 198.51.100.1:500, 369 bytes spi=0x30d3299fdb266d78: ikev2_childsa_enable: loaded SPIs: 0x6a275df5, 0x9cfd8ba5 (enc aes-128-gcm esn) spi=0x30d3299fdb266d78: ikev2_childsa_enable: loaded flows: ESP-10.0.2.0/24=10.0.1.0/24(0), ESP-10.0.2.0/24=192.0.2.1/32(0) spi=0x30d3299fdb266d78: established peer 192.0.2.1:500[FQDN/resp.example.com] local 198.51.100.1:500[FQDN/init.example.com] policy 'resp' as responder (enc aes-128-gcm group curve25519 prf hmac-sha2-256)
If successful, you should see an IKE SA init request, followed by an IKE AUTH response, followed by an establishing connection to the peer.
If keys aren't set up properly, you may see error messages like below:
spi=0xc1a541e28fb2a7a3: ikev2_dispatch_cert: peer certificate is invalid spi=0xc1a541e28fb2a7a3: ikev2_send_auth_failed: authentication failed for FQDN/init.example.com ... spi=0xc1a541e28fb2a7a3: sa_free: authentication failed notification from peer
Once iked is confirmed to work, use rcctl on both servers:
init# rcctl enable iked init# rcctl start iked iked(ok) resp# rcctl enable iked resp# rcctl start iked iked(ok)
tcpdump? can verify if the key exchange succeeded:
init# tcpdump -ne -i vio0 tcpdump: listening on vio0, link-type EN10MB 23:40:49.446068 e8:8b:11:11:11:11 fe:e1:ba:dc:40:69 0800 560: 192.0.2.1.500 > 198.51.100.1.500: isakmp v2.0 exchange IKE_SA_INIT cookie: 9dc861be02b4775d->0000000000000000 msgid: 00000000 len: 518 23:40:49.451306 fe:e1:ba:dc:40:69 e8:8b:11:11:11:11 0800 277: 198.51.100.1.500 > 192.0.2.1.500: isakmp v2.0 exchange IKE_SA_INIT cookie: 9dc861be02b4775d->e477a8efd0aab8c3 msgid: 00000000 len: 235 23:40:49.455770 e8:8b:11:11:11:11 fe:e1:ba:dc:40:69 0800 581: 192.0.2.1.500 > 198.51.100.1.500: isakmp v2.0 exchange IKE_AUTH cookie: 9dc861be02b4775d->e477a8efd0aab8c3 msgid: 00000001 len: 539 23:40:49.463241 fe:e1:ba:dc:40:69 e8:8b:11:11:11:11 0800 459: 198.51.100.1.500 > 192.0.2.1.500: isakmp v2.0 exchange IKE_AUTH cookie: 9dc861be02b4775d->e477a8efd0aab8c3 msgid: 00000001 len: 417
ipsecctl shows the IPsec flows:
init# 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/init.example.com dstid FQDN/resp.example.com type require flow esp in from 198.51.100.1 to 10.0.1.0/24 peer 198.51.100.1 srcid FQDN/init.example.com dstid FQDN/resp.example.com type require flow esp out from 10.0.1.0/24 to 10.0.2.0/24 peer 198.51.100.1 srcid FQDN/init.example.com dstid FQDN/resp.example.com type require flow esp out from 10.0.1.0/24 to 198.51.100.1 peer 198.51.100.1 srcid FQDN/init.example.com dstid FQDN/resp.example.com type require SAD: esp tunnel from 192.0.2.1 to 198.51.100.1 spi 0xcb5c66a6 enc aes-128-gcm esp tunnel from 198.51.100.1 to 192.0.2.1 spi 0xfcbab443 enc aes-128-gcm
ikectl shows the security associations:
resp# ikectl show sa iked_sas: 0xe2401b35780 rspi 0xc0e414c2ee02f63a ispi 0x7556f8d937f9a032 198.51.100.1:500->192.0.2.1:500<FQDN/init.example.com>[] ESTABLISHED r nexti 0x0 pol 0xe240b59c000 sa_childsas: 0xe2423e99900 ESP 0x7b7d374e in 192.0.2.1:500 -> 198.51.100.1:500 (LA) B=0x0 P=0xe2423e83600 @0xe2401b35780 sa_childsas: 0xe2423e83600 ESP 0x8be2ef36 out 198.51.100.1:500 -> 192.0.2.1:500 (L) B=0x0 P=0xe2423e99900 @0xe2401b35780 sa_flows: 0xe2401b4b800 ESP out 10.0.2.0/24 -> 10.0.1.0/24 [0]@-1 (L) @0xe2401b35780 sa_flows: 0xe2423e8fc00 ESP in 10.0.1.0/24 -> 10.0.2.0/24 [0]@-1 (L) @0xe2401b35780 sa_flows: 0xe2401b3b800 ESP out 10.0.2.0/24 -> 192.0.2.1/32 [0]@-1 (L) @0xe2401b35780 sa_flows: 0xe2401b15800 ESP in 192.0.2.1/32 -> 10.0.2.0/24 [0]@-1 (L) @0xe2401b35780 iked_activesas: 0xe2423e99900 ESP 0x7b7d374e in 192.0.2.1:500 -> 198.51.100.1:500 (LA) B=0x0 P=0xe2423e83600 @0xe2401b35780 iked_activesas: 0xe2423e83600 ESP 0x8be2ef36 out 198.51.100.1:500 -> 192.0.2.1:500 (L) B=0x0 P=0xe2423e99900 @0xe2401b35780 iked_flows: 0xe2423e8fc00 ESP in 10.0.1.0/24 -> 10.0.2.0/24 [0]@-1 (L) @0xe2401b35780 iked_flows: 0xe2401b15800 ESP in 192.0.2.1/32 -> 10.0.2.0/24 [0]@-1 (L) @0xe2401b35780 iked_flows: 0xe2401b4b800 ESP out 10.0.2.0/24 -> 10.0.1.0/24 [0]@-1 (L) @0xe2401b35780 iked_flows: 0xe2401b3b800 ESP out 10.0.2.0/24 -> 192.0.2.1/32 [0]@-1 (L) @0xe2401b35780 iked_dstid_sas: 0xe2401b35780 rspi 0xc0e414c2ee02f63a ispi 0x7556f8d937f9a032 198.51.100.1:500->192.0.2.1:500<FQDN/init.example.com>[] ESTABLISHED r nexti 0x0 pol 0xe240b59c000
Testing
To test if the tunnel works, you should attempt to send from a device on one
LAN to a device on another LAN. For example, on one of the machines in
init
's LAN, try sending ping from 10.0.1.2 to 10.0.2.2.
initlan$ ping -I 10.0.1.2 10.0.2.2 PING 10.0.2.2 (10.0.2.2): 56 data bytes 64 bytes from 10.0.2.2: icmp_seq=0 ttl=108 time=45.523 ms 64 bytes from 10.0.2.2: icmp_seq=1 ttl=108 time=45.304 ms
Run tcpdump? on an enc(4) interface to see packets prior to encapsulation and after decapsulation:
init# tcpdump -ne -i enc0 tcpdump: listening on enc0, link-type ENC 23:05:45.520270 (authentic,confidential): SPI 0xf997b9a0: 10.0.2.2 > 10.0.1.2: icmp: echo request (encap) 23:05:45.520825 (authentic,confidential): SPI 0xa484d765: 10.0.1.2 > 10.0.2.2: icmp: echo reply (encap)
If you lack devices to test with, please consult the site-to-site network lab guide for vmm.
IPsec Statistics
init# netstat -s -p esp esp: 144 input ESP packets 150 output ESP packets 0 packets from unsupported protocol families .... 0 raw ESP packets for encapsulating TDB received 19976 input bytes 20406 output bytes init# netstat -s -p ah ah: 0 input AH packets 0 output AH packets 0 packets from unsupported protocol families ... 0 output packets could not be sent 0 input bytes 0 output bytes
As expected, there are no AH packets being sent because the IPsec gateways are using ESP for confidentiality. For more information, see ipsec(4).