#!/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 ) &