# This should really be set per host, but I'm abusing the fact that there's only # one vpn_server host wireguard_addresses: - "{{ wireguard_ipv6_subnet | ansible.utils.ipaddr('net') | ansible.utils.ipaddr('1') }}" - "{{ wireguard_ipv4_subnet | ansible.utils.ipaddr('net') | ansible.utils.ipaddr('1') }}" wireguard_endpoint: "{{ inventory_hostname }}" wireguard_preup: - echo 1 > /proc/sys/net/ipv4/ip_forward - echo 1 > /proc/sys/net/ipv6/conf/all/forwarding # Disable autoconf while running. We assign some IP addresses that get removed # by autoconf otherwise. - echo 0 > /proc/sys/net/ipv6/conf/eth0/autoconf wireguard_postup: | {% filter from_yaml %} - ip6tables -A FORWARD -i wg0 -j ACCEPT - ip6tables -A FORWARD -o wg0 -j ACCEPT - iptables -A FORWARD -i wg0 -j ACCEPT - iptables -A FORWARD -o wg0 -j ACCEPT {% for value in (nat_map | dict2items | map(attribute='value')) %} # IPv6 will have a 1:1 port mapping - ip -6 addr add {{ value.vps_ipv6 }} dev eth0 # Incoming packets to this node's public IP are DNAT'd and forwarded to the # matching internal VPN IP {% for range in value.ipv6_port_ranges | default([]) %} - ip6tables -t nat -A PREROUTING -p tcp -d {{ value.vps_ipv6 | ansible.utils.ipaddr('address') }} --dport {{ range }} -j DNAT --to-destination {{ value.vpn_ipv6 | ansible.utils.ipaddr('address') }} - ip6tables -t nat -A PREROUTING -p udp -d {{ value.vps_ipv6 | ansible.utils.ipaddr('address') }} --dport {{ range }} -j DNAT --to-destination {{ value.vpn_ipv6 | ansible.utils.ipaddr('address') }} {% endfor %} # Incoming packets from an internal VPN IP are SNAT'd to use this node's public # IP. I think `-j MASQUERADE` might work here rather than doing the SNAT # manually(?), but I don't mind being explicit here. - ip6tables -t nat -A POSTROUTING -p tcp -s {{ value.vpn_ipv6 | ansible.utils.ipaddr('address') }} -j SNAT --to-source {{ value.vps_ipv6 | ansible.utils.ipaddr('address') }} - ip6tables -t nat -A POSTROUTING -p udp -s {{ value.vpn_ipv6 | ansible.utils.ipaddr('address') }} -j SNAT --to-source {{ value.vps_ipv6 | ansible.utils.ipaddr('address') }} # IPv4 will have manual port mapping {% for mapping in value.ipv4_port_mapping | default([]) %} - iptables -t nat -A PREROUTING -p tcp -d {{ value.vps_ipv4 | ansible.utils.ipaddr('address') }} --dport {{ mapping.external_port }} -j DNAT --to-destination {{ value.vpn_ipv4 | ansible.utils.ipaddr('address') }}:{{ mapping.internal_port }} - iptables -t nat -A PREROUTING -p udp -d {{ value.vps_ipv4 | ansible.utils.ipaddr('address') }} --dport {{ mapping.external_port }} -j DNAT --to-destination {{ value.vpn_ipv4 | ansible.utils.ipaddr('address') }}:{{ mapping.internal_port }} {% endfor %} # We're going to masquerade even if it's not from an internal port - iptables -t nat -A POSTROUTING -s {{ value.vpn_ipv4 | ansible.utils.ipaddr('address') }} -j MASQUERADE {% endfor %} {% endfilter %} # Exact reverse of above to delete all the rules wireguard_predown: | {% filter from_yaml %} {% for value in (nat_map | dict2items | map(attribute='value') | reverse) %} - iptables -t nat -D POSTROUTING -s {{ value.vpn_ipv4 | ansible.utils.ipaddr('address') }} -j MASQUERADE {% for mapping in value.ipv4_port_mapping | default([]) | reverse %} - iptables -t nat -D PREROUTING -p udp -d {{ value.vps_ipv4 | ansible.utils.ipaddr('address') }} --dport {{ mapping.external_port }} -j DNAT --to-destination {{ value.vpn_ipv4 | ansible.utils.ipaddr('address') }}:{{ mapping.internal_port }} - iptables -t nat -D PREROUTING -p tcp -d {{ value.vps_ipv4 | ansible.utils.ipaddr('address') }} --dport {{ mapping.external_port }} -j DNAT --to-destination {{ value.vpn_ipv4 | ansible.utils.ipaddr('address') }}:{{ mapping.internal_port }} {% endfor %} - ip6tables -t nat -D POSTROUTING -p udp -s {{ value.vpn_ipv6 | ansible.utils.ipaddr('address') }} -j SNAT --to-source {{ value.vps_ipv6 | ansible.utils.ipaddr('address') }} - ip6tables -t nat -D POSTROUTING -p tcp -s {{ value.vpn_ipv6 | ansible.utils.ipaddr('address') }} -j SNAT --to-source {{ value.vps_ipv6 | ansible.utils.ipaddr('address') }} {% for range in value.ipv6_port_ranges | default([]) | reverse %} - ip6tables -t nat -D PREROUTING -p udp -d {{ value.vps_ipv6 | ansible.utils.ipaddr('address') }} --dport {{ range }} -j DNAT --to-destination {{ value.vpn_ipv6 | ansible.utils.ipaddr('address') }} - ip6tables -t nat -D PREROUTING -p tcp -d {{ value.vps_ipv6 | ansible.utils.ipaddr('address') }} --dport {{ range }} -j DNAT --to-destination {{ value.vpn_ipv6 | ansible.utils.ipaddr('address') }} {% endfor %} - ip -6 addr del {{ value.vps_ipv6 }} dev eth0 {% endfor %} - iptables -D FORWARD -o wg0 -j ACCEPT - iptables -D FORWARD -i wg0 -j ACCEPT - ip6tables -D FORWARD -o wg0 -j ACCEPT - ip6tables -D FORWARD -i wg0 -j ACCEPT {% endfilter %} wireguard_postdown: - echo 1 > /proc/sys/net/ipv6/conf/eth0/autoconf - echo 0 > /proc/sys/net/ipv6/conf/all/forwarding - echo 0 > /proc/sys/net/ipv4/ip_forward # https://www.procustodibus.com/blog/2021/03/wireguard-allowedips-calculator/ # Above recommends to just add specific routing rules rather than compute # an equivalent list of subnets # # Yes, this is supposed to be defined on vpn_server rather than vpn_client, like # I initially thought. The reason for this is likely because the role was meant # for a fully meshed network rather than a single server with multiple clients, # and each host defines a list of IPs that should be routed _to this host_, not # a list of IPs that should be routed to the "server" (because everyone is a # peer in a fully meshed network) wireguard_allowed_ips: "0.0.0.0/0, ::0/0" # Disable IPv4 # wireguard_allowed_ips: "::0/0"