fix: iptables rule ordering, idempotent fw functions, rule module cleanup, variable leak fixes
This commit is contained in:
parent
8ef8ea91b3
commit
a09c59a7c4
6 changed files with 296 additions and 167 deletions
|
|
@ -46,33 +46,31 @@ EOF
|
|||
# ============================================
|
||||
|
||||
function cmd::watch::format_event() {
|
||||
local ts="${1:-}" client="${2:-}" endpoint="${3:-}"
|
||||
local event="${4:-}" status="${5:-}"
|
||||
local ts="${1:-}" source="${2:-}" client="${3:-}"
|
||||
local dest="${4:-}" event="${5:-}" status="${6:-}"
|
||||
|
||||
local event_color
|
||||
case "$event" in
|
||||
attempt) event_color="\033[1;31m" ;;
|
||||
handshake) event_color="\033[1;32m" ;;
|
||||
*) event_color="\033[0;37m" ;;
|
||||
attempt|drop) event_color="\033[1;31m" ;;
|
||||
handshake) event_color="\033[1;32m" ;;
|
||||
*) event_color="\033[0;37m" ;;
|
||||
esac
|
||||
|
||||
local status_str=""
|
||||
if [[ -n "$status" ]]; then
|
||||
case "$status" in
|
||||
blocked) status_str=" \033[1;31mblocked\033[0m" ;;
|
||||
allowed) status_str=" \033[1;32mallowed\033[0m" ;;
|
||||
esac
|
||||
fi
|
||||
local status_color=""
|
||||
case "$status" in
|
||||
blocked) status_color="\033[1;31m" ;;
|
||||
allowed) status_color="\033[1;32m" ;;
|
||||
esac
|
||||
|
||||
printf " %-20s %-25s %-20s ${event_color}%-12s\033[0m%b\n" \
|
||||
"$ts" "$client" "${endpoint:-—}" "$event" "$status_str"
|
||||
printf " %-20s %-8s %-22s %-28s ${event_color}%-14s\033[0m ${status_color}%s\033[0m\n" \
|
||||
"$ts" "$source" "$client" "${dest:-—}" "$event" "$status"
|
||||
}
|
||||
|
||||
function cmd::watch::header() {
|
||||
log::section "wgctl — Live Monitor (Ctrl+C to stop)"
|
||||
printf "\n %-20s %-25s %-20s %-12s %s\n" \
|
||||
"TIME" "CLIENT" "ENDPOINT" "EVENT" "STATUS"
|
||||
printf " %s\n\n" "$(printf '─%.0s' {1..85})"
|
||||
printf "\n %-20s %-8s %-22s %-28s %-14s %s\n" \
|
||||
"TIME" "SOURCE" "CLIENT" "DESTINATION/ENDPOINT" "EVENT" "STATUS"
|
||||
printf " %s\n\n" "$(printf '─%.0s' {1..105})"
|
||||
}
|
||||
|
||||
function cmd::watch::_peer_in_filter() {
|
||||
|
|
@ -151,7 +149,7 @@ function cmd::watch::poll_handshakes() {
|
|||
endpoint=$(monitor::endpoint_for_key "$public_key")
|
||||
|
||||
cmd::watch::format_event \
|
||||
"$formatted_ts" "$client_name" "${endpoint:-—}" "handshake" "allowed"
|
||||
"$formatted_ts" "wg" "$client_name" "${endpoint:-—}" "handshake" "allowed"
|
||||
fi
|
||||
|
||||
done < <(wg show "$(config::interface)" latest-handshakes 2>/dev/null)
|
||||
|
|
@ -171,55 +169,126 @@ function cmd::watch::tail_events() {
|
|||
|
||||
declare -A _WATCH_LAST_ATTEMPT=()
|
||||
|
||||
tail -f "$(ctx::events_log)" 2>/dev/null | while IFS= read -r line; do
|
||||
# Build ip->name map for fw events
|
||||
declare -A ip_to_name=()
|
||||
local conf_file
|
||||
while IFS= read -r conf_file; do
|
||||
local name
|
||||
name=$(basename "$conf_file" .conf)
|
||||
local ip
|
||||
ip=$(grep "^Address" "$conf_file" 2>/dev/null | awk '{print $3}' | cut -d'/' -f1)
|
||||
[[ -n "$ip" && -n "$name" ]] && ip_to_name["$ip"]="$name"
|
||||
done < <(find "$(ctx::clients)" -name "*.conf" 2>/dev/null)
|
||||
|
||||
# Source tracker via temp file (persists across subshell iterations)
|
||||
local source_file
|
||||
source_file=$(mktemp)
|
||||
echo "wg" > "$source_file"
|
||||
|
||||
# Cleanup temp file on exit
|
||||
trap "rm -f '$source_file'" EXIT
|
||||
|
||||
tail -f "$(ctx::events_log)" "$(ctx::fw_events_log)" 2>/dev/null \
|
||||
| while IFS= read -r line; do
|
||||
[[ -z "$line" ]] && continue
|
||||
|
||||
local event_data
|
||||
event_data=$(json::parse_event "$line")
|
||||
[[ -z "$event_data" ]] && continue
|
||||
|
||||
local ts client endpoint event
|
||||
IFS="|" read -r ts client endpoint event <<< "$event_data"
|
||||
|
||||
# Apply filters
|
||||
[[ -n "$filter_name" && "$client" != "$filter_name" ]] && continue
|
||||
cmd::watch::_peer_in_filter "$client" "${peer_set[@]}" || continue
|
||||
|
||||
if [[ -n "$filter_type" ]]; then
|
||||
local conf
|
||||
conf="$(ctx::clients)/${client}.conf"
|
||||
[[ -f "$conf" ]] || continue
|
||||
local ip
|
||||
ip=$(grep "^Address" "$conf" | awk '{print $3}' | cut -d'/' -f1)
|
||||
local subnet
|
||||
subnet=$(config::subnet_for "$filter_type")
|
||||
string::starts_with "$ip" "$subnet" || continue
|
||||
# Handle tail -f file headers
|
||||
if [[ "$line" == "==> "* ]]; then
|
||||
if [[ "$line" == *"fw_events"* ]]; then
|
||||
echo "fw" > "$source_file"
|
||||
else
|
||||
echo "wg" > "$source_file"
|
||||
fi
|
||||
continue
|
||||
fi
|
||||
|
||||
if $restricted_only; then
|
||||
local conf
|
||||
conf="$(ctx::clients)/${client}.conf"
|
||||
[[ -f "$conf" ]] || continue
|
||||
cmd::list::is_restricted "$client" || continue
|
||||
local source
|
||||
source=$(cat "$source_file")
|
||||
|
||||
if [[ "$source" == "wg" ]]; then
|
||||
$allowed_only && continue # wg events are attempts/blocked
|
||||
|
||||
local event_data
|
||||
event_data=$(json::parse_event "$line")
|
||||
[[ -z "$event_data" ]] && continue
|
||||
|
||||
local ts client endpoint event
|
||||
IFS="|" read -r ts client endpoint event <<< "$event_data"
|
||||
|
||||
[[ -n "$filter_name" && "$client" != "$filter_name" ]] && continue
|
||||
cmd::watch::_peer_in_filter "$client" "${peer_set[@]}" || continue
|
||||
|
||||
if [[ -n "$filter_type" ]]; then
|
||||
local conf
|
||||
conf="$(ctx::clients)/${client}.conf"
|
||||
[[ -f "$conf" ]] || continue
|
||||
local ip
|
||||
ip=$(grep "^Address" "$conf" 2>/dev/null | awk '{print $3}' | cut -d'/' -f1)
|
||||
local subnet
|
||||
subnet=$(config::subnet_for "$filter_type")
|
||||
string::starts_with "$ip" "$subnet" || continue
|
||||
fi
|
||||
|
||||
$restricted_only && { cmd::list::is_restricted "$client" || continue; }
|
||||
|
||||
# Dedup
|
||||
local now
|
||||
now=$(date +%s)
|
||||
local safe_client="${client//[-.]/_}"
|
||||
local last="${_WATCH_LAST_ATTEMPT[$safe_client]:-0}"
|
||||
(( now - last < 30 )) && continue
|
||||
_WATCH_LAST_ATTEMPT[$safe_client]="$now"
|
||||
|
||||
local formatted_ts
|
||||
formatted_ts=$(fmt::datetime_iso "$ts")
|
||||
|
||||
cmd::watch::format_event \
|
||||
"$formatted_ts" "wg" "$client" "${endpoint:-—}" "$event" "blocked"
|
||||
|
||||
else
|
||||
# FW event
|
||||
$allowed_only && continue
|
||||
$blocked_only && continue # fw drops aren't "blocked peers" per se
|
||||
|
||||
local fw_data
|
||||
fw_data=$(json::parse_fw_event "$line")
|
||||
[[ -z "$fw_data" ]] && continue
|
||||
|
||||
local ts src_ip dst_ip dst_port proto
|
||||
IFS="|" read -r ts src_ip dst_ip dst_port proto <<< "$fw_data"
|
||||
|
||||
[[ -z "$src_ip" ]] && continue
|
||||
|
||||
local client="${ip_to_name[$src_ip]:-$src_ip}"
|
||||
|
||||
[[ -n "$filter_name" && "$client" != "$filter_name" ]] && continue
|
||||
cmd::watch::_peer_in_filter "$client" "${peer_set[@]}" || continue
|
||||
|
||||
if [[ -n "$filter_type" ]]; then
|
||||
local peer_client="${ip_to_name[$src_ip]:-}"
|
||||
[[ -z "$peer_client" ]] && continue
|
||||
local conf
|
||||
conf="$(ctx::clients)/${peer_client}.conf"
|
||||
[[ -f "$conf" ]] || continue
|
||||
local ip
|
||||
ip=$(grep "^Address" "$conf" 2>/dev/null | awk '{print $3}' | cut -d'/' -f1)
|
||||
local subnet
|
||||
subnet=$(config::subnet_for "$filter_type")
|
||||
string::starts_with "$ip" "$subnet" || continue
|
||||
fi
|
||||
|
||||
local dst_str="${dst_ip:-—}"
|
||||
[[ -n "$dst_port" ]] && dst_str="${dst_ip}:${dst_port}/${proto}"
|
||||
|
||||
local formatted_ts
|
||||
formatted_ts=$(fmt::datetime_iso "$ts")
|
||||
|
||||
cmd::watch::format_event \
|
||||
"$formatted_ts" "fw" "$client" "$dst_str" "drop" "blocked"
|
||||
fi
|
||||
|
||||
$allowed_only && [[ "$event" != "handshake" ]] && continue
|
||||
|
||||
local formatted_ts
|
||||
formatted_ts=$(fmt::datetime_iso "$ts")
|
||||
|
||||
# Dedup attempts
|
||||
local now
|
||||
now=$(date +%s)
|
||||
local safe_client="${client//[-.]/_}"
|
||||
local last="${_WATCH_LAST_ATTEMPT[$safe_client]:-0}"
|
||||
local diff=$(( now - last ))
|
||||
(( diff < 30 )) && continue
|
||||
_WATCH_LAST_ATTEMPT[$safe_client]="$now"
|
||||
|
||||
cmd::watch::format_event \
|
||||
"$formatted_ts" "$client" "${endpoint:-—}" "$event" "blocked"
|
||||
done
|
||||
|
||||
rm -f "$source_file"
|
||||
}
|
||||
|
||||
# ============================================
|
||||
|
|
|
|||
|
|
@ -31,4 +31,5 @@ function json::create_rule() { python3 "$JSON_HELPER" create_rule "$@" </
|
|||
function json::cleanup_config() { python3 "$JSON_HELPER" cleanup_config "$@" </dev/null; }
|
||||
function json::remove_peer_block() { python3 "$JSON_HELPER" remove_peer_block "$@" </dev/null; }
|
||||
function json::create_group() { python3 "$JSON_HELPER" create_group "$@" </dev/null; }
|
||||
function json::parse_event() { python3 "$JSON_HELPER" parse_event "$@" </dev/null; }
|
||||
function json::parse_event() { python3 "$JSON_HELPER" parse_event "$@" </dev/null; }
|
||||
function json::parse_fw_event() { python3 "$JSON_HELPER" parse_fw_event "$@" </dev/null; }
|
||||
|
|
@ -696,6 +696,21 @@ def parse_event(line):
|
|||
except:
|
||||
pass
|
||||
|
||||
def parse_fw_event(line):
|
||||
"""Parse a single fw_events.log JSON line"""
|
||||
try:
|
||||
e = json.loads(line)
|
||||
ts = e.get('timestamp', '')
|
||||
src = e.get('src_ip', '')
|
||||
dst = e.get('dest_ip', '')
|
||||
port = e.get('dest_port', '')
|
||||
proto_map = {1: 'icmp', 6: 'tcp', 17: 'udp'}
|
||||
proto_num = e.get('ip.protocol', 0)
|
||||
proto = proto_map.get(proto_num, str(proto_num))
|
||||
print(f"{ts}|{src}|{dst}|{port}|{proto}")
|
||||
except:
|
||||
pass
|
||||
|
||||
commands = {
|
||||
'get': lambda args: get(args[0], args[1]),
|
||||
'set': lambda args: set_key(args[0], args[1], args[2]),
|
||||
|
|
@ -727,6 +742,7 @@ commands = {
|
|||
'remove_peer_block': lambda args: remove_peer_block(args[0], args[1]),
|
||||
'create_group': lambda args: create_group(args[0], args[1], args[2]),
|
||||
'parse_event': lambda args: parse_event(args[0]),
|
||||
'parse_fw_event': lambda args: parse_fw_event(args[0]),
|
||||
}
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
{
|
||||
"phone-fred": "94.63.0.129",
|
||||
"phone-helena": "148.69.44.173",
|
||||
"phone-nuno": "94.63.0.129",
|
||||
"phone-helena": "148.69.46.241",
|
||||
"phone-nuno": "148.69.51.160",
|
||||
"tablet-nuno": "148.69.202.5",
|
||||
"guest-zephyr": "5.13.82.5",
|
||||
"guest-zephyr-test": "94.63.0.129",
|
||||
"guest-zephyr-test": "148.69.193.47",
|
||||
"desktop-roboclean": "46.189.215.231"
|
||||
}
|
||||
|
|
@ -13,78 +13,115 @@ function fw::on_load() {
|
|||
# ============================================
|
||||
|
||||
function fw::block_ip() {
|
||||
local client_ip="$1" target_ip="$2"
|
||||
iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -j DROP
|
||||
iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:"
|
||||
local client_ip="${1:-}" target_ip="${2:-}"
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -j DROP \
|
||||
|| iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -j DROP
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
||||
|| iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:"
|
||||
}
|
||||
|
||||
function fw::unblock_ip() {
|
||||
local client_ip="$1" target_ip="$2"
|
||||
local client_ip="${1:-}" target_ip="${2:-}"
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_ip" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" 2>/dev/null || true
|
||||
|
||||
iptables -D FORWARD -s "$client_ip" -d "$target_ip" -j LOG --log-prefix "wgctl-dropped: " --log-level 4 2>/dev/null || true
|
||||
iptables -D FORWARD -s "$client_ip" -d "$target_ip" -j DROP 2>/dev/null || true
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -j DROP \
|
||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_ip" -j DROP 2>/dev/null || true
|
||||
}
|
||||
|
||||
function fw::block_port() {
|
||||
local client_ip="$1" target_ip="$2" port="$3" proto="${4:-tcp}"
|
||||
iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j DROP
|
||||
iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:"
|
||||
local client_ip="${1:-}" target_ip="${2:-}" port="${3:-}" proto="${4:-tcp}"
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j DROP \
|
||||
|| iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j DROP
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
||||
|| iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:"
|
||||
|
||||
}
|
||||
|
||||
function fw::unblock_port() {
|
||||
local client_ip="$1" target_ip="$2" port="$3" proto="${4:-tcp}"
|
||||
iptables -D FORWARD -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j DROP 2>/dev/null || true
|
||||
local client_ip="${1:-}" target_ip="${2:-}" port="${3:-}" proto="${4:-tcp}"
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" 2>/dev/null || true
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j DROP \
|
||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j DROP 2>/dev/null || true
|
||||
}
|
||||
|
||||
function fw::block_all() {
|
||||
local client_ip="$1" client_name="$2"
|
||||
local client_ip="${1:-}" client_name="${2:-}"
|
||||
|
||||
iptables -A FORWARD -s "$client_ip" -j DROP
|
||||
fw::_forward_exists -s "$client_ip" -j DROP \
|
||||
|| iptables -A FORWARD -s "$client_ip" -j DROP
|
||||
|
||||
log::debug "Blocked all traffic from: ${client_ip}"
|
||||
}
|
||||
|
||||
function fw::unblock_all() {
|
||||
local client_ip="$1"
|
||||
local client_ip="${1:-}"
|
||||
|
||||
iptables -D FORWARD -s "$client_ip" -j DROP 2>/dev/null || true
|
||||
fw::_forward_exists -s "$client_ip" -j DROP \
|
||||
&& iptables -D FORWARD -s "$client_ip" -j DROP 2>/dev/null || true
|
||||
|
||||
monitor::unwatch "$client_ip"
|
||||
log::debug "Unblocked all traffic from: ${client_ip}"
|
||||
}
|
||||
|
||||
function fw::block_subnet() {
|
||||
local client_ip="$1" target_subnet="$2"
|
||||
local client_ip="${1:-}" target_subnet="${2:-}"
|
||||
|
||||
iptables -A FORWARD -s "$client_ip" -d "$target_subnet" -j DROP
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
||||
|| iptables -A FORWARD -s "$client_ip" -d "$target_subnet" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:"
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j DROP \
|
||||
|| iptables -A FORWARD -s "$client_ip" -d "$target_subnet" -j DROP
|
||||
|
||||
log::wg_block "Blocked ${client_ip} → subnet ${target_subnet}"
|
||||
}
|
||||
|
||||
function fw::unblock_subnet() {
|
||||
local client_ip="$1" target_subnet="$2"
|
||||
local client_ip="${1:-}" target_subnet="${2:-}"
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" \
|
||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_subnet" -j NFLOG --nflog-group 1 --nflog-prefix "wgctl-drop:" 2>/dev/null || true
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_subnet" -j DROP \
|
||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_subnet" -j DROP 2>/dev/null || true
|
||||
|
||||
iptables -D FORWARD -s "$client_ip" -d "$target_subnet" -j DROP 2>/dev/null || true
|
||||
log::wg_unblock "Unblocked ${client_ip} → subnet ${target_subnet}"
|
||||
}
|
||||
|
||||
function fw::allow_ip() {
|
||||
local client_ip="$1" target_ip="$2"
|
||||
iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -j ACCEPT
|
||||
local client_ip="${1:-}" target_ip="${2:-}"
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -j ACCEPT \
|
||||
|| iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -j ACCEPT
|
||||
}
|
||||
|
||||
function fw::unallow_ip() {
|
||||
local client_ip="$1" target_ip="$2"
|
||||
iptables -D FORWARD -s "$client_ip" -d "$target_ip" -j ACCEPT 2>/dev/null || true
|
||||
local client_ip="${1:-}" target_ip="${2:-}"
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -j ACCEPT \
|
||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_ip" -j ACCEPT 2>/dev/null || true
|
||||
}
|
||||
|
||||
function fw::allow_port() {
|
||||
local client_ip="$1" target_ip="$2" port="$3" proto="${4:-tcp}"
|
||||
iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j ACCEPT
|
||||
local client_ip="${1:-}" target_ip="${2:-}" port="${3:-}" proto="${4:-tcp}"
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j ACCEPT \
|
||||
|| iptables -I FORWARD 1 -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j ACCEPT
|
||||
}
|
||||
|
||||
function fw::unallow_port() {
|
||||
local client_ip="$1" target_ip="$2" port="$3" proto="${4:-tcp}"
|
||||
iptables -D FORWARD -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j ACCEPT 2>/dev/null || true
|
||||
local client_ip="${1:-}" target_ip="${2:-}" port="${3:-}" proto="${4:-tcp}"
|
||||
|
||||
fw::_forward_exists -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j ACCEPT \
|
||||
&& iptables -D FORWARD -s "$client_ip" -d "$target_ip" -p "$proto" --dport "$port" -j ACCEPT 2>/dev/null || true
|
||||
}
|
||||
|
||||
function fw::flush_peer() {
|
||||
|
|
@ -205,3 +242,25 @@ function fw::restore_blocks() {
|
|||
log::debug "Restored block rules for: ${name}"
|
||||
done
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Helpers
|
||||
# ============================================
|
||||
|
||||
function fw::_nat_exists() {
|
||||
fw::_rule_exists nat PREROUTING "$@"
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# private
|
||||
# ============================================
|
||||
|
||||
function fw::_rule_exists() {
|
||||
local table="${1:-filter}" chain="${2:-FORWARD}"
|
||||
shift 2
|
||||
iptables -t "$table" -C "$chain" "$@" 2>/dev/null
|
||||
}
|
||||
|
||||
function fw::_forward_exists() {
|
||||
iptables -C FORWARD "$@" 2>/dev/null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@
|
|||
# ============================================
|
||||
|
||||
function rule::exists() {
|
||||
local name="$1"
|
||||
local name="${1:-}"
|
||||
[[ -f "$(ctx::rule::path "${name}.rule")" ]]
|
||||
}
|
||||
|
||||
function rule::require_exists() {
|
||||
local name="$1"
|
||||
local name="${1:-}"
|
||||
if ! rule::exists "$name"; then
|
||||
log::error "Rule not found: ${name}"
|
||||
return 1
|
||||
|
|
@ -18,52 +18,47 @@ function rule::require_exists() {
|
|||
}
|
||||
|
||||
function rule::get() {
|
||||
local name="$1" key="$2"
|
||||
local name="${1:-}" key="${2:-}"
|
||||
json::get "$(ctx::rule::path "${name}.rule")" "$key"
|
||||
}
|
||||
|
||||
function rule::get_all() {
|
||||
local name="$1"
|
||||
local rule_file
|
||||
rule_file="$(ctx::rule::path "${name}.rule")"
|
||||
cat "$rule_file"
|
||||
local name="${1:-}"
|
||||
cat "$(ctx::rule::path "${name}.rule")"
|
||||
}
|
||||
|
||||
function rule::is_applied() {
|
||||
local rule_name="$1"
|
||||
local client_ip="$2"
|
||||
local rule_name="${1:-}" client_ip="${2:-}"
|
||||
|
||||
# Try first block_ports entry
|
||||
local first_port
|
||||
first_port=$(rule::get "$rule_name" "block_ports" | head -1)
|
||||
if [[ -n "$first_port" ]]; then
|
||||
local target port proto
|
||||
IFS=":" read -r target port proto <<< "$first_port"
|
||||
proto="${proto:-tcp}"
|
||||
iptables -C FORWARD -s "$client_ip" -d "$target" -p "$proto" --dport "$port" -j DROP 2>/dev/null
|
||||
fw::_forward_exists -s "$client_ip" -d "$target" \
|
||||
-p "$proto" --dport "$port" -j DROP
|
||||
return $?
|
||||
fi
|
||||
|
||||
# Fall back to first block_ips entry
|
||||
local first_ip
|
||||
first_ip=$(rule::get "$rule_name" "block_ips" | head -1)
|
||||
if [[ -n "$first_ip" ]]; then
|
||||
iptables -C FORWARD -s "$client_ip" -d "$first_ip" -j DROP 2>/dev/null
|
||||
fw::_forward_exists -s "$client_ip" -d "$first_ip" -j DROP
|
||||
return $?
|
||||
fi
|
||||
|
||||
# No rules to check (admin rule) — check allow_ports
|
||||
local first_allow
|
||||
first_allow=$(rule::get "$rule_name" "allow_ports" | head -1)
|
||||
if [[ -n "$first_allow" ]]; then
|
||||
local target port proto
|
||||
IFS=":" read -r target port proto <<< "$first_allow"
|
||||
proto="${proto:-tcp}"
|
||||
iptables -C FORWARD -s "$client_ip" -d "$target" -p "$proto" --dport "$port" -j ACCEPT 2>/dev/null
|
||||
fw::_forward_exists -s "$client_ip" -d "$target" \
|
||||
-p "$proto" --dport "$port" -j ACCEPT
|
||||
return $?
|
||||
fi
|
||||
|
||||
# Empty rule (admin) — never "applied" in iptables sense
|
||||
return 1
|
||||
}
|
||||
|
||||
|
|
@ -74,42 +69,27 @@ function rule::is_applied() {
|
|||
function rule::apply() {
|
||||
local rule_name="${1:?rule_name required}"
|
||||
local client_ip="${2:?client_ip required}"
|
||||
local peer_name="${3:-}" # optional, avoids find_by_ip call
|
||||
local peer_name="${3:-}"
|
||||
|
||||
rule::require_exists "$rule_name" || return 1
|
||||
|
||||
# Use provided peer_name or look it up
|
||||
if [[ -z "$peer_name" ]]; then
|
||||
peer_name=$(peers::find_by_ip "$client_ip")
|
||||
fi
|
||||
|
||||
log::debug "rule::apply: peer_name=$peer_name ip=$client_ip"
|
||||
|
||||
# Check if already applied
|
||||
if rule::is_applied "$rule_name" "$client_ip"; then
|
||||
log::wg "Rule '${rule_name}' already applied to: ${client_ip}"
|
||||
|
||||
# Still update meta even if rules exist
|
||||
if [[ -n "$peer_name" ]]; then
|
||||
peers::set_meta "$peer_name" "rule" "$rule_name"
|
||||
fi
|
||||
|
||||
[[ -n "$peer_name" ]] && peers::set_meta "$peer_name" "rule" "$rule_name"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check if already applied
|
||||
local peer_name
|
||||
peer_name=$(peers::find_by_ip "$client_ip")
|
||||
if [[ -n "$peer_name" ]]; then
|
||||
# Check if already applied via iptables
|
||||
if rule::is_applied "$rule_name" "$client_ip"; then
|
||||
log::wg "Rule '${rule_name}' already applied to: ${client_ip}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Process block_ips
|
||||
while IFS= read -r ip; do
|
||||
[[ -z "$ip" ]] && continue
|
||||
fw::block_ip "$client_ip" "$ip"
|
||||
while IFS= read -r block_ip; do
|
||||
[[ -z "$block_ip" ]] && continue
|
||||
fw::block_ip "$client_ip" "$block_ip"
|
||||
done < <(rule::get "$rule_name" "block_ips")
|
||||
|
||||
# Process block_ports
|
||||
|
|
@ -122,12 +102,12 @@ function rule::apply() {
|
|||
done < <(rule::get "$rule_name" "block_ports")
|
||||
|
||||
# Process allow_ips (inserted before blocks)
|
||||
while IFS= read -r ip; do
|
||||
[[ -z "$ip" ]] && continue
|
||||
fw::allow_ip "$client_ip" "$ip"
|
||||
while IFS= read -r allow_ip; do
|
||||
[[ -z "$allow_ip" ]] && continue
|
||||
fw::allow_ip "$client_ip" "$allow_ip"
|
||||
done < <(rule::get "$rule_name" "allow_ips")
|
||||
|
||||
# allow_ports (inserted last = highest priority)
|
||||
# Process allow_ports (highest priority)
|
||||
while IFS= read -r entry; do
|
||||
[[ -z "$entry" ]] && continue
|
||||
local target port proto
|
||||
|
|
@ -136,20 +116,18 @@ function rule::apply() {
|
|||
fw::allow_port "$client_ip" "$target" "$port" "$proto"
|
||||
done < <(rule::get "$rule_name" "allow_ports")
|
||||
|
||||
# Persist rule assignment in meta
|
||||
if [[ -n "$peer_name" ]]; then
|
||||
peers::set_meta "$peer_name" "rule" "$rule_name"
|
||||
fi
|
||||
# Persist rule assignment
|
||||
[[ -n "$peer_name" ]] && peers::set_meta "$peer_name" "rule" "$rule_name"
|
||||
|
||||
# DNS redirect
|
||||
local dns_redirect
|
||||
dns_redirect=$(rule::get "$rule_name" "dns_redirect")
|
||||
|
||||
if [[ "$dns_redirect" == "true" ]]; then
|
||||
local peer_subnet
|
||||
peer_subnet=$(peers::get_ip "$peer_name" | cut -d'.' -f1-3)
|
||||
# Only apply if not already in PREROUTING
|
||||
if ! iptables -t nat -C PREROUTING -i wg0 -s "${peer_subnet}.0/24" -p udp --dport 53 \
|
||||
-j DNAT --to-destination "$(config::dns):53" 2>/dev/null; then
|
||||
if ! fw::_nat_exists -i wg0 -s "${peer_subnet}.0/24" \
|
||||
-p udp --dport 53 -j DNAT \
|
||||
--to-destination "$(config::dns):53" 2>/dev/null; then
|
||||
rule::apply_dns_redirect "${peer_subnet}.0/24"
|
||||
log::debug "dns_redirect: applied for ${peer_subnet}.0/24"
|
||||
else
|
||||
|
|
@ -161,11 +139,13 @@ function rule::apply() {
|
|||
}
|
||||
|
||||
function rule::unapply() {
|
||||
local rule_name="$1"
|
||||
local client_ip="$2"
|
||||
local rule_name="${1:-}" client_ip="${2:-}"
|
||||
|
||||
rule::require_exists "$rule_name" || return 1
|
||||
|
||||
local peer_name
|
||||
peer_name=$(peers::find_by_ip "$client_ip")
|
||||
|
||||
# Remove allow_ports first (reverse order of apply)
|
||||
while IFS= read -r entry; do
|
||||
[[ -z "$entry" ]] && continue
|
||||
|
|
@ -176,9 +156,9 @@ function rule::unapply() {
|
|||
done < <(rule::get "$rule_name" "allow_ports")
|
||||
|
||||
# Remove allow_ips
|
||||
while IFS= read -r ip; do
|
||||
[[ -z "$ip" ]] && continue
|
||||
fw::unallow_ip "$client_ip" "$ip"
|
||||
while IFS= read -r allow_ip; do
|
||||
[[ -z "$allow_ip" ]] && continue
|
||||
fw::unallow_ip "$client_ip" "$allow_ip"
|
||||
done < <(rule::get "$rule_name" "allow_ips")
|
||||
|
||||
# Remove block_ports
|
||||
|
|
@ -191,38 +171,41 @@ function rule::unapply() {
|
|||
done < <(rule::get "$rule_name" "block_ports")
|
||||
|
||||
# Remove block_ips
|
||||
while IFS= read -r ip; do
|
||||
[[ -z "$ip" ]] && continue
|
||||
fw::unblock_ip "$client_ip" "$ip"
|
||||
while IFS= read -r block_ip; do
|
||||
[[ -z "$block_ip" ]] && continue
|
||||
fw::unblock_ip "$client_ip" "$block_ip"
|
||||
done < <(rule::get "$rule_name" "block_ips")
|
||||
|
||||
# Remove DNS redirect if applicable
|
||||
local dns_redirect
|
||||
dns_redirect=$(rule::get "$rule_name" "dns_redirect")
|
||||
if [[ "$dns_redirect" == "true" ]]; then
|
||||
local peer_name subnet
|
||||
peer_name=$(peers::find_by_ip "$client_ip")
|
||||
subnet=$(config::subnet_for "$(peers::get_meta "$peer_name" "subtype")")
|
||||
local subtype
|
||||
subtype=$(peers::get_meta "$peer_name" "subtype")
|
||||
local subnet
|
||||
if [[ -n "$subtype" ]]; then
|
||||
subnet=$(config::subnet_for "$subtype")
|
||||
else
|
||||
local peer_type
|
||||
peer_type=$(peers::get_type "$peer_name") || true
|
||||
[[ -z "$peer_type" ]] && peer_type="phone"
|
||||
subnet=$(config::subnet_for "$peer_type")
|
||||
fi
|
||||
rule::remove_dns_redirect "${subnet}.0/24"
|
||||
fi
|
||||
|
||||
# Clear rule from meta
|
||||
local peer_name
|
||||
peer_name=$(peers::find_by_ip "$client_ip")
|
||||
if [[ -n "$peer_name" ]]; then
|
||||
peers::set_meta "$peer_name" "rule" ""
|
||||
fi
|
||||
[[ -n "$peer_name" ]] && peers::set_meta "$peer_name" "rule" ""
|
||||
|
||||
log::debug "Removed rule '${rule_name}' from: ${client_ip}"
|
||||
}
|
||||
|
||||
function rule::reapply_all() {
|
||||
local rule_name="$1"
|
||||
local rule_name="${1:-}"
|
||||
rule::require_exists "$rule_name" || return 1
|
||||
|
||||
local peers=()
|
||||
mapfile -t peers < <(peers::with_rule "$rule_name")
|
||||
|
||||
[[ ${#peers[@]} -eq 0 ]] && return 0
|
||||
|
||||
local count=0
|
||||
|
|
@ -243,27 +226,27 @@ function rule::restore_all() {
|
|||
local rule_name
|
||||
rule_name=$(peers::get_meta "$peer_name" "rule")
|
||||
[[ -z "$rule_name" ]] && continue
|
||||
|
||||
|
||||
if ! rule::exists "$rule_name"; then
|
||||
log::wg_warning "Rule '${rule_name}' not found for peer '${peer_name}', skipping"
|
||||
continue
|
||||
fi
|
||||
|
||||
|
||||
local client_ip
|
||||
client_ip=$(peers::get_ip "$peer_name")
|
||||
[[ -z "$client_ip" ]] && continue
|
||||
|
||||
|
||||
rule::apply "$rule_name" "$client_ip"
|
||||
done < <(peers::all)
|
||||
log::wg "Rules restored for all peers"
|
||||
}
|
||||
|
||||
# ============================================
|
||||
# Guest DNS Redirect (rule-level feature)
|
||||
# DNS Redirect
|
||||
# ============================================
|
||||
|
||||
function rule::apply_dns_redirect() {
|
||||
local client_subnet="$1"
|
||||
local client_subnet="${1:-}"
|
||||
local dns
|
||||
dns="$(config::dns)"
|
||||
|
||||
|
|
@ -276,12 +259,13 @@ function rule::apply_dns_redirect() {
|
|||
}
|
||||
|
||||
function rule::remove_dns_redirect() {
|
||||
local client_subnet="$1"
|
||||
local client_subnet="${1:-}"
|
||||
local dns
|
||||
dns="$(config::dns)"
|
||||
|
||||
iptables -t nat -D PREROUTING -i wg0 -s "$client_subnet" -p udp --dport 53 \
|
||||
! -d "$dns" -j LOG --log-prefix "wgctl-dns-redirect: " --log-level 4 2>/dev/null || true
|
||||
! -d "$dns" -j LOG --log-prefix "wgctl-dns-redirect: " \
|
||||
--log-level 4 2>/dev/null || true
|
||||
iptables -t nat -D PREROUTING -i wg0 -s "$client_subnet" -p udp --dport 53 \
|
||||
-j DNAT --to-destination "${dns}:53" 2>/dev/null || true
|
||||
iptables -t nat -D PREROUTING -i wg0 -s "$client_subnet" -p tcp --dport 53 \
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue