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:
@@ -268,6 +268,7 @@ def _apply_startup_enforcement():
|
||||
try:
|
||||
peers = peer_registry.list_peers()
|
||||
cell_links = cell_link_manager.list_connections()
|
||||
firewall_manager.reconcile_stale_peer_rules(peers)
|
||||
firewall_manager.apply_all_peer_rules(peers)
|
||||
firewall_manager.apply_all_cell_rules(cell_links)
|
||||
firewall_manager.ensure_cell_api_dnat()
|
||||
|
||||
@@ -221,6 +221,42 @@ def apply_all_peer_rules(peers: List[Dict[str, Any]]) -> None:
|
||||
})
|
||||
|
||||
|
||||
def reconcile_stale_peer_rules(peers: List[Dict[str, Any]]) -> int:
|
||||
"""Remove iptables rules for peer IPs that are no longer in the registry.
|
||||
|
||||
Returns the number of stale IPs cleaned up.
|
||||
"""
|
||||
known_ips = set()
|
||||
for peer in peers:
|
||||
raw = peer.get('ip', '')
|
||||
ip = raw.split('/')[0] if raw else ''
|
||||
if ip:
|
||||
known_ips.add(ip)
|
||||
|
||||
# Parse pic-peer-* comments from iptables-save to find IPs with live rules
|
||||
save = _wg_exec(['iptables-save'])
|
||||
if save.returncode != 0:
|
||||
return 0
|
||||
|
||||
# Comment format: pic-peer-A-B-C-D/32 (dots replaced with dashes)
|
||||
comment_re = re.compile(r'pic-peer-([\d]+-[\d]+-[\d]+-[\d]+)/32')
|
||||
stale_ips: set = set()
|
||||
for line in save.stdout.splitlines():
|
||||
m = comment_re.search(line)
|
||||
if m:
|
||||
ip = m.group(1).replace('-', '.')
|
||||
if ip not in known_ips:
|
||||
stale_ips.add(ip)
|
||||
|
||||
for ip in stale_ips:
|
||||
logger.warning(f"Removing stale iptables rules for deleted peer {ip}")
|
||||
clear_peer_rules(ip)
|
||||
|
||||
if stale_ips:
|
||||
logger.info(f"Reconciled {len(stale_ips)} stale peer(s): {sorted(stale_ips)}")
|
||||
return len(stale_ips)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Cell-to-cell firewall rules
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -134,11 +134,11 @@ class WireGuardManager(BaseServiceManager):
|
||||
f'PrivateKey = {keys["private_key"]}\n'
|
||||
f'Address = {address}\n'
|
||||
f'ListenPort = {cfg_port}\n'
|
||||
f'PostUp = iptables -A FORWARD -i %i -j ACCEPT; '
|
||||
f'PostUp = iptables -A FORWARD -i %i -j DROP; '
|
||||
f'iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE; '
|
||||
f'{hairpin}'
|
||||
f'sysctl -q net.ipv4.conf.all.rp_filter=0 || true\n'
|
||||
f'PostDown = iptables -D FORWARD -i %i -j ACCEPT; '
|
||||
f'PostDown = iptables -D FORWARD -i %i -j DROP; '
|
||||
f'iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE; '
|
||||
f'{hairpin_down}'
|
||||
f'sysctl -q net.ipv4.conf.all.rp_filter=1 || true\n'
|
||||
|
||||
Reference in New Issue
Block a user