security: replace WireGuard catch-all ACCEPT with DROP
The PostUp rule appended `iptables -A FORWARD -i wg0 -j ACCEPT` which allowed any WireGuard-connected client full internet access regardless of per-peer rules, even when no peers were configured in wg0.conf. Fix: change PostUp/PostDown to use DROP as the catch-all. Per-peer and per-cell rules use -I (insert at top) so they take precedence; unknown or unconfigured WG traffic hits the DROP at the bottom. Also add reconcile_stale_peer_rules() called on startup to remove FORWARD rules for peer IPs that no longer exist in the registry, preventing deleted peers from retaining firewall access across container restarts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -46,8 +46,13 @@ def _make_wg(tmp: str) -> WireGuardManager:
|
||||
class TestInternetForwardingRules(unittest.TestCase):
|
||||
"""
|
||||
Verify that generate_config() emits the exact iptables rules required for
|
||||
'internet through VPN': MASQUERADE on eth0 (outbound NAT) and FORWARD ACCEPT
|
||||
on the wg0 interface. Missing either rule means VPN clients get no internet.
|
||||
'internet through VPN': MASQUERADE on eth0 (outbound NAT) and a catch-all
|
||||
FORWARD DROP on the wg0 interface.
|
||||
|
||||
The catch-all is DROP (not ACCEPT) so that only per-peer rules inserted at
|
||||
chain position 1 via apply_peer_rules() can forward traffic. An ACCEPT
|
||||
catch-all would allow any WireGuard-connected client full internet access
|
||||
even if they have no entry in peers.json.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
@@ -61,10 +66,11 @@ class TestInternetForwardingRules(unittest.TestCase):
|
||||
cfg = self.wg.generate_config()
|
||||
self.assertIn('POSTROUTING -o eth0 -j MASQUERADE', cfg)
|
||||
|
||||
def test_postup_has_forward_accept_on_wg_interface(self):
|
||||
"""FORWARD ACCEPT allows packets from the WireGuard interface through the kernel."""
|
||||
def test_postup_has_forward_drop_on_wg_interface(self):
|
||||
"""Catch-all DROP blocks unconfigured WG clients; per-peer rules inserted above it allow known peers."""
|
||||
cfg = self.wg.generate_config()
|
||||
self.assertIn('FORWARD -i %i -j ACCEPT', cfg)
|
||||
self.assertIn('FORWARD -i %i -j DROP', cfg)
|
||||
self.assertNotIn('FORWARD -i %i -j ACCEPT', cfg)
|
||||
|
||||
def test_postdown_removes_masquerade_rule(self):
|
||||
"""PostDown must mirror PostUp so rules are cleaned up when the tunnel goes down."""
|
||||
@@ -73,7 +79,7 @@ class TestInternetForwardingRules(unittest.TestCase):
|
||||
|
||||
def test_postdown_removes_forward_rule(self):
|
||||
cfg = self.wg.generate_config()
|
||||
self.assertIn('FORWARD -i %i -j ACCEPT', cfg.split('PostDown')[1])
|
||||
self.assertIn('FORWARD -i %i -j DROP', cfg.split('PostDown')[1])
|
||||
|
||||
def test_postup_and_postdown_are_present(self):
|
||||
"""Both PostUp and PostDown must exist — PostUp without PostDown leaks rules."""
|
||||
|
||||
Reference in New Issue
Block a user