A quick note ✍️

This guide will walk through the set up of an IKEv2 VPN using OpenIKED on OpenBSD. It will detail a “road warrior” configuration, and use a PSK (pre-shared-key) for authentication. I’m sure it can be easily adapted to work on any other platforms that OpenIKED is available on, but keep in mind my steps are specifically for OpenBSD.

Server Configuration

As with all my home infrastructure, I crafted this set-up declaratively. So, I had the deployment of the VM setup in Terraform (deployed on my private Triton cluster), and wrote the configuration in Ansible, then tied them together using radekg/terraform-provisioner-ansible.

One of the reasons I love Ansible is that its syntax is very simplistic, yet expressive. As such, I feel it fits very well into explaining these steps with snippets of the playbook I wrote.

I’ll link the full playbook a bit further down for those interested.

sysctl parameters

First off, we need to alter the kernel state so it’s fit to manage VPN traffic. Naturally, the parameters we’re going to be setting are in the net.inet namespace:

- name: Ensure sysctl params are set

sysctl:

name: net.inet.{{item}}

value: 1

with_items:

- ip.forwarding

- esp.enable

- ah.enable

- ipcomp.enable

Hopefully this is clear enough. For those unfamiliar with Ansible, I’ll show what it looks like in the shell:

$ sysctl net.inet.ip.forwarding=1

$ sysctl net.inet.esp.enable=1

$ sysctl net.inet.ah.enable=1

$ sysctl net.inet.ipcomp.enable=1

These should also be persisted to /etc/sysctl.conf :

Note: this is already handled by the above Ansible task

net.inet.ip.forwarding=1

net.inet.esp.enable=1

net.inet.ah.enable=1

net.inet.ipcomp.enable=1

The naughty list (optional) 😈

If you’d like to maintain a list of those out there trying to constantly hit SSH on your network which you can use to outright block them, follow this step. We’ll use this in our pf configuration a bit further down.

- name: Ensure /etc/badguys exists

copy:

dest: /etc/badguys

owner: root

group: wheel

mode: 0640

force: false

content: ""

Configure the VPN network interface

You can use whatever subnet configuration you desire, here. But, if you choose to do something outside of 10.0.1.0/24 (used in these examples); you’ll need to make sure you substitute it elsewhere throughout the post.

- name: Ensure the enc0 interface is configured

copy:

dest: /etc/hostname.enc0

owner: root

group: wheel

mode: 0640

content: |

inet 10.0.1.1 255.255.255.0 10.0.1.255

up

notify: Reload network configuration

Note: the notify expression here is just running $ sh /etc/netstart

Configure the firewall

If you didn’t bother with the “naughty list” step earlier, leave out any lines containing “bad” or “badguys”.

Also, you’ll want to make sure you set the intra macro to the interface your system uses for network connectivity (presumably to the internet). Mine is vio0 , but yours may be something different. You can find this out using good old ifconfig .

- name: Ensure pf is configured

copy:

dest: /etc/pf.conf

owner: root

group: wheel

mode: 0644

content: |

intra = "vio0"

vpn = "enc0" set reassemble yes

set block-policy return

set loginterface egress

set skip on { lo, enc } match in all scrub (no-df random-id max-mss 1440) table <ossec_fwtable> persist

table <badguys> persist file "/etc/badguys" block in quick on egress from <badguys> label "bad"

block out quick on egress to <badguys> label "bad"

block in quick on egress from <ossec_fwtable> label "bad"

block out quick on egress to <ossec_fwtable> label "bad"

block in quick from urpf-failed label uRPF

block return log pass out all modulate state pass in on egress proto { ah, esp }

pass in on egress proto udp to (egress) port { isakmp, ipsec-nat-t }

pass out on egress from 10.0.1.0/24 to any nat-to (egress)

pass out on $intra from 10.0.1.0/24 to $intra:network nat-to ($intra) pass in quick inet proto icmp icmp-type { echoreq, unreach } pass in on egress proto tcp from any to (egress) port 22 keep state (max-src-conn 40, max-src-conn-rate 10/30, overload <badguys> flush global)

pass in on $intra proto { udp tcp } from any to ($intra) port 53

notify: Reload pf configuration

Note: the notify expression here is just running $ pfctl -f /etc/pf.conf

Configure the iked service

For this step, you’ll need to have generated a secure pre-shared-key. Represented here in the contents of /etc/iked.conf as {{iked_psk}} (Ansible variable syntax).

This is the secret you’ll be using on your clients to connect. Make sure the string itself is secure and stored securely!

Personally; I’ve used Ansible Vault to store an encrypted variable in my Ansible configuration for deployment, and use pass to carry it around with me on my client devices.

- name: Ensure iked is configured

copy:

dest: /etc/iked.conf

owner: root

group: wheel

mode: 0600

content: |

ikev2 "inet" passive ipcomp esp \

from 0.0.0.0/0 to 10.0.1.0/24 \

from 10.0.0.0/24 to 10.0.1.0/24 \

local egress peer any \

psk "{{iked_psk}}" \

config protected-subnet 0.0.0.0/0 \

config address 10.0.1.0/24 \

config name-server 10.19.3.1 \

tag "IKED" tap enc0

notify: Reload iked service

Note: the notify expression here is just running $ rcctl reload iked

You may notice the name-server configuration here. This is the address of the DNS server you want clients to query when connected to the VPN. Yours is very likely a different address, so make sure you set this appropriately!

For more information on iked configuration, see: iked.conf(5)

Finally, start/enable the iked service ⏯

- name: Ensure iked service is running/enabled

service:

name: iked

state: started

enabled: true

In the shell: $ rcctl start iked && rcctl enable iked

The Ansible playbook