diff options
| -rw-r--r-- | roles/gateway/README.md | 94 | ||||
| -rw-r--r-- | roles/gateway/defaults/main.yml | 3 | ||||
| -rw-r--r-- | roles/gateway/handlers/main.yml | 17 | ||||
| -rw-r--r-- | roles/gateway/meta/argument_specs.yml | 20 | ||||
| -rw-r--r-- | roles/gateway/meta/main.yml | 19 | ||||
| -rw-r--r-- | roles/gateway/tasks/main.yml | 73 | ||||
| -rw-r--r-- | roles/gateway/templates/99-wireguard-gateway.rules.j2 | 2 | ||||
| -rw-r--r-- | roles/gateway/templates/gateway-apply-rules.sh.j2 | 93 | ||||
| -rw-r--r-- | roles/gateway/templates/gateway-direct-mode.service.j2 | 14 | ||||
| -rw-r--r-- | roles/gateway/templates/gateway-init.service.j2 | 15 | ||||
| -rw-r--r-- | roles/gateway/templates/gateway-vpn-mode.service.j2 | 14 |
11 files changed, 364 insertions, 0 deletions
diff --git a/roles/gateway/README.md b/roles/gateway/README.md new file mode 100644 index 00000000..97ebf682 --- /dev/null +++ b/roles/gateway/README.md @@ -0,0 +1,94 @@ +# Gateway Role + +Configures a system as a network gateway with dynamic VPN routing capabilities. + +## Overview + +This role makes a system (typically Raspberry Pi) into a network gateway. +Paired with `wireguard`, and `wg-portal` roles, this can control network VPN transparently. + +## Configuration + +### Required Variables + +```yaml +gateway_enabled: true +gateway_local_ipv4_subnet: "192.168.1.0/24" +gateway_router_interface: "end0" +``` + +## Network Architecture + +```txt +Client Devices (192.168.1.0/24) + ↓ +┌─────────────────────────────┐ +│ Raspberry Pi Gateway │ +│ (192.168.1.254) │ +└─────────────────────────────┘ + ↓ +┌─────────────┬───────────────┐ +│ VPN Mode │ Direct Mode │ +│ │ │ +│ WireGuard │ ISP Router │ +│ Tunnel │ (192.168.1.1) │ +│ wg-* │ │ +└─────────────┴───────────────┘ + ↓ ↓ + VPN Server ────────┴───── Internet +``` + +**VPN Mode:** + +- All traffic NAT'd through `wg+` interfaces +- Pi and client traffic both use VPN exit IP +- Most VPN providers IPv4/IPv6 protocol routing automatically + +**Direct Mode Characteristics:** + +- All traffic NAT'd through router interface (`end0`) +- Direct IPv4/IPv6 routing to ISP +- Standard internet routing + +## Requirements + +- **WireGuard Interfaces**: Must follow `wg*` naming pattern (`wg0`, `wg-us1`, etc.) +- **Client Configuration**: Devices must use Pi as default gateway (192.168.1.254) +- **IP Forwarding**: Kernel IP forwarding must be enabled (`net.ipv4.ip_forward=1`) +- **WireGuard Config**: Use default routing (`Table=auto` or unset, NOT `Table=off`) + +## Operation + +### Event-Based System + +```bash +# Check udev rules +cat /etc/udev/rules.d/99-wireguard-gateway.rules + +# Monitor VPN mode service +systemctl status gateway-vpn-mode.service +journalctl -u gateway-vpn-mode.service -f + +# Monitor direct mode service +systemctl status gateway-direct-mode.service +journalctl -u gateway-direct-mode.service -f + +# Test manual execution +/usr/local/bin/gateway-apply-rules vpn +/usr/local/bin/gateway-apply-rules direct +``` + +### Troubleshooting + +```bash +# Verify iptables rules +iptables -L -n -v +iptables -t nat -L -n -v + +# Check WireGuard interface detection +ip link show | grep wg + +# Test traffic routing +curl -4 ifconfig.co # Should show VPN IP when VPN active +curl -6 ifconfig.co # Should show VPN IPv6 when VPN active +``` diff --git a/roles/gateway/defaults/main.yml b/roles/gateway/defaults/main.yml new file mode 100644 index 00000000..4f062143 --- /dev/null +++ b/roles/gateway/defaults/main.yml @@ -0,0 +1,3 @@ +--- +gateway_enabled: false +gateway_router_interface: "{{ ansible_default_ipv4.interface }}" diff --git a/roles/gateway/handlers/main.yml b/roles/gateway/handlers/main.yml new file mode 100644 index 00000000..5dae0f76 --- /dev/null +++ b/roles/gateway/handlers/main.yml @@ -0,0 +1,17 @@ +--- +- name: "Reload sysctl" + become: true + ansible.builtin.command: + cmd: "sysctl -p" + changed_when: false + +- name: "Reload systemd" + become: true + ansible.builtin.systemd_service: + daemon_reload: true + +- name: "Reload udev" + become: true + ansible.builtin.command: + cmd: "udevadm control --reload-rules" + changed_when: false diff --git a/roles/gateway/meta/argument_specs.yml b/roles/gateway/meta/argument_specs.yml new file mode 100644 index 00000000..0312bef2 --- /dev/null +++ b/roles/gateway/meta/argument_specs.yml @@ -0,0 +1,20 @@ +--- +argument_specs: + main: + author: "a14m" + description: "Configure system as intelligent network gateway with dynamic VPN routing" + options: + gateway_enabled: + description: "Enable gateway functionality" + type: "bool" + default: false + required: false + gateway_local_ipv4_subnet: + description: "Local subnet CIDR for NAT and routing rules (e.g., '192.168.178.0/24')" + type: "str" + required: true + gateway_router_interface: + description: "Default route interface for direct routing mode" + type: "str" + default: "{{ ansible_default_ipv4.interface }}" + required: false diff --git a/roles/gateway/meta/main.yml b/roles/gateway/meta/main.yml new file mode 100644 index 00000000..29482365 --- /dev/null +++ b/roles/gateway/meta/main.yml @@ -0,0 +1,19 @@ +--- +dependencies: + - role: "systemd" +galaxy_info: + author: "a14m" + description: "Configure system as network gateway (with VPN forwarding capablilities)" + company: "kartoffeln.work GmbH." + license: "MIT" + min_ansible_version: "2.18" + platforms: + - name: "ArchLinux" + versions: + - "all" + - name: "Ubuntu" + versions: + - "noble" + - name: "Debian" + versions: + - "bookworm" diff --git a/roles/gateway/tasks/main.yml b/roles/gateway/tasks/main.yml new file mode 100644 index 00000000..f0bdb4d4 --- /dev/null +++ b/roles/gateway/tasks/main.yml @@ -0,0 +1,73 @@ +--- +- name: "Ensure iptables and iproute2 are installed" + become: true + ansible.builtin.package: + name: + - "iptables" + - "iproute2" + state: "present" + +- name: "Configure IP forwarding" + become: true + ansible.builtin.blockinfile: + path: "/etc/sysctl.conf" + state: "{{ 'present' if gateway_enabled else 'absent' }}" + prepend_newline: true + append_newline: true + marker: "# ==== {mark} ANSIBLE GATEWAY CONFIG" + create: true + mode: "0644" + block: | + net.ipv4.ip_forward=1 + net.ipv6.conf.all.forwarding=1 + notify: "Reload sysctl" + +- name: "Deploy gateway apply rules script" + become: true + ansible.builtin.template: + src: "gateway-apply-rules.sh.j2" + dest: "/usr/local/bin/gateway-apply-rules" + mode: "0755" + owner: "root" + group: "root" + +- name: "Deploy udev rules for WireGuard interface events" + become: true + ansible.builtin.template: + src: "99-wireguard-gateway.rules.j2" + dest: "/etc/udev/rules.d/99-wireguard-gateway.rules" + mode: "0644" + owner: "root" + group: "root" + notify: "Reload udev" + +- name: "Deploy gateway VPN mode systemd service" + become: true + ansible.builtin.template: + src: "gateway-vpn-mode.service.j2" + dest: "/etc/systemd/system/gateway-vpn-mode.service" + mode: "0644" + notify: "Reload systemd" + +- name: "Deploy gateway direct mode systemd service" + become: true + ansible.builtin.template: + src: "gateway-direct-mode.service.j2" + dest: "/etc/systemd/system/gateway-direct-mode.service" + mode: "0644" + notify: "Reload systemd" + +- name: "Deploy gateway init systemd service" + become: true + ansible.builtin.template: + src: "gateway-init.service.j2" + dest: "/etc/systemd/system/gateway-init.service" + mode: "0644" + notify: "Reload systemd" + +- name: "Enable gateway init service" + become: true + ansible.builtin.systemd_service: + name: "gateway-init.service" + enabled: "{{ gateway_enabled }}" + daemon_reload: true diff --git a/roles/gateway/templates/99-wireguard-gateway.rules.j2 b/roles/gateway/templates/99-wireguard-gateway.rules.j2 new file mode 100644 index 00000000..a4164bc3 --- /dev/null +++ b/roles/gateway/templates/99-wireguard-gateway.rules.j2 @@ -0,0 +1,2 @@ +ACTION=="add", SUBSYSTEM=="net", KERNEL=="wg*", TAG+="systemd", ENV{SYSTEMD_WANTS}="gateway-vpn-mode.service" +ACTION=="remove", SUBSYSTEM=="net", KERNEL=="wg*", TAG+="systemd", ENV{SYSTEMD_WANTS}="gateway-direct-mode.service" diff --git a/roles/gateway/templates/gateway-apply-rules.sh.j2 b/roles/gateway/templates/gateway-apply-rules.sh.j2 new file mode 100644 index 00000000..d79f2279 --- /dev/null +++ b/roles/gateway/templates/gateway-apply-rules.sh.j2 @@ -0,0 +1,93 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_NAME="gateway-apply-rules" +LOG_FACILITY="local0.info" + +# Ansible template variables - configured at deployment time +GATEWAY_SUBNET="{{ gateway_local_ipv4_subnet }}" +GATEWAY_INTERFACE="{{ gateway_router_interface }}" +log_message() { + echo "[$SCRIPT_NAME] $1" | logger -t "$SCRIPT_NAME" -p "$LOG_FACILITY" + echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" +} + +detect_vpn_interfaces() { + ip link show | grep -E '^[0-9]+: wg.*:.*UP' | cut -d: -f2 | tr -d ' ' || true +} + +clear_rules() { + log_message "Clearing existing gateway rules" + iptables -F FORWARD 2>/dev/null || true + iptables -t nat -D POSTROUTING -s "$GATEWAY_SUBNET" -o wg+ -j MASQUERADE 2>/dev/null || true + iptables -t nat -D POSTROUTING -s "$GATEWAY_SUBNET" -o "$GATEWAY_INTERFACE" -j MASQUERADE 2>/dev/null || true + iptables -D OUTPUT -o wg+ -j ACCEPT 2>/dev/null || true + iptables -D OUTPUT -o "$GATEWAY_INTERFACE" -j ACCEPT 2>/dev/null || true +} + +apply_vpn_mode() { + local vpn_interfaces="$1" + log_message "Applying VPN mode rules for interfaces: $vpn_interfaces" + iptables -t nat -A POSTROUTING -s "$GATEWAY_SUBNET" -o wg+ -j MASQUERADE + iptables -A OUTPUT -o wg+ -j ACCEPT + iptables -A FORWARD -s "$GATEWAY_SUBNET" -o wg+ -j ACCEPT + iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT + log_message "VPN mode rules applied - traffic routed through VPN" +} + +apply_direct_rules() { + log_message "Applying direct mode rules" + iptables -t nat -A POSTROUTING -s "$GATEWAY_SUBNET" -o "$GATEWAY_INTERFACE" -j MASQUERADE + iptables -A OUTPUT -o "$GATEWAY_INTERFACE" -j ACCEPT + iptables -A FORWARD -s "$GATEWAY_SUBNET" -o "$GATEWAY_INTERFACE" -j ACCEPT + iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT + log_message "Direct mode rules applied - normal routing active" +} + +handle_vpn_mode() { + local vpn_interfaces + vpn_interfaces=$(detect_vpn_interfaces) + + if [ -n "$vpn_interfaces" ]; then + clear_rules + apply_vpn_mode "$vpn_interfaces" + log_message "Switched to VPN mode" + else + log_message "No VPN interfaces found, ignoring VPN mode request" + fi +} + +handle_direct_mode() { + local vpn_interfaces + vpn_interfaces=$(detect_vpn_interfaces) + + if [ -z "$vpn_interfaces" ]; then + clear_rules + apply_direct_rules + log_message "Switched to direct mode" + else + log_message "VPN interfaces still active: $vpn_interfaces" + fi +} + +main() { + local mode="$1" + + case "$mode" in + "vpn") + handle_vpn_mode + ;; + "direct") + handle_direct_mode + ;; + *) + log_message "Unknown mode: $mode" + exit 1 + ;; + esac +} + +trap 'log_message "Script failed with error on line $LINENO"' ERR + +main "$@" diff --git a/roles/gateway/templates/gateway-direct-mode.service.j2 b/roles/gateway/templates/gateway-direct-mode.service.j2 new file mode 100644 index 00000000..5d708f84 --- /dev/null +++ b/roles/gateway/templates/gateway-direct-mode.service.j2 @@ -0,0 +1,14 @@ +[Unit] +Description=Gateway Direct Mode Service +Documentation=man:systemd.service(5) +After=network.target + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/gateway-apply-rules direct +User=root +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target diff --git a/roles/gateway/templates/gateway-init.service.j2 b/roles/gateway/templates/gateway-init.service.j2 new file mode 100644 index 00000000..8b4635d8 --- /dev/null +++ b/roles/gateway/templates/gateway-init.service.j2 @@ -0,0 +1,15 @@ +[Unit] +Description=Gateway Init Service +Documentation=man:systemd.service(5) +After=network.target + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/gateway-apply-rules vpn +ExecStart=/usr/local/bin/gateway-apply-rules direct +User=root +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target
\ No newline at end of file diff --git a/roles/gateway/templates/gateway-vpn-mode.service.j2 b/roles/gateway/templates/gateway-vpn-mode.service.j2 new file mode 100644 index 00000000..7816e6d5 --- /dev/null +++ b/roles/gateway/templates/gateway-vpn-mode.service.j2 @@ -0,0 +1,14 @@ +[Unit] +Description=Gateway VPN Mode Service +Documentation=man:systemd.service(5) +After=network.target + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/gateway-apply-rules vpn +User=root +StandardOutput=journal +StandardError=journal + +[Install] +WantedBy=multi-user.target |
