fix: resolve ghost iptables rules, set -e crashes, and automate MASQUERADE NAT
This commit is contained in:
+78
-33
@@ -16,7 +16,15 @@ rollback() {
|
||||
log_error "ROLLBACK triggered! Restoring previous rules..."
|
||||
|
||||
# Remove new chain references
|
||||
while iptables -D FORWARD -i "$WG_IF" -j "$CHAIN" 2>/dev/null; do :; done
|
||||
while true; do
|
||||
local rline=""
|
||||
rline=$(iptables -nL FORWARD --line-numbers 2>/dev/null | grep "$CHAIN" | awk '{print $1}' | head -n 1 || true)
|
||||
if [[ -n "$rline" ]]; then
|
||||
iptables -D FORWARD "$rline" 2>/dev/null || break
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Flush and remove new chain
|
||||
iptables -F "$CHAIN" 2>/dev/null || true
|
||||
@@ -52,6 +60,11 @@ main() {
|
||||
log_info "Starting policy engine..."
|
||||
|
||||
# === VALIDATE ===
|
||||
if ! ip link show "$WG_IF" &>/dev/null; then
|
||||
log_error "Interface $WG_IF is not running. Aborting policy engine."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$POLICY_FILE" ]]; then
|
||||
log_error "Policy file not found: $POLICY_FILE"
|
||||
exit 1
|
||||
@@ -69,11 +82,12 @@ main() {
|
||||
trap 'rollback' ERR
|
||||
|
||||
# === DETECT SUBNETS ===
|
||||
local WG_SUBNET WG_SUBNET_V6 LAN_SUBNETS
|
||||
local WG_SUBNET WG_SUBNET_V6 LAN_SUBNETS DEF_IF
|
||||
|
||||
WG_SUBNET="$(detect_wg_subnet inet)"
|
||||
WG_SUBNET_V6="$(detect_wg_subnet inet6)"
|
||||
LAN_SUBNETS="$(detect_lan_subnets)"
|
||||
DEF_IF="$(detect_default_if)"
|
||||
|
||||
if [[ -z "$WG_SUBNET" ]]; then
|
||||
log_warn "Interface $WG_IF has no IPv4, skipping client isolation"
|
||||
@@ -90,14 +104,41 @@ main() {
|
||||
echo "$LAN_SUBNETS" | while read -r s; do log_info " $s"; done
|
||||
fi
|
||||
|
||||
# === BASE ROUTING & NAT ===
|
||||
# Enable IP Forwarding
|
||||
sysctl -w net.ipv4.ip_forward=1 >/dev/null 2>&1 || true
|
||||
if command -v ip6tables &>/dev/null; then
|
||||
sysctl -w net.ipv6.conf.all.forwarding=1 >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
# Allow return traffic to wg interface
|
||||
if ! iptables -C FORWARD -o "$WG_IF" -j ACCEPT 2>/dev/null; then
|
||||
iptables -I FORWARD 1 -o "$WG_IF" -j ACCEPT
|
||||
log_info "Added FORWARD rule for return traffic to $WG_IF"
|
||||
fi
|
||||
|
||||
# Setup MASQUERADE on default interface
|
||||
if ! iptables -t nat -C POSTROUTING -o "$DEF_IF" -j MASQUERADE 2>/dev/null; then
|
||||
iptables -t nat -A POSTROUTING -o "$DEF_IF" -j MASQUERADE
|
||||
log_info "Enabled IPv4 MASQUERADE on $DEF_IF"
|
||||
fi
|
||||
|
||||
# === CLEANUP OLD CHAIN (loop until all references removed) ===
|
||||
log_info "Cleaning up old chain references..."
|
||||
while iptables -D FORWARD -i "$WG_IF" -j "$CHAIN" 2>/dev/null; do :; done
|
||||
while true; do
|
||||
local rline=""
|
||||
rline=$(iptables -nL FORWARD --line-numbers 2>/dev/null | grep "$CHAIN" | awk '{print $1}' | head -n 1 || true)
|
||||
if [[ -n "$rline" ]]; then
|
||||
iptables -D FORWARD "$rline" 2>/dev/null || break
|
||||
else
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# Backup existing chain before flushing
|
||||
if iptables -L "$CHAIN" -n &>/dev/null; then
|
||||
iptables -N "$CHAIN_BACKUP" 2>/dev/null || iptables -F "$CHAIN_BACKUP"
|
||||
iptables-save -c | grep "^-A $CHAIN" | \
|
||||
iptables-save -c 2>/dev/null | grep "^-A $CHAIN" | \
|
||||
sed "s/-A $CHAIN/-A $CHAIN_BACKUP/" | \
|
||||
iptables-restore -c 2>/dev/null || true
|
||||
log_info "Backed up existing chain to $CHAIN_BACKUP"
|
||||
@@ -114,7 +155,7 @@ main() {
|
||||
fi
|
||||
log_info "Chain $CHAIN created and linked to FORWARD"
|
||||
|
||||
# === POPULATE IPSET (for large-scale whitelist) ===
|
||||
# === POPULATE IPSET (hash:net,net for source->target mapping) ===
|
||||
local use_ipset=false
|
||||
if has_ipset; then
|
||||
use_ipset=true
|
||||
@@ -131,7 +172,7 @@ main() {
|
||||
flush_ipset "$IPSET_V6"
|
||||
fi
|
||||
|
||||
# Read all access entries and populate ipset
|
||||
# Read all access entries and populate ipset (client_ip,target)
|
||||
jq -r '
|
||||
.clients // {} | to_entries[] |
|
||||
select(.value.access != null and (.value.access | length > 0)) |
|
||||
@@ -143,12 +184,12 @@ main() {
|
||||
|
||||
if [[ "$target" == *":"* ]]; then
|
||||
if [[ "$use_ipv6" == true ]]; then
|
||||
ipset add "$IPSET_V6" "$target" 2>/dev/null || \
|
||||
log_warn "Failed to add $target to ipset $IPSET_V6"
|
||||
ipset add "$IPSET_V6" "${client_ip},${target}" 2>/dev/null || \
|
||||
log_warn "Failed to add ${client_ip},${target} to ipset $IPSET_V6"
|
||||
fi
|
||||
else
|
||||
ipset add "$IPSET_V4" "$target" 2>/dev/null || \
|
||||
log_warn "Failed to add $target to ipset $IPSET_V4"
|
||||
ipset add "$IPSET_V4" "${client_ip},${target}" 2>/dev/null || \
|
||||
log_warn "Failed to add ${client_ip},${target} to ipset $IPSET_V4"
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -169,21 +210,10 @@ main() {
|
||||
|
||||
# === RULE 2: WHITELIST (per-client source) ===
|
||||
if [[ "$use_ipset" == true ]]; then
|
||||
jq -r '
|
||||
.clients // {} | to_entries[] |
|
||||
select(.value.access != null and (.value.access | length > 0)) |
|
||||
"\(.key)"
|
||||
' "$POLICY_FILE" 2>/dev/null | while read -r client_ip; do
|
||||
[[ -z "$client_ip" ]] && continue
|
||||
|
||||
if [[ "$client_ip" == *":"* ]]; then
|
||||
if [[ "$use_ipv6" == true ]]; then
|
||||
ip6tables -A "$CHAIN" -s "$client_ip" -m set --match-set "$IPSET_V6" dst -j ACCEPT 2>/dev/null || true
|
||||
fi
|
||||
else
|
||||
iptables -A "$CHAIN" -s "$client_ip" -m set --match-set "$IPSET_V4" dst -j ACCEPT
|
||||
fi
|
||||
done
|
||||
iptables -A "$CHAIN" -m set --match-set "$IPSET_V4" src,dst -j ACCEPT
|
||||
if [[ "$use_ipv6" == true ]]; then
|
||||
ip6tables -A "$CHAIN" -m set --match-set "$IPSET_V6" src,dst -j ACCEPT 2>/dev/null || true
|
||||
fi
|
||||
else
|
||||
jq -r '
|
||||
.clients // {} | to_entries[] |
|
||||
@@ -224,12 +254,11 @@ main() {
|
||||
fi
|
||||
|
||||
# === RULE 4: BLOCK LAN — drop from WG subnet to private LAN ===
|
||||
# FIXED: iterate per LAN subnet, block from WG_SUBNET (not per-client IP)
|
||||
if [[ -n "$WG_SUBNET" && -n "$LAN_SUBNETS" ]]; then
|
||||
echo "$LAN_SUBNETS" | while read -r subnet; do
|
||||
[[ -z "$subnet" ]] && continue
|
||||
# Skip if LAN subnet overlaps with WG subnet
|
||||
[[ -n "$WG_SUBNET" && "$subnet" == "$WG_SUBNET" ]] && continue
|
||||
# Skip if LAN subnet exactly matches WG subnet (handled by Rule 3)
|
||||
[[ "$subnet" == "$WG_SUBNET" ]] && continue
|
||||
|
||||
iptables -A "$CHAIN" -s "$WG_SUBNET" -d "$subnet" -j DROP
|
||||
log_info "Block: $WG_SUBNET → $subnet"
|
||||
@@ -245,8 +274,24 @@ main() {
|
||||
log_info "IPv6 LAN block applied (link-local + ULA)"
|
||||
fi
|
||||
|
||||
# === RULE 5: LOGGING (rate-limited) — BEFORE final ACCEPT ===
|
||||
# FIXED: LOG was after ACCEPT in original, now placed before final rule
|
||||
# === RULE 5: INTERNET ACCESS (#Internet = true) ===
|
||||
jq -r '
|
||||
.clients // {} | to_entries[] |
|
||||
select(.value.internet == "true") |
|
||||
"\(.key)"
|
||||
' "$POLICY_FILE" 2>/dev/null | while read -r client_ip; do
|
||||
[[ -z "$client_ip" ]] && continue
|
||||
|
||||
if [[ "$client_ip" == *":"* ]]; then
|
||||
if [[ "$use_ipv6" == true ]]; then
|
||||
ip6tables -A "$CHAIN" -s "$client_ip" -j ACCEPT 2>/dev/null || true
|
||||
fi
|
||||
else
|
||||
iptables -A "$CHAIN" -s "$client_ip" -j ACCEPT
|
||||
fi
|
||||
done
|
||||
|
||||
# === RULE 6: LOGGING (rate-limited) — BEFORE final DROP ===
|
||||
iptables -A "$CHAIN" \
|
||||
-m limit --limit "$LOG_RATE" \
|
||||
-j LOG --log-prefix "${LOG_PREFIX}: " --log-level 4
|
||||
@@ -257,11 +302,11 @@ main() {
|
||||
-j LOG --log-prefix "${LOG_PREFIX}: " --log-level 4 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# === RULE 6: DEFAULT ACCEPT (internet access) ===
|
||||
iptables -A "$CHAIN" -j ACCEPT
|
||||
# === RULE 7: DEFAULT DROP (internet block by default) ===
|
||||
iptables -A "$CHAIN" -j DROP
|
||||
|
||||
if [[ "$use_ipv6" == true ]]; then
|
||||
ip6tables -A "$CHAIN" -j ACCEPT 2>/dev/null || true
|
||||
ip6tables -A "$CHAIN" -j DROP 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# === CLEANUP BACKUP CHAIN (no rollback needed anymore) ===
|
||||
|
||||
Reference in New Issue
Block a user