diff --git a/ansible-playbook.yml b/ansible-playbook.yml index fea00f7..0165ea7 100755 --- a/ansible-playbook.yml +++ b/ansible-playbook.yml @@ -49,6 +49,7 @@ dest: /etc/modules-load.d/wg-easy.conf content: | wireguard + nft_masq mode: '0644' - name: Enable IP forwarding on host @@ -146,17 +147,18 @@ # - 7359:7359/udp #optional # - 1900:1900/udp #optional - # WireGuard Easy VPN — deployed on npm_internal behind NPM - # After first deploy, you MUST add NAT hooks in the wg-easy UI: - # 1. Open http://10.10.0.36:51821 in a browser - # 2. Log in with the username/password from vars.yml - # 3. Go to the "Hooks" tab - # 4. PostUp — paste this EXACTLY: - # nft add table inet wg_table; nft add chain inet wg_table prerouting { type nat hook prerouting priority 100 \; }; nft add chain inet wg_table postrouting { type nat hook postrouting priority 100 \; }; nft add rule inet wg_table postrouting ip saddr {{ipv4Cidr}} oifname {{device}} masquerade; nft add rule inet wg_table postrouting ip6 saddr {{ipv6Cidr}} oifname {{device}} masquerade; nft add chain inet wg_table input { type filter hook input priority 0 \; policy accept \; }; nft add rule inet wg_table input udp dport {{port}} accept; nft add rule inet wg_table input tcp dport {{uiPort}} accept; nft add chain inet wg_table forward { type filter hook forward priority 0 \; policy accept \; }; nft add rule inet wg_table forward iifname "wg0" accept; nft add rule inet wg_table forward oifname "wg0" accept; - # 5. PostDown — paste this EXACTLY: - # nft delete table inet wg_table - # 6. Click Save and restart the container - # These hooks enable VPN clients to reach your app containers (10.10.0.0/24) + # WireGuard Easy VPN (v15) — UI at 10.10.0.36:51821 on npm_internal, fronted by NPM + # (vpn.boxswapper.io -> http://10.10.0.36:51821). Login: admin / wireguard_ui_password from vars.yml. + # v15 init is ONE-TIME: the INIT_* envs below are consumed only when /etc/wireguard/wg-easy.db is ABSENT. + # Force a re-init (wipes clients + server keys): podman rm -f wg-easy && rm -rf /opt/apps/wireguard, then re-run. + # NAT from VPN clients to LAN/apps (10.10.1.0/24 -> 10.10.0.0/24) uses NFTABLES hooks — + # wg-easy's iptables-legacy hooks cannot init the nat table inside a podman netns + # ("Table does not exist"), which makes wg-quick up fail and every client API call + # 500 with "Unable to access interface". scripts/patch-wg-hooks.py rewrites the hooks + # to nft rules and the tasks below restart wg-easy to apply them. See the official + # wg-easy "Podman + nftables" tutorial. + # EXTERNAL ACCESS: router MUST forward UDP 51820 -> 192.168.0.98. Test from LTE, + # not your LAN — NAT hairpin breaks public-IP handshakes from inside the network. - name: Deploy WireGuard Easy (wg-easy) containers.podman.podman_container: name: wg-easy @@ -190,10 +192,37 @@ sysctl: net.ipv4.ip_forward: "1" net.ipv4.conf.all.src_valid_mark: "1" + net.ipv6.conf.all.disable_ipv6: "0" + net.ipv6.conf.all.forwarding: "1" + net.ipv6.conf.default.forwarding: "1" network: - npm_internal ip: '{{ wireguard_ip }}' + - name: Copy wg-easy hook repair script + ansible.builtin.copy: + src: ./scripts/patch-wg-hooks.py + dest: /opt/apps/wireguard/patch-wg-hooks.py + owner: root + group: root + mode: '0755' + + - name: Replace iptables hooks with nftables (iptables-legacy broken in podman) + ansible.builtin.command: python3 /opt/apps/wireguard/patch-wg-hooks.py + register: wg_hooks_patch + changed_when: "'patched hooks' in wg_hooks_patch.stdout" + + - name: Restart wg-easy to apply nft hooks + ansible.builtin.command: podman restart wg-easy + when: "'patched hooks' in wg_hooks_patch.stdout" + + - name: Wait for WireGuard Easy web UI + ansible.builtin.wait_for: + host: '{{ wireguard_ip }}' + port: '{{ wireguard_ui_port }}' + delay: 5 + timeout: 120 + # Wazuh SIEM (single-node: indexer + manager + dashboard) on npm_internal # Dashboard is NOT exposed on host ports — NPM proxies wazuh.boxswapper.io to https://10.10.0.40:5601 # DNS record must be grey-cloud (Cloudflare 523 lesson from vpn.boxswapper.io), cert via NPM diff --git a/scripts/patch-wg-hooks.py b/scripts/patch-wg-hooks.py new file mode 100644 index 0000000..ef3c14b --- /dev/null +++ b/scripts/patch-wg-hooks.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""Replace wg-easy iptables hooks with nftables equivalents. + +wg-easy images ship iptables-legacy, which cannot initialize the `nat`/`filter` +tables inside a podman container netns on nftables-only kernels (fc42). Every +`wg-quick up wg0` then fails at the PostUp hook, so the wg0 interface is rolled +back and ALL client API calls 500 with "Unable to access interface: No such device". + +The official wg-easy podman+nftables tutorial replaces the hooks with nft rules. +This script rewrites hooks_table in /opt/apps/wireguard/wg-easy.db when the hooks +still reference iptables. Run it with the wg-easy container stopped, then restart +the container so wg0.conf is regenerated. Idempotent. +""" +import sqlite3 +import sys + +DB = "/opt/apps/wireguard/wg-easy.db" + +POSTUP = ( + "nft add table inet wg_table;" + " nft add chain inet wg_table prerouting { type nat hook prerouting priority 100 \\; };" + " nft add chain inet wg_table postrouting { type nat hook postrouting priority 100 \\; };" + " nft add rule inet wg_table postrouting ip saddr {{ipv4Cidr}} oifname {{device}} masquerade;" + " nft add rule inet wg_table postrouting ip6 saddr {{ipv6Cidr}} oifname {{device}} masquerade;" + " nft add chain inet wg_table input { type filter hook input priority 0 \\; policy accept \\; };" + " nft add rule inet wg_table input udp dport {{port}} accept;" + " nft add rule inet wg_table input tcp dport {{uiPort}} accept;" + " nft add chain inet wg_table forward { type filter hook forward priority 0 \\; policy accept \\; };" + ' nft add rule inet wg_table forward iifname "wg0" accept;' + ' nft add rule inet wg_table forward oifname "wg0" accept;' +) +POSTDOWN = "nft delete table inet wg_table" + + +def main() -> int: + con = sqlite3.connect(DB) + try: + rows = con.execute("SELECT id, post_up, post_down FROM hooks_table").fetchall() + changed = 0 + for name, post_up, post_down in rows: + post_up = post_up or "" + post_down = post_down or "" + if "iptables" in post_up or "iptables" in post_down: + con.execute( + "UPDATE hooks_table SET post_up=?, post_down=?, updated_at=CURRENT_TIMESTAMP WHERE id=?", + (POSTUP, POSTDOWN, name), + ) + print(f"patched hooks for {name!r}") + changed += 1 + else: + print(f"hooks ok for {name!r}") + con.commit() + finally: + con.close() + print(f"changed={changed}") + return 0 if changed >= 0 else 1 + + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/vars.yml b/vars.yml index 7c96186..99e37d0 100755 --- a/vars.yml +++ b/vars.yml @@ -46,9 +46,9 @@ POSTGRES_DB: thatoneit ## IMPORTANT: After first run, change wireguard_ui_password to a strong password ## Public IP or DDNS hostname clients connect to — CHANGE THIS before deploying wireguard_image: ghcr.io/wg-easy/wg-easy:15 -wireguard_host: CHANGE_ME +wireguard_host: vpn.boxswapper.io wireguard_ui_username: admin -wireguard_ui_password: CHANGE_ME +wireguard_ui_password: T3l0penF@5tback wireguard_port: 51820 wireguard_ui_port: 51821 wireguard_cidr: 10.10.1.0/24