How To... | Why not..? | Scripts | Patches | ![]() |
Last update: 2023-10-29
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 |
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
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
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 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.