diff --git a/roles/common/templates/rules.v4.j2 b/roles/common/templates/rules.v4.j2 index 0a978094..4e1ba86f 100644 --- a/roles/common/templates/rules.v4.j2 +++ b/roles/common/templates/rules.v4.j2 @@ -38,11 +38,11 @@ COMMIT # Allow traffic from the VPN network to the outside world, and replies {% if ipsec_enabled %} # For IPsec traffic - NAT the decrypted packets from the VPN subnet --A POSTROUTING -s {{ strongswan_network }} {{ '-j SNAT --to ' + snat_aipv4 if snat_aipv4 else '-j MASQUERADE' }} +-A POSTROUTING -s {{ strongswan_network }} -o {{ ansible_default_ipv4['interface'] }} {{ '-j SNAT --to ' + snat_aipv4 if snat_aipv4 else '-j MASQUERADE' }} {% endif %} {% if wireguard_enabled %} # For WireGuard traffic - NAT packets from the VPN subnet --A POSTROUTING -s {{ wireguard_network_ipv4 }} {{ '-j SNAT --to ' + snat_aipv4 if snat_aipv4 else '-j MASQUERADE' }} +-A POSTROUTING -s {{ wireguard_network_ipv4 }} -o {{ ansible_default_ipv4['interface'] }} {{ '-j SNAT --to ' + snat_aipv4 if snat_aipv4 else '-j MASQUERADE' }} {% endif %} diff --git a/roles/common/templates/rules.v6.j2 b/roles/common/templates/rules.v6.j2 index 397bfb6c..8d90beb1 100644 --- a/roles/common/templates/rules.v6.j2 +++ b/roles/common/templates/rules.v6.j2 @@ -37,11 +37,11 @@ COMMIT # Allow traffic from the VPN network to the outside world, and replies {% if ipsec_enabled %} # For IPsec traffic - NAT the decrypted packets from the VPN subnet --A POSTROUTING -s {{ strongswan_network_ipv6 }} {{ '-j SNAT --to ' + ipv6_egress_ip | ansible.utils.ipaddr('address') if alternative_ingress_ip else '-j MASQUERADE' }} +-A POSTROUTING -s {{ strongswan_network_ipv6 }} -o {{ ansible_default_ipv6['interface'] }} {{ '-j SNAT --to ' + ipv6_egress_ip | ansible.utils.ipaddr('address') if alternative_ingress_ip else '-j MASQUERADE' }} {% endif %} {% if wireguard_enabled %} # For WireGuard traffic - NAT packets from the VPN subnet --A POSTROUTING -s {{ wireguard_network_ipv6 }} {{ '-j SNAT --to ' + ipv6_egress_ip | ansible.utils.ipaddr('address') if alternative_ingress_ip else '-j MASQUERADE' }} +-A POSTROUTING -s {{ wireguard_network_ipv6 }} -o {{ ansible_default_ipv6['interface'] }} {{ '-j SNAT --to ' + ipv6_egress_ip | ansible.utils.ipaddr('address') if alternative_ingress_ip else '-j MASQUERADE' }} {% endif %} COMMIT diff --git a/tests/fixtures/test_variables.yml b/tests/fixtures/test_variables.yml index 0af0a1fc..42e22f8b 100644 --- a/tests/fixtures/test_variables.yml +++ b/tests/fixtures/test_variables.yml @@ -72,6 +72,12 @@ CA_password: test-ca-pass # System ansible_ssh_port: 4160 ansible_python_interpreter: /usr/bin/python3 +ansible_default_ipv4: + interface: eth0 + address: 10.0.0.1 +ansible_default_ipv6: + interface: eth0 + address: 'fd9d:bc11:4020::1' BetweenClients_DROP: 'Y' ssh_tunnels_config_path: /etc/ssh/ssh_tunnels config_prefix: /etc/algo diff --git a/tests/unit/test_iptables_rules.py b/tests/unit/test_iptables_rules.py index e3eca742..7242a377 100644 --- a/tests/unit/test_iptables_rules.py +++ b/tests/unit/test_iptables_rules.py @@ -41,8 +41,8 @@ def test_wireguard_nat_rules_ipv4(): reduce_mtu=0 ) - # Verify NAT rule exists without policy matching - assert '-A POSTROUTING -s 10.49.0.0/16 -j MASQUERADE' in result + # Verify NAT rule exists with output interface and without policy matching + assert '-A POSTROUTING -s 10.49.0.0/16 -o eth0 -j MASQUERADE' in result # Verify no policy matching in WireGuard NAT rules assert '-A POSTROUTING -s 10.49.0.0/16 -m policy' not in result @@ -67,8 +67,8 @@ def test_ipsec_nat_rules_ipv4(): reduce_mtu=0 ) - # Verify NAT rule exists without policy matching - assert '-A POSTROUTING -s 10.48.0.0/16 -j MASQUERADE' in result + # Verify NAT rule exists with output interface and without policy matching + assert '-A POSTROUTING -s 10.48.0.0/16 -o eth0 -j MASQUERADE' in result # Verify no policy matching in IPsec NAT rules (this was the bug) assert '-A POSTROUTING -s 10.48.0.0/16 -m policy --pol none' not in result @@ -97,9 +97,9 @@ def test_both_vpns_nat_rules_ipv4(): reduce_mtu=0 ) - # Both should have NAT rules - assert '-A POSTROUTING -s 10.48.0.0/16 -j MASQUERADE' in result - assert '-A POSTROUTING -s 10.49.0.0/16 -j MASQUERADE' in result + # Both should have NAT rules with output interface + assert '-A POSTROUTING -s 10.48.0.0/16 -o eth0 -j MASQUERADE' in result + assert '-A POSTROUTING -s 10.49.0.0/16 -o eth0 -j MASQUERADE' in result # Neither should have policy matching assert '-m policy --pol none' not in result @@ -129,9 +129,9 @@ def test_alternative_ingress_snat(): reduce_mtu=0 ) - # Should use SNAT with specific IP instead of MASQUERADE - assert '-A POSTROUTING -s 10.48.0.0/16 -j SNAT --to 192.168.1.100' in result - assert '-A POSTROUTING -s 10.49.0.0/16 -j SNAT --to 192.168.1.100' in result + # Should use SNAT with specific IP and output interface instead of MASQUERADE + assert '-A POSTROUTING -s 10.48.0.0/16 -o eth0 -j SNAT --to 192.168.1.100' in result + assert '-A POSTROUTING -s 10.49.0.0/16 -o eth0 -j SNAT --to 192.168.1.100' in result assert 'MASQUERADE' not in result