From cf49c7d80b83409912fb5355c388fc452fe00f58 Mon Sep 17 00:00:00 2001 From: CyberMind-FR Date: Wed, 28 Jan 2026 15:29:24 +0100 Subject: [PATCH] fix(tor-shield,security-threats): Preset persistence and firewall stats Tor Shield: - Store current_preset in UCI when enabling with preset - Return current_preset in status response - Initialize currentPreset from stored UCI value on page load Security Threats: - Fix get_security_stats() firewall packet counting - Use correct nftables chain names (input_wan, handle_reject) - Fix grep -c exit code issue (returns 1 when no matches) - Improve numeric validation (use tr -cd to strip non-digits) - Add fallbacks for HAProxy socket paths Co-Authored-By: Claude Opus 4.5 --- .../Makefile | 2 +- .../rpcd/luci.secubox-security-threats | 106 ++++++++++++++---- package/secubox/luci-app-tor-shield/Makefile | 2 +- .../resources/view/tor-shield/overview.js | 3 + .../root/usr/libexec/rpcd/luci.tor-shield | 5 +- 5 files changed, 91 insertions(+), 27 deletions(-) diff --git a/package/secubox/luci-app-secubox-security-threats/Makefile b/package/secubox/luci-app-secubox-security-threats/Makefile index 61174859..61fd2a68 100644 --- a/package/secubox/luci-app-secubox-security-threats/Makefile +++ b/package/secubox/luci-app-secubox-security-threats/Makefile @@ -5,7 +5,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-secubox-security-threats PKG_VERSION:=1.0.0 -PKG_RELEASE:=3 +PKG_RELEASE:=4 PKG_ARCH:=all PKG_LICENSE:=Apache-2.0 PKG_MAINTAINER:=CyberMind diff --git a/package/secubox/luci-app-secubox-security-threats/root/usr/libexec/rpcd/luci.secubox-security-threats b/package/secubox/luci-app-secubox-security-threats/root/usr/libexec/rpcd/luci.secubox-security-threats index da43af72..b9e53df0 100755 --- a/package/secubox/luci-app-secubox-security-threats/root/usr/libexec/rpcd/luci.secubox-security-threats +++ b/package/secubox/luci-app-secubox-security-threats/root/usr/libexec/rpcd/luci.secubox-security-threats @@ -279,58 +279,116 @@ get_security_stats() { local wan_iface=$(uci -q get network.wan.device || uci -q get network.wan.ifname) [ -z "$wan_iface" ] && wan_iface="eth0" - # WAN dropped packets from nftables (actual firewall drops on input chain) - # Count packets dropped/rejected on wan zone input + # WAN dropped packets from nftables (use counter on drop rules) if command -v nft >/dev/null 2>&1; then - # Get drop counters from firewall input chain for wan - wan_drops=$(nft list chain inet fw4 input 2>/dev/null | grep -E "iifname.*$wan_iface.*drop|iifname.*$wan_iface.*reject" | grep -oE 'packets [0-9]+' | awk '{sum+=$2} END {print sum+0}') - # Also count from forward chain drops (wan to lan blocked) - local wan_fwd_drops=$(nft list chain inet fw4 forward 2>/dev/null | grep -E "iifname.*$wan_iface.*drop|iifname.*$wan_iface.*reject" | grep -oE 'packets [0-9]+' | awk '{sum+=$2} END {print sum+0}') - wan_drops=$((${wan_drops:-0} + ${wan_fwd_drops:-0})) + # Get all drop/reject counters from input_wan zone + # nft format: "counter packets X bytes Y" + wan_drops=$(nft list chain inet fw4 input_wan 2>/dev/null | \ + grep -E "counter packets" | \ + grep -oE 'packets [0-9]+' | \ + awk '{sum+=$2} END {print sum+0}') + [ -z "$wan_drops" ] && wan_drops=0 + + # Also try reject chain + local reject_drops=$(nft list chain inet fw4 reject_from_wan 2>/dev/null | \ + grep -E "counter packets" | \ + grep -oE 'packets [0-9]+' | \ + awk '{sum+=$2} END {print sum+0}') + [ -n "$reject_drops" ] && wan_drops=$((wan_drops + reject_drops)) fi wan_drops=${wan_drops:-0} - # Firewall rejects - count from nftables counters (more accurate than logs) + # Firewall rejects - count from reject-specific chains if command -v nft >/dev/null 2>&1; then - fw_rejects=$(nft list ruleset 2>/dev/null | grep -E "reject|drop" | grep -oE 'packets [0-9]+' | awk '{sum+=$2} END {print sum+0}') + # Count from handle_reject chain which has actual reject rules + fw_rejects=$(nft list chain inet fw4 handle_reject 2>/dev/null | \ + grep -E "counter packets" | \ + grep -oE 'packets [0-9]+' | \ + awk '{sum+=$2} END {print sum+0}') + [ -z "$fw_rejects" ] && fw_rejects=0 + + # If no handle_reject, try counting reject rules in all chains + if [ "$fw_rejects" = "0" ]; then + fw_rejects=$(nft -a list ruleset 2>/dev/null | \ + grep -E "reject|drop" | grep "counter" | \ + grep -oE 'packets [0-9]+' | \ + awk '{sum+=$2} END {print sum+0}') + fi else # Fallback to log parsing fw_rejects=$(logread 2>/dev/null | grep -c "reject\|DROP\|REJECT" || echo 0) fi - fw_rejects=$(echo "$fw_rejects" | tr -d '\n') fw_rejects=${fw_rejects:-0} # CrowdSec active bans if [ -x "$CSCLI" ]; then - cs_bans=$($CSCLI decisions list -o json 2>/dev/null | grep -c '"id":' || echo 0) - cs_bans=$(echo "$cs_bans" | tr -d '\n') + # Use jq for proper JSON parsing if available, fallback to grep + if command -v jq >/dev/null 2>&1; then + cs_bans=$($CSCLI decisions list -o json 2>/dev/null | jq 'length' 2>/dev/null) + [ -z "$cs_bans" ] && cs_bans=0 + else + # Count JSON array items + local cs_json=$($CSCLI decisions list -o json 2>/dev/null) + if [ -n "$cs_json" ] && [ "$cs_json" != "null" ] && [ "$cs_json" != "[]" ]; then + cs_bans=$(echo "$cs_json" | jsonfilter -e '@[*]' 2>/dev/null | wc -l) + fi + fi cs_bans=${cs_bans:-0} # CrowdSec alerts in last 24h - cs_alerts_24h=$($CSCLI alerts list -o json --since 24h 2>/dev/null | grep -c '"id":' || echo 0) - cs_alerts_24h=$(echo "$cs_alerts_24h" | tr -d '\n') + if command -v jq >/dev/null 2>&1; then + cs_alerts_24h=$($CSCLI alerts list -o json --since 24h 2>/dev/null | jq 'length' 2>/dev/null) + [ -z "$cs_alerts_24h" ] && cs_alerts_24h=0 + else + local alerts_json=$($CSCLI alerts list -o json --since 24h 2>/dev/null) + if [ -n "$alerts_json" ] && [ "$alerts_json" != "null" ] && [ "$alerts_json" != "[]" ]; then + cs_alerts_24h=$(echo "$alerts_json" | jsonfilter -e '@[*]' 2>/dev/null | wc -l) + fi + fi cs_alerts_24h=${cs_alerts_24h:-0} fi - # Invalid connections (conntrack) + # Invalid connections (conntrack) - only count INVALID, not UNREPLIED if [ -f /proc/net/nf_conntrack ]; then - invalid_conns=$(grep -c "INVALID\|UNREPLIED" /proc/net/nf_conntrack 2>/dev/null || echo 0) + # grep -c returns 1 exit code when no matches, so we can't use || echo 0 + invalid_conns=$(grep -c "\[INVALID\]" /proc/net/nf_conntrack 2>/dev/null) + [ -z "$invalid_conns" ] && invalid_conns=0 fi - invalid_conns=$(echo "$invalid_conns" | tr -d '\n') invalid_conns=${invalid_conns:-0} - # HAProxy connections (if running in LXC) - if lxc-info -n haproxy -s 2>/dev/null | grep -q "RUNNING"; then - # Try multiple socket paths (chroot-relative and absolute) + # HAProxy connections + # Try local haproxy first, then LXC + if [ -S /var/run/haproxy/admin.sock ]; then + haproxy_conns=$(echo "show stat" | socat stdio /var/run/haproxy/admin.sock 2>/dev/null | \ + tail -n+2 | awk -F, '{sum+=$5} END {print sum+0}') + elif [ -S /var/lib/haproxy/stats ]; then + haproxy_conns=$(echo "show stat" | socat stdio /var/lib/haproxy/stats 2>/dev/null | \ + tail -n+2 | awk -F, '{sum+=$5} END {print sum+0}') + elif command -v lxc-info >/dev/null 2>&1 && lxc-info -n haproxy -s 2>/dev/null | grep -q "RUNNING"; then + # HAProxy in LXC container haproxy_conns=$(lxc-attach -n haproxy -- sh -c ' for sock in /stats /run/haproxy.sock /var/lib/haproxy/stats /var/run/haproxy/admin.sock; do - [ -S "$sock" ] && echo "show stat" | socat stdio "$sock" 2>/dev/null && break - done | tail -n+2 | awk -F, "{sum+=\$5} END {print sum+0}" - ' 2>/dev/null || echo 0) + [ -S "$sock" ] && { echo "show stat" | socat stdio "$sock" 2>/dev/null; break; } + done' 2>/dev/null | tail -n+2 | awk -F, '{sum+=$5} END {print sum+0}') fi - haproxy_conns=$(echo "$haproxy_conns" | tr -d '\n') haproxy_conns=${haproxy_conns:-0} + # Clean up and ensure numeric values (remove all non-digits) + wan_drops=$(printf '%s' "$wan_drops" | tr -cd '0-9') + fw_rejects=$(printf '%s' "$fw_rejects" | tr -cd '0-9') + cs_bans=$(printf '%s' "$cs_bans" | tr -cd '0-9') + cs_alerts_24h=$(printf '%s' "$cs_alerts_24h" | tr -cd '0-9') + invalid_conns=$(printf '%s' "$invalid_conns" | tr -cd '0-9') + haproxy_conns=$(printf '%s' "$haproxy_conns" | tr -cd '0-9') + + # Default to 0 if empty + : ${wan_drops:=0} + : ${fw_rejects:=0} + : ${cs_bans:=0} + : ${cs_alerts_24h:=0} + : ${invalid_conns:=0} + : ${haproxy_conns:=0} + # Output JSON cat << EOF { diff --git a/package/secubox/luci-app-tor-shield/Makefile b/package/secubox/luci-app-tor-shield/Makefile index 435443fe..fdbd3a72 100644 --- a/package/secubox/luci-app-tor-shield/Makefile +++ b/package/secubox/luci-app-tor-shield/Makefile @@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-tor-shield PKG_VERSION:=1.0.0 -PKG_RELEASE:=9 +PKG_RELEASE:=10 PKG_ARCH:=all PKG_LICENSE:=MIT diff --git a/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/view/tor-shield/overview.js b/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/view/tor-shield/overview.js index 2c692083..bb59bacb 100644 --- a/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/view/tor-shield/overview.js +++ b/package/secubox/luci-app-tor-shield/htdocs/luci-static/resources/view/tor-shield/overview.js @@ -285,6 +285,9 @@ return view.extend({ var presets = data.presets || []; var bandwidth = data.bandwidth || {}; + // Initialize currentPreset from stored UCI value + this.currentPreset = status.current_preset || 'anonymous'; + var isActive = status.enabled && status.running; var isProtected = isActive && status.is_tor; var isConnecting = isActive && status.bootstrap < 100; diff --git a/package/secubox/luci-app-tor-shield/root/usr/libexec/rpcd/luci.tor-shield b/package/secubox/luci-app-tor-shield/root/usr/libexec/rpcd/luci.tor-shield index 320081af..a7d1e136 100755 --- a/package/secubox/luci-app-tor-shield/root/usr/libexec/rpcd/luci.tor-shield +++ b/package/secubox/luci-app-tor-shield/root/usr/libexec/rpcd/luci.tor-shield @@ -54,17 +54,19 @@ get_bootstrap() { get_status() { json_init - local enabled mode dns_over_tor kill_switch + local enabled mode dns_over_tor kill_switch current_preset config_load "$CONFIG" config_get enabled main enabled '0' config_get mode main mode 'transparent' config_get dns_over_tor main dns_over_tor '1' config_get kill_switch main kill_switch '1' + config_get current_preset main current_preset 'anonymous' json_add_boolean "enabled" "$enabled" json_add_string "mode" "$mode" json_add_boolean "dns_over_tor" "$dns_over_tor" json_add_boolean "kill_switch" "$kill_switch" + json_add_string "current_preset" "$current_preset" # Running state if is_running; then @@ -164,6 +166,7 @@ do_enable() { uci set tor-shield.main.mode="$preset_mode" uci set tor-shield.main.dns_over_tor="$preset_dns" uci set tor-shield.main.kill_switch="$preset_kill" + uci set tor-shield.main.current_preset="$preset" if [ "$preset_bridges" = "1" ]; then uci set tor-shield.bridges.enabled='1'