The process name is mitmweb (not mitmdump) when running inside the mitmproxy LXC container. Use pgrep -f mitmweb for correct detection. 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 -f mitmweb >/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
|