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