$_ BSDHowTo.ch
How To... Why not..? Scripts Patches RSS logo

How to setup IPsec transport encryption

Last update: 2023-10-29

Introduction

This article shows how to use ipsec(4) transport mode to protect traffic between two hosts on the Internet. Transport mode comes in handy if you want to encrypt traffic between two publicly reachable hosts without the hassle of a full-blown VPN.

Today, most encryption you see happens on the transport layer by TLS. This is perfect for all the connections coming to your system from all over the Internet. But if you have services that should only be accessible for certain other hosts you might find it easier to shift the encryption one layer down. This way you don’t only eliminate the need of certificate management for each of the services, you don’t even let the public know that these services exist.

I use two systems for the examples in this article:

Hostname IPv4 IPv6
host1.example.net 192.0.2.1 2001:db8:1::c000:0201
host2.example.net 192.0.2.2 2001:db8:1::c000:0202

Private keys for authentication

Setting up a PKI for one point-to-point connection is rather pointless. The OpenBSD IPsec implementation allows the use of public key pairs for mutual authentication of the two hosts. OpenBSD comes with a key-pair for iked(8). But you can easily generate a new one for the IPsec connection.

Generate a new key-pair on each of the two hosts and copy the public key over to the other host:

host1$ cd /etc/iked
host1$ doas rm local.pub private/local.key
host1$ doas openssl ecparam -genkey -name prime256v1 -out private/local.key
host1$ doas openssl ec -pubout -in private/local.key -out local.pub
host1$ scp local.pub host2.example.net:

The same steps are required on the second host:

host2$ cd /etc/iked
host2$ doas rm local.pub private/local.key
host2$ doas openssl ecparam -genkey -name prime256v1 -out private/local.key
host2$ doas openssl ec -pubout -in private/local.key -out local.pub
host2$ scp local.pub host1.example.net:

Whilst here move the copied public key of host1 to the right place with the right name and permissions:

host2$ doas mv local.pub /etc/iked/pubkeys/ipv4/192.0.2.1
host2$ doas chown root:wheel /etc/iked/pubkeys/ipv4/192.0.2.1
host2$ cd /etc/iked/pubkeys/ipv6
host2$ doas ln ../ipv4/192.0.2.1 2001:db8:1::c000:0201

Back on host1 do the same with the public key of host2:

host1$ doas mv local.pub /etc/iked/pubkeys/ipv4/192.0.2.2
host1$ doas chown root:wheel /etc/iked/pubkeys/ipv4/192.0.2.2
host1$ cd /etc/iked/pubkeys/ipv6
host1$ doas ln ../ipv4/192.0.2.2 2001:db8:1::c000:0202

Configure iked(8)

Let’s use the latest and greatest for key exchange: IKEv2. On OpenBSD this protocol is implemented by iked(8) which uses iked.conf(5) as its configuration file.

By default flows in iked.conf(5) are passive. That means that iked(8) will be listening for the peer to initiate the communication. But if both peers are listening, nobody is talking. Also if you make one host passive and the other one active your IPsec tunnel will not get back on its feet once the passive host gets rebooted. Therefore it is the best way to make both hosts active.

The file /etc/iked.conf on host1 looks like this:

ikev2 "host2_v4" active transport \
    from 192.0.2.1 \
    to 192.0.2.2
    srcid 192.0.2.1

ikev2 "host2_v6" active transport \
    from 2001:db8:1::c000:0201 \
    to 2001:db8:1::c000:0202 \
    srcid 2001:db8:1::c000:0201

On host2 the file /etc/iked.conf looks like this:

ikev2 "host1_v4" active transport \
    from 192.0.2.2 \
    to 192.0.2.1
    srcid 192.0.2.2

ikev2 "host1_v6" active transport \
    from 2001:db8:1::c000:0202 \
    to 2001:db8:1::c000:0201 \
    srcid 2001:db8:1::c000:0202

Add required pf(4) rules

The rules I show you here assume that your pf.conf(5) contains some variant of block all as default rule. This makes sure that the services you want to keep hidden from the public actually are.

Make your live and rules both easier by skipping filtering on the whole interface group enc:

set skip on { lo enc }

Macros for easier readability:

host1="{ 192.0.2.1 2001:db8:1::c000:0201 }"
host2="{ 192.0.2.2 2001:db8:1::c000:0202 }"

Allow incoming IPsec protocols from the peer system:

pass in on egress proto udp from $host2 to $host1 port isakmp
pass in on egress proto udp from $host2 to $host1 port ipsec-nat-t
pass in on egress proto esp from $host2 to $host1

Allow outgoing IPsec protocols to the peer system:

pass out on egress proto udp from $host1 to $host2 port isakmp
pass out on egress proto udp from $host1 to $host2 port ipsec-nat-t
pass out on egress proto esp from $host1 to $host2

Enable and start everything

Enable IPsec with these commands on both hosts (start with the passive one):

$ doas pfctl -f /etc/pf.conf
$ doas rcctl enable ipsec iked
$ doas rcctl start iked

You can check if the secured channel is established using either of the following commands:

$ doas ipsecctl -s all
$ doas ikectl show sa

Now all the traffic between the hosts is encrypted using IPsec. This allows you to use services between the two hosts without implementing TLS for each and every service separately.