--- - name: Configure WireGuard split tunnel routing hosts: production become: true gather_facts: true vars: wg_interface: wg0 wg_addr: 10.8.0.1/24 wg_net: 10.8.0.0/24 wan_interface: eth0 listening_port: 51820 extra_nets: - 192.168.178.0/24 - 172.20.0.0/16 firewall_backend: iptables # or nftables manage_ufw: false manage_firewalld: false firewalld_zone: public pre_tasks: - name: Ensure required collections are installed (documentation note) debug: msg: > Install collections if missing: ansible-galaxy collection install ansible.posix community.general when: false tasks: - name: Ensure WireGuard config directory exists ansible.builtin.file: path: "/etc/wireguard" state: directory mode: "0700" owner: root group: root - name: Persist IPv4 forwarding ansible.builtin.copy: dest: "/etc/sysctl.d/99-{{ wg_interface }}-forward.conf" owner: root group: root mode: "0644" content: | # Managed by Ansible - WireGuard {{ wg_interface }} net.ipv4.ip_forward=1 - name: Enable IPv4 forwarding runtime ansible.posix.sysctl: name: net.ipv4.ip_forward value: "1" state: present reload: true - name: Configure MASQUERADE (iptables) community.general.iptables: table: nat chain: POSTROUTING out_interface: "{{ wan_interface }}" source: "{{ wg_net }}" jump: MASQUERADE state: present when: firewall_backend == "iptables" - name: Allow forwarding wg -> wan (iptables) community.general.iptables: table: filter chain: FORWARD in_interface: "{{ wg_interface }}" out_interface: "{{ wan_interface }}" source: "{{ wg_net }}" jump: ACCEPT state: present when: firewall_backend == "iptables" - name: Allow forwarding wan -> wg (iptables) community.general.iptables: table: filter chain: FORWARD out_interface: "{{ wg_interface }}" destination: "{{ wg_net }}" ctstate: RELATED,ESTABLISHED jump: ACCEPT state: present when: firewall_backend == "iptables" - name: Allow forwarding to extra nets (iptables) community.general.iptables: table: filter chain: FORWARD in_interface: "{{ wg_interface }}" destination: "{{ item }}" jump: ACCEPT state: present loop: "{{ extra_nets }}" when: firewall_backend == "iptables" - name: Allow return from extra nets (iptables) community.general.iptables: table: filter chain: FORWARD source: "{{ item }}" out_interface: "{{ wg_interface }}" ctstate: RELATED,ESTABLISHED jump: ACCEPT state: present loop: "{{ extra_nets }}" when: firewall_backend == "iptables" - name: Deploy nftables WireGuard rules ansible.builtin.template: src: "{{ playbook_dir }}/../templates/wireguard-nftables.nft.j2" dest: "/etc/nftables.d/wireguard-{{ wg_interface }}.nft" owner: root group: root mode: "0644" when: firewall_backend == "nftables" notify: Reload nftables - name: Ensure nftables main config includes WireGuard rules ansible.builtin.lineinfile: path: /etc/nftables.conf regexp: '^include "/etc/nftables.d/wireguard-{{ wg_interface }}.nft";$' line: 'include "/etc/nftables.d/wireguard-{{ wg_interface }}.nft";' create: true when: firewall_backend == "nftables" notify: Reload nftables - name: Manage UFW forward policy ansible.builtin.lineinfile: path: /etc/default/ufw regexp: '^DEFAULT_FORWARD_POLICY=' line: 'DEFAULT_FORWARD_POLICY="ACCEPT"' when: manage_ufw - name: Allow WireGuard port in UFW community.general.ufw: rule: allow port: "{{ listening_port }}" proto: udp comment: "WireGuard VPN" when: manage_ufw - name: Allow routed traffic via UFW (wg -> wan) ansible.builtin.command: cmd: "ufw route allow in on {{ wg_interface }} out on {{ wan_interface }} to any" register: ufw_route_result changed_when: "'Skipping' not in ufw_route_result.stdout" when: manage_ufw - name: Allow extra nets via UFW ansible.builtin.command: cmd: "ufw route allow in on {{ wg_interface }} to {{ item }}" loop: "{{ extra_nets }}" register: ufw_extra_result changed_when: "'Skipping' not in ufw_extra_result.stdout" when: manage_ufw - name: Allow WireGuard port in firewalld ansible.posix.firewalld: zone: "{{ firewalld_zone }}" port: "{{ listening_port }}/udp" permanent: true state: enabled when: manage_firewalld - name: Enable firewalld masquerade ansible.posix.firewalld: zone: "{{ firewalld_zone }}" masquerade: true permanent: true state: enabled when: manage_firewalld - name: Allow forwarding from WireGuard via firewalld ansible.posix.firewalld: permanent: true state: enabled immediate: false rich_rule: 'rule family="ipv4" source address="{{ wg_net }}" accept' when: manage_firewalld - name: Allow extra nets via firewalld ansible.posix.firewalld: permanent: true state: enabled immediate: false rich_rule: 'rule family="ipv4" source address="{{ item }}" accept' loop: "{{ extra_nets }}" when: manage_firewalld - name: Ensure wg-quick service enabled and restarted ansible.builtin.systemd: name: "wg-quick@{{ wg_interface }}" enabled: true state: restarted - name: Show WireGuard status ansible.builtin.command: "wg show {{ wg_interface }}" register: wg_status changed_when: false failed_when: false - name: Render routing summary ansible.builtin.debug: msg: | WireGuard routing updated for {{ wg_interface }} {{ wg_status.stdout }} handlers: - name: Reload nftables ansible.builtin.command: nft -f /etc/nftables.conf