mirror of
https://github.com/trailofbits/algo.git
synced 2025-09-03 10:33:13 +02:00
Fix VPN routing by adding output interface to NAT rules
The NAT rules were missing the output interface specification (-o eth0), which caused routing failures on multi-homed systems (servers with multiple network interfaces). Without specifying the output interface, packets might not be NAT'd correctly. Changes: - Added -o {{ ansible_default_ipv4['interface'] }} to all NAT rules - Updated both IPv4 and IPv6 templates - Updated tests to verify output interface is present - Added ansible_default_ipv4/ipv6 to test fixtures This fixes the issue where VPN clients could connect but not route traffic to the internet on servers with multiple network interfaces (like DigitalOcean droplets with private networking enabled). 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
9cc0b029ac
commit
65fe846499
4 changed files with 20 additions and 14 deletions
|
@ -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 %}
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
6
tests/fixtures/test_variables.yml
vendored
6
tests/fixtures/test_variables.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue