From cf6b682cf8e5bfa1f270faaff746798301764682 Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Sat, 12 Apr 2025 18:09:02 +0200 Subject: [PATCH] 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. --- roles/debian/files/nftables.conf | 8 --- roles/ocserv/README.md | 11 ++++ roles/ocserv/files/ocserv-script | 25 +++++++++ roles/ocserv/files/ocserv.nft | 14 +++++ roles/ocserv/handlers/main.yml | 15 +++++ roles/ocserv/tasks/main.yml | 78 ++++++++++++++++++++++++++ roles/ocserv/templates/ocserv-group.j2 | 3 + roles/ocserv/templates/ocserv.conf.j2 | 26 +++++++++ setup.yml | 5 ++ 9 files changed, 177 insertions(+), 8 deletions(-) create mode 100644 roles/ocserv/README.md create mode 100644 roles/ocserv/files/ocserv-script create mode 100644 roles/ocserv/files/ocserv.nft create mode 100644 roles/ocserv/handlers/main.yml create mode 100644 roles/ocserv/tasks/main.yml create mode 100644 roles/ocserv/templates/ocserv-group.j2 create mode 100644 roles/ocserv/templates/ocserv.conf.j2 diff --git a/roles/debian/files/nftables.conf b/roles/debian/files/nftables.conf index 430db42..6ec6a16 100644 --- a/roles/debian/files/nftables.conf +++ b/roles/debian/files/nftables.conf @@ -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" diff --git a/roles/ocserv/README.md b/roles/ocserv/README.md new file mode 100644 index 0000000..49c6f67 --- /dev/null +++ b/roles/ocserv/README.md @@ -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": "" + "routes": { + "": [ "", … ] + … + } + } diff --git a/roles/ocserv/files/ocserv-script b/roles/ocserv/files/ocserv-script new file mode 100644 index 0000000..c56a7da --- /dev/null +++ b/roles/ocserv/files/ocserv-script @@ -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 diff --git a/roles/ocserv/files/ocserv.nft b/roles/ocserv/files/ocserv.nft new file mode 100644 index 0000000..5879f16 --- /dev/null +++ b/roles/ocserv/files/ocserv.nft @@ -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 + } +} diff --git a/roles/ocserv/handlers/main.yml b/roles/ocserv/handlers/main.yml new file mode 100644 index 0000000..34e7883 --- /dev/null +++ b/roles/ocserv/handlers/main.yml @@ -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" diff --git a/roles/ocserv/tasks/main.yml b/roles/ocserv/tasks/main.yml new file mode 100644 index 0000000..3201c77 --- /dev/null +++ b/roles/ocserv/tasks/main.yml @@ -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 diff --git a/roles/ocserv/templates/ocserv-group.j2 b/roles/ocserv/templates/ocserv-group.j2 new file mode 100644 index 0000000..d9d04e0 --- /dev/null +++ b/roles/ocserv/templates/ocserv-group.j2 @@ -0,0 +1,3 @@ +{% for route in item.value %} +route = {{ route }} +{% endfor %} diff --git a/roles/ocserv/templates/ocserv.conf.j2 b/roles/ocserv/templates/ocserv.conf.j2 new file mode 100644 index 0000000..3ddeadf --- /dev/null +++ b/roles/ocserv/templates/ocserv.conf.j2 @@ -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 }} diff --git a/setup.yml b/setup.yml index 7778cbe..3867b8a 100644 --- a/setup.yml +++ b/setup.yml @@ -84,6 +84,11 @@ - nginx - unifi +- hosts: vrata + roles: + - nginx + - ocserv + - hosts: web-front roles: - nginx