Files
the-other-dude/docker-data/wireguard/custom-cont-init.d/10-forwarding.sh
Jason Staack 2ad0367c91 fix(vpn): backport VPN fixes from production debugging
- Fix _commit_and_sync infinite recursion
- Use admin session for subnet_index allocation (bypass RLS)
- Auto-set VPN endpoint from CORS_ORIGINS hostname
- Remove server address field from VPN setup UI
- Add DELETE endpoint and button for VPN config removal
- Add wg-reload watcher for reliable config hot-reload via wg syncconf
- Add wg_status.json writer for live peer handshake status in UI
- Per-tenant SNAT for poller-to-device routing through VPN
- Restrict VPN→eth0 forwarding to Docker networks only (block exit node abuse)
- Use 10.10.0.0/16 allowed-address in RouterOS commands
- Fix structlog event= conflict (use audit=True)
- Export backup_scheduler proxy for firmware/upgrade imports
2026-03-14 20:59:14 -05:00

91 lines
3.9 KiB
Bash
Executable File

#!/bin/sh
# Enable forwarding between Docker network and WireGuard tunnel
# Idempotent: check before adding to prevent duplicates on restart
# Allow Docker→VPN (poller/API reaching devices)
iptables -C FORWARD -i eth0 -o wg0 -j ACCEPT 2>/dev/null || iptables -A FORWARD -i eth0 -o wg0 -j ACCEPT
# Allow VPN→Docker ONLY (devices reaching poller/API, NOT the public internet)
iptables -C FORWARD -i wg0 -o eth0 -d 172.16.0.0/12 -j ACCEPT 2>/dev/null || iptables -A FORWARD -i wg0 -o eth0 -d 172.16.0.0/12 -j ACCEPT
# Block VPN→anywhere else (prevents using server as exit node)
iptables -C FORWARD -i wg0 -o eth0 -j DROP 2>/dev/null || iptables -A FORWARD -i wg0 -o eth0 -j DROP
# Block cross-subnet traffic on wg0 (tenant isolation)
# Peers in 10.10.1.0/24 cannot reach peers in 10.10.2.0/24
iptables -C FORWARD -i wg0 -o wg0 -j DROP 2>/dev/null || iptables -A FORWARD -i wg0 -o wg0 -j DROP
# Block IPv6 forwarding on wg0 (prevent link-local bypass)
ip6tables -C FORWARD -i wg0 -j DROP 2>/dev/null || ip6tables -A FORWARD -i wg0 -j DROP
# NAT for return traffic — per-tenant SNAT rules are applied by wg-reload watcher
# (nat_rules.sh is generated by sync_wireguard_config)
echo "WireGuard forwarding and tenant isolation rules applied"
# Start config reload watcher in background
# Polls for .reload flag every 2s, applies changes via wg syncconf
(
CONF_DIR="/config/wg_confs"
RELOAD_FLAG="$CONF_DIR/.reload"
echo "wg-reload: watcher started (pid $$)"
# Wait for wg0 interface to be fully up before processing reloads
while ! wg show wg0 >/dev/null 2>&1; do
sleep 2
done
# Apply NAT rules on startup if they exist
if [ -f "$CONF_DIR/nat_rules.sh" ]; then
sh "$CONF_DIR/nat_rules.sh" 2>&1
echo "wg-reload: startup NAT rules applied"
fi
# Clear any reload flag that was set during startup (interface already has the config)
rm -f "$RELOAD_FLAG"
echo "wg-reload: wg0 is up, watching for changes"
while true; do
if [ -f "$RELOAD_FLAG" ]; then
rm -f "$RELOAD_FLAG"
sleep 0.5
if [ -f "$CONF_DIR/wg0.conf" ]; then
# Strip Address and comments; keep ListenPort + PrivateKey + Peers
# wg syncconf rejects Address but needs ListenPort to preserve it
grep -v "^Address" "$CONF_DIR/wg0.conf" | grep -v "^#" | wg syncconf wg0 /dev/stdin 2>&1
# Apply per-tenant SNAT rules for poller connectivity
if [ -f "$CONF_DIR/nat_rules.sh" ]; then
sh "$CONF_DIR/nat_rules.sh" 2>&1
echo "wg-reload: NAT rules applied"
fi
if [ $? -eq 0 ]; then
echo "wg-reload: config applied"
else
echo "wg-reload: syncconf failed"
fi
fi
fi
sleep 2
done
) &
# Start status writer in background
# Writes wg_status.json every 15 seconds from `wg show wg0 dump`
(
STATUS_FILE="/config/wg_status.json"
# Wait for wg0
while ! wg show wg0 >/dev/null 2>&1; do
sleep 2
done
echo "wg-status: writer started"
while true; do
# Parse `wg show wg0 dump` into JSON array
# Format: private_key public_key listen_port fwmark
# Peer lines: public_key preshared_key endpoint allowed_ips latest_handshake transfer_rx transfer_tx persistent_keepalive
wg show wg0 dump 2>/dev/null | awk -F'\t' '
BEGIN { printf "[" ; first=1 }
NR > 1 {
if (!first) printf ","
first=0
printf "{\"public_key\":\"%s\",\"endpoint\":\"%s\",\"allowed_ips\":\"%s\",\"last_handshake\":%s,\"rx\":%s,\"tx\":%s}",
$1, $3, $4, ($5 == "" ? "0" : $5), ($6 == "" ? "0" : $6), ($7 == "" ? "0" : $7)
}
END { printf "]\n" }
' > "${STATUS_FILE}.tmp" && mv "${STATUS_FILE}.tmp" "$STATUS_FILE"
sleep 15
done
) &