Add ocserv role

Create a self-signed CA, set up group configs, add script to allow new
connections through the firewall.

In the base debian role, drop the default nftables forward chain with
drop policy because it clashes with this. If you enable forwarding on
a debian host, make sure to configure the firewall.
This commit is contained in:
Timotej Lazar 2025-04-12 18:09:02 +02:00
parent a1c7be8184
commit cf6b682cf8
9 changed files with 177 additions and 8 deletions

View file

@ -23,14 +23,6 @@ table inet filter {
nd-neighbor-advert, nd-neighbor-solicit, nd-router-advert, nd-router-solicit
} accept comment "accept IPv6 neighbor discovery"
}
chain forward {
type filter hook forward priority filter; policy drop
}
chain output {
type filter hook output priority filter; policy accept
}
}
include "/etc/nftables.d/*.nft"

11
roles/ocserv/README.md Normal file
View file

@ -0,0 +1,11 @@
Install and configure ocserv with a script to configure nftables on (dis)connection.
Create a self‐signed CA authority for issuing user certificates. User and group are read from the CN and OU certificate subject fields, respectively. To configure VPN groups, define the variable `vpn` as follows:
"vpn": {
"network": "<VPN network>"
"routes": {
"<group>": [ "<route>", … ]
}
}

View file

@ -0,0 +1,25 @@
#!/bin/sh
set -x
[ -n "$DEVICE" ] || exit 1
[ -n "$USERNAME" ] || exit 2
[ -n "$IP_REMOTE" ] || exit 3
chain="inet ocserv client-${USERNAME}"
remote_ip="${IP_REMOTE%/*}"
case "${REASON}" in
connect)
nft "add chain ${chain} { type filter hook forward priority filter; policy accept; }"
nft "flush chain ${chain}" # in case it already existed and not empty
if [ -n "$OCSERV_ROUTES" ] ; then
# convert netmask to prefix len, e.g. /255.0.0.0 to /8 and replace spaces with commas
routes="$(netmask $OCSERV_ROUTES | paste -s -d ',' | tr -d '[:space:]')"
nft "add rule ${chain} iif ${DEVICE} ip saddr ${remote_ip} ip daddr { ${routes} } mark set 0x100"
fi
;;
disconnect)
nft "delete chain ${chain}"
;;
esac

View file

@ -0,0 +1,14 @@
table inet ocserv {
chain forward {
type filter hook forward priority filter + 10; policy drop;
ct state { established, related } accept
meta mark 0x100 accept
}
}
table ip ocserv {
chain postrouting {
type nat hook postrouting priority srcnat; policy drop;
meta mark 0x100 masquerade
}
}

View file

@ -0,0 +1,15 @@
- name: reload nftables
service:
name: nftables
state: reloaded
when: "'handler' not in ansible_skip_tags"
- name: reload systemd
command: systemctl daemon-reload
when: "'handler' not in ansible_skip_tags"
- name: restart ocserv
service:
name: ocserv
state: restarted
when: "'handler' not in ansible_skip_tags"

View file

@ -0,0 +1,78 @@
- name: Install packages
package:
name:
- netmask # for ocserv-script
- ocserv
install_recommends: false # don’t install dnsmasq for whatever reason
- name: Configure firewall
copy:
dest: /etc/nftables.d/
src: ocserv.nft
notify: reload nftables
- name: Generate CA key
command:
cmd: openssl genpkey -algorithm ec -pkeyopt ec_paramgen_curve:P-256 -out ca.key
chdir: /etc/ocserv
creates: ca.key
notify: restart ocserv
- name: Create CA certificate
command:
cmd: >
openssl req -key ca.key -out ca.crt -new -x509 -days 3650
-subj "/O=FRI/CN=vrata"
-addext keyUsage=critical,keyCertSign,cRLSign
chdir: /etc/ocserv
creates: ca.crt
notify: restart ocserv
# this script allows routing from the client to their networks on connection
- name: Install ocserv firewall script
copy:
dest: /usr/local/bin/
src: ocserv-script
mode: 755
- name: Configure ocserv
template:
dest: /etc/ocserv/ocserv.conf
src: ocserv.conf.j2
notify: restart ocserv
- name: Create config-per-group directory
file:
path: /etc/ocserv/config-per-group/
state: directory
- name: Configure ocserv routes for each group
template:
dest: '/etc/ocserv/config-per-group/{{ item.key }}'
src: ocserv-group.j2
loop: '{{ vpn.routes | dict2items }}'
notify: restart ocserv
- name: Create ocserv service override directory
file:
path: /etc/systemd/system/ocserv.service.d
state: directory
owner: root
group: root
mode: 0755
- name: Set ocserv to start after network is online
copy:
dest: /etc/systemd/system/ocserv.service.d/override.conf
content: |
[Unit]
After=network-online.target
Wants=network-online.target
notify: reload systemd
- name: Enable IP forwarding
sysctl:
name: net.ipv4.ip_forward
value: 1
sysctl_file: /etc/sysctl.d/99-local.conf
sysctl_set: true

View file

@ -0,0 +1,3 @@
{% for route in item.value %}
route = {{ route }}
{% endfor %}

View file

@ -0,0 +1,26 @@
listen-host = {{ dns_name }}
tcp-port = 443
server-cert = /etc/letsencrypt/live/{{ dns_name }}/fullchain.pem
server-key = /etc/letsencrypt/live/{{ dns_name }}/privkey.pem
run-as-user = ocserv
run-as-group = ocserv
socket-file = /run/ocserv-socket
chroot-dir = /var/lib/ocserv
connect-script = /usr/local/bin/ocserv-script
disconnect-script = /usr/local/bin/ocserv-script
device = vpns
cisco-client-compat = true
dtls-legacy = true
compression = true
isolate-workers = true
auth = certificate
ca-cert = /etc/ocserv/ca.crt
cert-user-oid = 2.5.4.3
cert-group-oid = 2.5.4.11
config-per-group = /etc/ocserv/config-per-group/
default-domain = {{ domain }}
ipv4-network = {{ vpn.network }}
route = {{ vpn.network }}

View file

@ -84,6 +84,11 @@
- nginx
- unifi
- hosts: vrata
roles:
- nginx
- ocserv
- hosts: web-front
roles:
- nginx