diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5d48b33 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2021 Chris Bouchard + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/conf/namespaced-wireguard-vpn.conf b/conf/namespaced-wireguard-vpn.conf new file mode 100644 index 0000000..cea2c95 --- /dev/null +++ b/conf/namespaced-wireguard-vpn.conf @@ -0,0 +1,33 @@ +# Name of the VPN network namespace +NETNS_NAME=vpn + +# Name of the VPN WireGuard interface +WIREGUARD_NAME=wg-vpn + +# Endpoint of the VPN WireGuard server +WIREGUARD_ENDPOINT=1.2.3.4 + +# Public key of the VPN WireGuard peer +WIREGUARD_VPN_PUBLIC_KEY=abcdFAKEefghFAKEijklFAKEmnopFAKEqrstFAKEuvw= + +# Comma-separated list of allowed IP addresses for the VPN WireGuard interface +WIREGUARD_ALLOWED_IPS=0.0.0.0/0,::0/0 + +# Comma-separated list of static IP addresses to assign to the VPN WireGuard +# interface +WIREGUARD_IP_ADDRESSES=10.0.0.1/32,fd12:3456:789a:1::1/128 + +# Name of the init-facing tunnel interface +TUNNEL_INIT_NAME=veth-vpn0 + +# Comma-separated list of static IP addresses to assign to the init-facing +# (public) tunnel interface +TUNNEL_INIT_IP_ADDRESSES=10.127.0.1/24 + +# Name of the VPN-facing tunnel interface +TUNNEL_VPN_NAME=veth-vpn1 + +# Comma-separated list of static IP addresses to assign to the VPN-facing +# tunnel interface +TUNNEL_VPN_IP_ADDRESSES=10.127.0.2/24 + diff --git a/lib/base.sh b/lib/base.sh new file mode 100644 index 0000000..fa06d43 --- /dev/null +++ b/lib/base.sh @@ -0,0 +1,5 @@ +die() { + [[ -n "$1" ]] && echo $1 >&2 + exit 1 +} + diff --git a/lib/interface.sh b/lib/interface.sh new file mode 100755 index 0000000..699e15d --- /dev/null +++ b/lib/interface.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +source base.sh + +case "$1" + up) + ip link add "$WIREGUARD_NAME" type wireguard || die + + wg set \ + private-key <(echo "$WIREGUARD_PRIVATE_KEY") \ + peer "$WIREGUARD_VPN_PUBLIC_KEY" \ + endpoint "$WIREGUARD_ENDPOINT" \ + allowed-ips "$WIREGUARD_ALLOWED_IPS" || die + + ip link set "$WIREGUARD_NAME" netns "$NETNS_NAME" || die + + # Addresses are comma-separated, so to split them. + xargs -d ',' -I '{}' \ + ip -n "$NETNS_NAME" address add '{}' dev "$VPN_WIREGUARD_NAME" \ + <<<"$WIREGUARD_IP_ADDRESSES" || die + + ip -n "$NETNS_NAME" link set "$WIREGUARD_NAME" up || die + ip -n "$NETNS_NAME" route add default dev "$WIREGUARD_NAME" || die + ;; + + down) + # We need to delete the WireGuard interface. It's initially created in + # the init network namespace, then moved to the VPN namespace. + # Depending how well the "up" operation went, it might be in either. + ip -n "$NETNS_NAME" link delete "$WIREGUARD_NAME" || + ip link delete "$WIREGUARD_NAME" || die + ;; +esac + diff --git a/lib/netns.sh b/lib/netns.sh new file mode 100755 index 0000000..7bdd39f --- /dev/null +++ b/lib/netns.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash + +source base.sh + +case "$1" + up) + ip netns add "$NETNS_NAME" || die + + if [[ -n "$PRIVATE_NETNS_BIND_MOUNT" ]] + then + umount "/var/run/netns/$NETNS_NAME" || die + mount --bind "$PRIVATE_NETNS_BIND_MOUNT" "/var/run/netns/$NETNS_NAME" || die + fi + + ip -n "$NETNS_NAME" link set lo up || die + ;; + + down) + ip netns delete "$NETNS_NAME" || die + ;; +esac + diff --git a/lib/tunnel.sh b/lib/tunnel.sh new file mode 100755 index 0000000..b6eee51 --- /dev/null +++ b/lib/tunnel.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +source base.sh + +case "$1" + up) + ip link add "$TUNNEL_INIT_NAME" type veth \ + peer "$TUNNEL_VPN_NAME" netns "$NETNS_NAME" || die + + # Addresses are comma-separated, so to split them. + xargs -d ',' -I '{}' \ + ip address add '{}' dev "$TUNNEL_INIT_NAME" \ + <<<"$TUNNEL_INIT_IP_ADDRESSES" || die + xargs -d ',' -I '{}' \ + ip -n "$NETNS_NAME" address add '{}' dev "$TUNNEL_VPN_NAME" \ + <<<"$TUNNEL_VPN_IP_ADDRESSES" || die + + ip link set "$TUNNEL_INIT_NAME" up || die + ip -n "$NETNS_NAME" link set "$TUNNEL_VPN_NAME" up || die + ;; + + down) + EXIT_CODE=0 + ip link delete "$TUNNEL_INIT_NAME" || EXIT_CODE=$? + ip -n "$NETNS_NAME" link delete "$TUNNEL_VPN_NAME" || EXIT_CODE=$? + [[ EXIT_CODE -eq 0 ]] || die + ;; +esac + diff --git a/namespaced-wireguard-vpn.spec.rpkg b/namespaced-wireguard-vpn.spec.rpkg new file mode 100644 index 0000000..146cf7f --- /dev/null +++ b/namespaced-wireguard-vpn.spec.rpkg @@ -0,0 +1,60 @@ +Name: {{{ git_dir_name }}} +Version: {{{ git_dir_version }}} +Release: 1%{?dist} +Summary: Systemd configuration for a network namespace containing a WireGuard VPN connection + +License: MIT +VCS: {{{ git_dir_vcs }}} + +Source: {{{ git_dir_pack }}} + +BuildRequires: systemd-rpm-macros +Requires: bash, iproute2, systemd, xargs, wireguard-tools + +BuildArch: noarch + +%description +This package contains configuration for Systemd to create and manage a network +namespace containing a WireGuard VPN connection. + +namespaced-wireguard-vpn.target: + namespaced-wireguard-vpn-netns.service: + Creates the network namespace + namespaced-wireguard-vpn-interface.service: + Creates the WireGuard connection and moves it into the namespace + namespaced-wireguard-vpn-tunnel.service: + Creates a Veth tunnel into the namespace + +%prep +{{{ git_dir_setup_macro }}} + +%install +install --mode=755 --directory=%{buildroot}%{_libdir}/%{name} +install --mode=755 --target-directory=%{buildroot}%{_libdir}/%{name} lib/* + +install --mode=700 --directory %{buildroot}%{_sysconfdir}/%{name} +install --mode=600 --target-directory=%{buildroot}%{_sysconfdir}/%{name} conf/* + +install --mode=755 --directory=%{buildroot}%{_unitdir} +install --mode=644 --target-directory=%{buildroot}%{_unitdir} systemd/* + +%post +%systemd_post namespaced-wireguard-vpn.target + +%preun +%systemd_preun namespaced-wireguard-vpn.target + +%postun +%systemd_postun namespaced-wireguard-vpn.target + +%files +%license LICENSE +%{_libdir}/%{name}/* +%dir %{_sysconfdir}/%{name} +%config(noreplace) %{_sysconfdir}/%{name}/* +%{_unitdir}/* + +%changelog +{{{ git_dir_changelog }}} + +# vim: syntax=spec diff --git a/systemd/namespaced-wireguard-vpn-interface.service b/systemd/namespaced-wireguard-vpn-interface.service new file mode 100644 index 0000000..54ab47d --- /dev/null +++ b/systemd/namespaced-wireguard-vpn-interface.service @@ -0,0 +1,19 @@ +# Based on: +# https://cloudnull.io/2019/04/running-services-in-network-name-spaces-with-systemd/ + +[Unit] +Description=VPN Wireguard Interface +PartOf=namespaced-wireguard-vpn.target +Requires=namespaced-wireguard-vpn-netns.service +After=namespaced-wireguard-vpn-netns.service +After=network-online.target nss-lookup.target + +[Service] +Type=oneshot +RemainAfterExit=yes + +EnvironmentFile=/etc/namespaced-wireguard-vpn/namespaced-wireguard-vpn.conf + +ExecStart=/usr/lib/namespaced-wireguard-vpn/interface.sh up +ExecStopPost=/usr/lib/namespaced-wireguard-vpn/interface.sh down + diff --git a/systemd/namespaced-wireguard-vpn-netns.service b/systemd/namespaced-wireguard-vpn-netns.service new file mode 100644 index 0000000..3e83570 --- /dev/null +++ b/systemd/namespaced-wireguard-vpn-netns.service @@ -0,0 +1,19 @@ +# Based on: +# https://cloudnull.io/2019/04/running-services-in-network-name-spaces-with-systemd/ + +[Unit] +Description=VPN Network Namespace +PartOf=namespaced-wireguard-vpn.target +After=network.target + +[Service] +Type=oneshot +RemainAfterExit=yes +PrivateNetwork=yes + +Environment=PRIVATE_NETNS_BIND_MOUNT=/proc/self/ns/net +EnvironmentFile=/etc/namespaced-wireguard-vpn/namespaced-wireguard-vpn.conf + +ExecStart=/usr/lib/namespaced-wireguard-vpn/netns.sh up +ExecStopPost=/usr/lib/namespaced-wireguard-vpn/netns.sh down + diff --git a/systemd/namespaced-wireguard-vpn-tunnel.service b/systemd/namespaced-wireguard-vpn-tunnel.service new file mode 100644 index 0000000..4a244f1 --- /dev/null +++ b/systemd/namespaced-wireguard-vpn-tunnel.service @@ -0,0 +1,28 @@ +[Unit] +Description=VPN Tunnel Veth Interface +PartOf=namespaced-wireguard-vpn.target +Requires=namespaced-wireguard-vpn-netns.service +After=vpn-netns.service + +[Service] +Type=oneshot +RemainAfterExit=yes + +EnvironmentFile=/etc/namespaced-wireguard-vpn/namespaced-wireguard-vpn.conf + +ExecStart=/usr/lib/namespaced-wireguard-vpn/netns.sh up +ExecStopPost=/usr/lib/namespaced-wireguard-vpn/netns.sh down + +ExecStart=/sbin/ip link add veth-vpn0 type veth peer name veth-vpn1 netns vpn +# TODO: Extract these to configuration +ExecStart=/sbin/ip address add 10.127.0.1/24 dev veth-vpn0 +ExecStart=/sbin/ip -n vpn address add 10.127.0.2/24 dev veth-vpn1 +ExecStart=/sbin/ip link set veth-vpn0 up +ExecStart=/sbin/ip -n vpn link set veth-vpn1 up + +# TODO: What to do if the first one fails? +ExecStop=/sbin/ip -n vpn link set veth-vpn1 down +ExecStop=/sbin/ip link set veth-vpn0 down +ExecStopPost=-/sbin/ip -n vpn link delete veth-vpn1 +ExecStopPost=-/sbin/ip link delete veth-vpn0 + diff --git a/systemd/namespaced-wireguard-vpn.target b/systemd/namespaced-wireguard-vpn.target new file mode 100644 index 0000000..115a03f --- /dev/null +++ b/systemd/namespaced-wireguard-vpn.target @@ -0,0 +1,9 @@ +[Unit] +Description=VPN Network Configuration Target +Requires=namespaced-wireguard-vpn-interface.service +Requires=namespaced-wireguard-vpn-netns.service +Requires=namespaced-wireguard-vpn-tunnel.service + +[Install] +WantedBy=multi-user.target +