secubox-openwrt/package/secubox/luci-app-secubox-security-threats/root/usr/libexec/rpcd/luci.secubox-security-threats
CyberMind-FR c4302504df refactor(security-threats): KISS rewrite with mesh threat intelligence
Replace overengineered dashboard (2025 lines) with focused security
intelligence view (847 lines). Drop hero banner, risk gauge, device
zoning, nDPId correlation engine. Keep firewall stats, mitmproxy
threats, CrowdSec blocking. Add mesh intelligence section with P2P
threat-intel sharing (IOC counts, peer contributors, publish/apply).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 11:30:25 +01:00

319 lines
10 KiB
Bash
Executable File

#!/bin/sh
# SPDX-License-Identifier: Apache-2.0
# SecuBox Security Intelligence - RPCD backend
# Copyright (C) 2026 CyberMind.fr - Gandalf
#
# Combines mitmproxy threat detection, CrowdSec blocking,
# and P2P mesh threat intelligence sharing
. /lib/functions.sh
. /usr/share/libubox/jshn.sh
CSCLI="/usr/bin/cscli"
THREAT_INTEL="/usr/lib/secubox/threat-intel.sh"
# ==============================================================================
# HELPERS
# ==============================================================================
execute_block() {
local ip="$1" duration="$2" reason="$3"
[ -x "$CSCLI" ] || return 1
$CSCLI decisions add --ip "$ip" --duration "$duration" --reason "$reason" >/dev/null 2>&1
}
# ==============================================================================
# SECURITY STATS (firewall counters)
# ==============================================================================
get_security_stats() {
local wan_drops=0 fw_rejects=0 cs_bans=0 cs_alerts_24h=0 haproxy_conns=0 invalid_conns=0
# WAN dropped packets from nftables
if command -v nft >/dev/null 2>&1; then
wan_drops=$(nft list chain inet fw4 input_wan 2>/dev/null | \
grep -oE 'packets [0-9]+' | awk '{sum+=$2} END {print sum+0}')
local reject_drops=$(nft list chain inet fw4 reject_from_wan 2>/dev/null | \
grep -oE 'packets [0-9]+' | awk '{sum+=$2} END {print sum+0}')
[ -n "$reject_drops" ] && wan_drops=$((${wan_drops:-0} + reject_drops))
fi
# Firewall rejects
if command -v nft >/dev/null 2>&1; then
fw_rejects=$(nft list chain inet fw4 handle_reject 2>/dev/null | \
grep -oE 'packets [0-9]+' | awk '{sum+=$2} END {print sum+0}')
if [ "${fw_rejects:-0}" = "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
fi
# CrowdSec bans and alerts
if [ -x "$CSCLI" ]; then
if command -v jq >/dev/null 2>&1; then
cs_bans=$($CSCLI decisions list -o json 2>/dev/null | jq 'length' 2>/dev/null)
cs_alerts_24h=$($CSCLI alerts list -o json --since 24h 2>/dev/null | jq 'length' 2>/dev/null)
else
local cs_json=$($CSCLI decisions list -o json 2>/dev/null)
[ -n "$cs_json" ] && [ "$cs_json" != "null" ] && [ "$cs_json" != "[]" ] && \
cs_bans=$(echo "$cs_json" | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
local alerts_json=$($CSCLI alerts list -o json --since 24h 2>/dev/null)
[ -n "$alerts_json" ] && [ "$alerts_json" != "null" ] && [ "$alerts_json" != "[]" ] && \
cs_alerts_24h=$(echo "$alerts_json" | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
fi
fi
# Invalid connections
if [ -f /proc/net/nf_conntrack ]; then
invalid_conns=$(grep -c "\[INVALID\]" /proc/net/nf_conntrack 2>/dev/null)
fi
# HAProxy connections
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_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' 2>/dev/null | tail -n+2 | awk -F, '{sum+=$5} END {print sum+0}')
fi
# Sanitize to numeric
wan_drops=$(printf '%s' "${wan_drops:-0}" | tr -cd '0-9'); : ${wan_drops:=0}
fw_rejects=$(printf '%s' "${fw_rejects:-0}" | tr -cd '0-9'); : ${fw_rejects:=0}
cs_bans=$(printf '%s' "${cs_bans:-0}" | tr -cd '0-9'); : ${cs_bans:=0}
cs_alerts_24h=$(printf '%s' "${cs_alerts_24h:-0}" | tr -cd '0-9'); : ${cs_alerts_24h:=0}
invalid_conns=$(printf '%s' "${invalid_conns:-0}" | tr -cd '0-9'); : ${invalid_conns:=0}
haproxy_conns=$(printf '%s' "${haproxy_conns:-0}" | tr -cd '0-9'); : ${haproxy_conns:=0}
cat << EOF
{
"wan_dropped": $wan_drops,
"firewall_rejects": $fw_rejects,
"crowdsec_bans": $cs_bans,
"crowdsec_alerts_24h": $cs_alerts_24h,
"invalid_connections": $invalid_conns,
"haproxy_connections": $haproxy_conns
}
EOF
}
# ==============================================================================
# UBUS INTERFACE
# ==============================================================================
case "$1" in
list)
json_init
json_add_object "status"; json_close_object
json_add_object "get_security_stats"; json_close_object
json_add_object "get_active_threats"; json_close_object
json_add_object "get_blocked_ips"; json_close_object
json_add_object "block_threat"
json_add_string "ip" "string"
json_add_string "duration" "string"
json_add_string "reason" "string"
json_close_object
json_add_object "whitelist_host"
json_add_string "ip" "string"
json_add_string "reason" "string"
json_close_object
json_add_object "remove_whitelist"
json_add_string "ip" "string"
json_close_object
json_add_object "get_threat_intel"; json_close_object
json_add_object "get_mesh_iocs"; json_close_object
json_add_object "get_mesh_peers"; json_close_object
json_add_object "publish_intel"; json_close_object
json_add_object "apply_intel"; json_close_object
json_dump
;;
call)
case "$2" in
status)
json_init
json_add_boolean "netifyd_running" $(pgrep netifyd >/dev/null 2>&1 && echo 1 || echo 0)
json_add_boolean "crowdsec_running" $(pgrep crowdsec >/dev/null 2>&1 && echo 1 || echo 0)
json_add_boolean "mitmproxy_running" $(pgrep mitmdump >/dev/null 2>&1 && echo 1 || echo 0)
json_add_boolean "cscli_available" $([ -x "$CSCLI" ] && echo 1 || echo 0)
json_add_boolean "threat_intel_available" $([ -x "$THREAT_INTEL" ] && echo 1 || echo 0)
json_dump
;;
get_security_stats)
get_security_stats
;;
get_active_threats)
_log_file="/srv/mitmproxy/threats.log"
if [ -f "$_log_file" ]; then
_threats=$(tail -50 "$_log_file" 2>/dev/null | jq -sc '
map({
ip: .source_ip,
severity: .severity,
score: (if .severity == "critical" then 90
elif .severity == "high" then 70
elif .severity == "medium" then 50
else 30 end),
type: .type,
pattern: .pattern,
host: .host,
country: (.country // "??"),
timestamp: .timestamp,
request: (.request // "-"),
is_bot: (.is_bot // false),
bot_type: (.bot_type // null),
cve: (.cve // null)
}) | unique_by(.ip) | sort_by(.score) | reverse
' 2>/dev/null || echo "[]")
printf '{"threats":%s}\n' "$_threats"
else
echo '{"threats":[]}'
fi
;;
get_blocked_ips)
if [ -x "$CSCLI" ]; then
_decisions=$($CSCLI decisions list -o json 2>/dev/null || echo "[]")
printf '{"blocked":%s}\n' "$_decisions"
else
echo '{"blocked":[]}'
fi
;;
block_threat)
read -r input
json_load "$input"
json_get_var ip ip
json_get_var duration duration
json_get_var reason reason
duration=${duration:-4h}
reason=${reason:-"Manual block from Security Dashboard"}
if [ -z "$ip" ]; then
json_init
json_add_boolean "success" 0
json_add_string "error" "IP address required"
json_dump
elif execute_block "$ip" "$duration" "$reason"; then
json_init
json_add_boolean "success" 1
json_add_string "message" "IP $ip blocked for $duration"
json_dump
else
json_init
json_add_boolean "success" 0
json_add_string "error" "Failed to block IP"
json_dump
fi
;;
whitelist_host)
read -r input
json_load "$input"
json_get_var ip ip
json_get_var reason reason
reason=${reason:-"Whitelisted from Security Dashboard"}
if [ -z "$ip" ]; then
json_init
json_add_boolean "success" 0
json_add_string "error" "IP address required"
json_dump
else
local section="whitelist_${ip//./_}"
uci set "secubox_security_threats.${section}=whitelist"
uci set "secubox_security_threats.${section}.ip=$ip"
uci set "secubox_security_threats.${section}.reason=$reason"
uci set "secubox_security_threats.${section}.added_at=$(date -Iseconds)"
uci commit secubox_security_threats
json_init
json_add_boolean "success" 1
json_add_string "message" "IP $ip whitelisted"
json_dump
fi
;;
remove_whitelist)
read -r input
json_load "$input"
json_get_var ip ip
if [ -z "$ip" ]; then
json_init
json_add_boolean "success" 0
json_add_string "error" "IP address required"
json_dump
else
local section="whitelist_${ip//./_}"
uci delete "secubox_security_threats.${section}" 2>/dev/null
uci commit secubox_security_threats
json_init
json_add_boolean "success" 1
json_add_string "message" "IP $ip removed from whitelist"
json_dump
fi
;;
get_threat_intel)
if [ -x "$THREAT_INTEL" ]; then
"$THREAT_INTEL" status 2>/dev/null
else
echo '{"enabled":false,"error":"threat-intel not available"}'
fi
;;
get_mesh_iocs)
if [ -x "$THREAT_INTEL" ]; then
_iocs=$("$THREAT_INTEL" list received 2>/dev/null)
printf '{"iocs":%s}\n' "${_iocs:-[]}"
else
echo '{"iocs":[]}'
fi
;;
get_mesh_peers)
if [ -x "$THREAT_INTEL" ]; then
_peers=$("$THREAT_INTEL" peers 2>/dev/null)
printf '{"peers":%s}\n' "${_peers:-[]}"
else
echo '{"peers":[]}'
fi
;;
publish_intel)
if [ -x "$THREAT_INTEL" ]; then
"$THREAT_INTEL" collect-and-publish >/dev/null 2>&1 &
json_init
json_add_boolean "started" 1
json_add_string "message" "Publish started in background"
json_dump
else
echo '{"error":"threat-intel not available"}'
fi
;;
apply_intel)
if [ -x "$THREAT_INTEL" ]; then
"$THREAT_INTEL" apply-pending 2>/dev/null
"$THREAT_INTEL" status 2>/dev/null
else
echo '{"error":"threat-intel not available"}'
fi
;;
*)
json_init
json_add_string "error" "Unknown method: $2"
json_dump
;;
esac
;;
esac