summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAhmed Abdelhalim <[email protected]>2025-09-12 00:17:35 +0200
committerAhmed Abdelhalim <[email protected]>2025-09-12 00:17:35 +0200
commit4a812a999a79dae501db62e0eeb9e33e34282b83 (patch)
tree7843b04b7f6be1eac6d5d4e421b38a5e12bb4fec
parentd8bd623f3a86e7f472cd034f627a86dc8ccf4955 (diff)
Implement the gateway role (replacing old wireguard-gateway)
-rw-r--r--roles/gateway/README.md94
-rw-r--r--roles/gateway/defaults/main.yml3
-rw-r--r--roles/gateway/handlers/main.yml17
-rw-r--r--roles/gateway/meta/argument_specs.yml20
-rw-r--r--roles/gateway/meta/main.yml19
-rw-r--r--roles/gateway/tasks/main.yml73
-rw-r--r--roles/gateway/templates/99-wireguard-gateway.rules.j22
-rw-r--r--roles/gateway/templates/gateway-apply-rules.sh.j293
-rw-r--r--roles/gateway/templates/gateway-direct-mode.service.j214
-rw-r--r--roles/gateway/templates/gateway-init.service.j215
-rw-r--r--roles/gateway/templates/gateway-vpn-mode.service.j214
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