summaryrefslogtreecommitdiffstats
path: root/roles/gateway
diff options
context:
space:
mode:
authorAhmed Abdelhalim <[email protected]>2025-09-03 02:26:21 +0200
committerAhmed Abdelhalim <[email protected]>2025-09-03 03:26:09 +0200
commit7221496f0555bbdd1601ea70da2a9ca99cd34488 (patch)
tree412c8a78aaccb9000cad92e2a515ae87922ec353 /roles/gateway
parent0985f7534da7ffabec9e707f0a355a8307b1ea97 (diff)
Refactor: rename wireguard_gateway to gateway
As the role now functions as a gateway and not just a wireguard gateway it's better name for clarity
Diffstat (limited to 'roles/gateway')
-rw-r--r--roles/gateway/README.md98
-rw-r--r--roles/gateway/defaults/main.yml2
-rw-r--r--roles/gateway/handlers/main.yml6
-rw-r--r--roles/gateway/meta/argument_specs.yml10
-rw-r--r--roles/gateway/meta/main.yml20
-rw-r--r--roles/gateway/tasks/main.yml112
6 files changed, 248 insertions, 0 deletions
diff --git a/roles/gateway/README.md b/roles/gateway/README.md
new file mode 100644
index 00000000..54c45430
--- /dev/null
+++ b/roles/gateway/README.md
@@ -0,0 +1,98 @@
+# Ansible Role: gateway
+
+This role configures a Linux host as a gateway,
+routing all traffic from local network clients through.
+
+## Role Variables
+
+- `gateway_enabled`: Boolean flag to enable/disable gateway functionality (default: `false`)
+
+## What This Role Does
+
+When `gateway_enabled` is `true`, the role:
+
+- Sets `net.ipv4.ip_forward=1` and `net.ipv6.conf.all.forwarding=1` in `/etc/sysctl.conf`
+- Creates custom routing table (100) to route traffic through VPN
+- Routes IPv4 and IPv6 traffic from local subnet through VPN interface
+- Allows traffic forwarding and established connections
+
+When `gateway_enabled` is `false`, the role:
+
+- Removes all routing rules and iptables configurations
+- Disables IP forwarding in sysctl configuration
+- Cleans up custom routing tables
+
+## Example Configuration
+
+```yaml
+# In host_vars/rpi5.local.yml
+gateway_enabled: true
+wireguard_autostart_connection: "protonvpn-us1"
+
+# WireGuard connection configuration
+wireguard_connections:
+ protonvpn-us1: |
+ [Interface]
+ PrivateKey = your_private_key_here
+ Address = 10.2.0.2/32
+ DNS = 10.2.0.1
+
+ [Peer]
+ PublicKey = server_public_key
+ AllowedIPs = 0.0.0.0/0, ::/0
+ Endpoint = vpn.example.com:51820
+```
+
+## Client Configuration
+
+For client machines to route through the VPN gateway:
+
+1. **IPv4**: Set gateway to VPN gateway host's IP
+2. **IPv6**: Set IPv6 gateway to VPN gateway host's IPv6 address
+3. **Manual network config**: Disable auto-configuration to use custom gateway
+
+Example client network configuration:
+
+```yaml
+network_ipv4_address: "192.168.1.100"
+network_ipv4_gateway: "192.168.1.254" # VPN gateway host
+network_ipv6_address: "2001:db8::100/64"
+network_ipv6_gateway: "2001:db8::254" # VPN gateway host
+```
+
+## Technical Details
+
+### Routing Rules Created
+
+```bash
+# Policy routing rules (priority 200)
+ip rule add iif eth0 table 100 priority 200
+ip -6 rule add iif eth0 table 100 priority 200
+
+# Custom routing table (table 100)
+ip route add default dev wg-connection table 100
+ip -6 route add default dev wg-connection table 100
+```
+
+### iptables Rules Created
+
+```bash
+# IPv4 NAT masquerading
+iptables -t nat -A POSTROUTING -s 192.168.1.0/24 -o wg-connection -j MASQUERADE
+
+# IPv4 FORWARD rules
+iptables -A FORWARD -s 192.168.1.0/24 -j ACCEPT
+iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+
+# IPv6 equivalent rules
+ip6tables -t nat -A POSTROUTING -s 2001:db8::/64 -o wg-connection -j MASQUERADE
+ip6tables -A FORWARD -s 2001:db8::/64 -j ACCEPT
+ip6tables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+```
+
+## Troubleshooting
+
+- **Check VPN connection**: `sudo wg show` should show active connection
+- **Verify routing**: `ip rule show` and `ip route show table 100`
+- **Test connectivity**: `curl ip.me` should show VPN IP from client machines
+- **Check iptables**: `sudo iptables -t nat -L -n -v` should show masquerading rules
diff --git a/roles/gateway/defaults/main.yml b/roles/gateway/defaults/main.yml
new file mode 100644
index 00000000..8cddb272
--- /dev/null
+++ b/roles/gateway/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+gateway_enabled: false
diff --git a/roles/gateway/handlers/main.yml b/roles/gateway/handlers/main.yml
new file mode 100644
index 00000000..4b7cb2f2
--- /dev/null
+++ b/roles/gateway/handlers/main.yml
@@ -0,0 +1,6 @@
+---
+- name: "Reload sysctl"
+ become: true
+ ansible.builtin.command:
+ cmd: "sysctl -p"
+ 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..d6520641
--- /dev/null
+++ b/roles/gateway/meta/argument_specs.yml
@@ -0,0 +1,10 @@
+---
+argument_specs:
+ main:
+ description: "Configure the PI as a network gateway forcing all traffic through it (or through active VPN on it)"
+ author: "a14m"
+ options:
+ gateway_enabled:
+ description: "Toggle flag for enabling/disabling network gateway configurations"
+ type: "bool"
+ default: false
diff --git a/roles/gateway/meta/main.yml b/roles/gateway/meta/main.yml
new file mode 100644
index 00000000..36df4aad
--- /dev/null
+++ b/roles/gateway/meta/main.yml
@@ -0,0 +1,20 @@
+---
+dependencies:
+ - "wireguard"
+
+galaxy_info:
+ author: "a14m"
+ description: "Install and configure WireGuard VPN as a network gateway"
+ 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..f9ccd4cb
--- /dev/null
+++ b/roles/gateway/tasks/main.yml
@@ -0,0 +1,112 @@
+---
+- name: "Ensure iptables are installed"
+ become: true
+ ansible.builtin.package:
+ name: "iptables"
+ 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"
+
+# IPv4 iptables rules
+- name: "Add IPv4 NAT masquerading for traffic through internet"
+ become: true
+ ansible.builtin.iptables:
+ table: "nat"
+ chain: "POSTROUTING"
+ source: "{{ (ansible_default_ipv4.address.split('.')[0:3] | join('.')) }}.0/24"
+ out_interface: "{{ ansible_default_ipv4.interface }}"
+ jump: "MASQUERADE"
+ comment: "NAT local subnet traffic through internet"
+ state: "present"
+
+- name: "Add IPv4 NAT masquerading for traffic through VPN"
+ become: true
+ ansible.builtin.iptables:
+ table: "nat"
+ chain: "POSTROUTING"
+ source: "{{ (ansible_default_ipv4.address.split('.')[0:3] | join('.')) }}.0/24"
+ out_interface: "{{ item }}"
+ jump: "MASQUERADE"
+ comment: "NAT local subnet traffic through VPN"
+ state: "{{ 'present' if gateway_enabled else 'absent' }}"
+ with_items: "{{ wireguard_connections }}"
+
+- name: "Add IPv4 FORWARD rule to accept traffic from local subnet"
+ become: true
+ ansible.builtin.iptables:
+ chain: "FORWARD"
+ source: "{{ (ansible_default_ipv4.address.split('.')[0:3] | join('.')) }}.0/24"
+ jump: "ACCEPT"
+ comment: "Allow forwarding from local subnet"
+ state: "{{ 'present' if gateway_enabled else 'absent' }}"
+
+- name: "Add IPv4 FORWARD rule to accept established connections"
+ become: true
+ ansible.builtin.iptables:
+ chain: "FORWARD"
+ match: "conntrack"
+ ctstate: "RELATED,ESTABLISHED"
+ jump: "ACCEPT"
+ comment: "Allow established connections"
+ state: "{{ 'present' if gateway_enabled else 'absent' }}"
+
+# IPv6 iptables rules
+- name: "Add IPv6 NAT masquerading for traffic through internet"
+ become: true
+ ansible.builtin.iptables:
+ table: "nat"
+ chain: "POSTROUTING"
+ source: "{{ (ansible_default_ipv6.address.split(':')[:4] | join(':')) + '::/64' }}"
+ out_interface: "{{ ansible_default_ipv6.interface }}"
+ jump: "MASQUERADE"
+ comment: "NAT IPv6 local subnet traffic through internet"
+ ip_version: "ipv6"
+ state: "present"
+ when: ansible_default_ipv6.address is defined
+
+- name: "Add IPv6 NAT masquerading for traffic through VPN"
+ become: true
+ ansible.builtin.iptables:
+ table: "nat"
+ chain: "POSTROUTING"
+ source: "{{ (ansible_default_ipv6.address.split(':')[:4] | join(':')) + '::/64' }}"
+ out_interface: "{{ item }}"
+ jump: "MASQUERADE"
+ comment: "NAT IPv6 local subnet traffic through VPN"
+ ip_version: "ipv6"
+ state: "{{ 'present' if gateway_enabled else 'absent' }}"
+ with_items: "{{ wireguard_connections }}"
+
+- name: "Add IPv6 FORWARD rule to accept traffic from local subnet"
+ become: true
+ ansible.builtin.iptables:
+ chain: "FORWARD"
+ source: "{{ (ansible_default_ipv6.address.split(':')[:4] | join(':')) + '::/64' }}"
+ jump: "ACCEPT"
+ comment: "Allow IPv6 forwarding from local subnet"
+ ip_version: "ipv6"
+ state: "{{ 'present' if gateway_enabled else 'absent' }}"
+
+- name: "Add IPv6 FORWARD rule to accept established connections"
+ become: true
+ ansible.builtin.iptables:
+ chain: "FORWARD"
+ match: "conntrack"
+ ctstate: "RELATED,ESTABLISHED"
+ jump: "ACCEPT"
+ comment: "Allow IPv6 established connections"
+ ip_version: "ipv6"
+ state: "{{ 'present' if gateway_enabled else 'absent' }}"