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>
319 lines
10 KiB
Bash
Executable File
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
|