secubox-openwrt/package/secubox/luci-app-threat-analyst/root/usr/libexec/rpcd/luci.threat-analyst
CyberMind-FR e13b6e4c8c feat(vhost-manager): Add centralized VHost manager
- Create secubox-app-vhost-manager package for unified vhost orchestration
- Single CLI tool (secubox-vhost) manages HAProxy, DNS, Tor, Mesh, mitmproxy
- Unified UCI config (/etc/config/vhosts) as single source of truth
- Backend adapters for each component (haproxy.sh, dns.sh, tor.sh, mesh.sh, mitmproxy.sh)
- Centralized backend resolution function (backends.sh)
- Import tool for existing HAProxy vhosts
- Validation of backend reachability before creation

Also includes:
- FAQ-TROUBLESHOOTING.md with LXC cgroup v1/v2 fixes
- Fix mitmproxyctl cgroup v1 -> v2 syntax for container compatibility
- HAProxy backend resolution bugfixes

CLI commands:
  secubox-vhost add <domain> <service> <port> [--ssl] [--tor] [--mesh]
  secubox-vhost remove/list/status/enable/disable/set/sync/validate/import

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 10:16:19 +01:00

281 lines
7.6 KiB
Bash

#!/bin/sh
# SecuBox Threat Analyst RPCD Handler
. /usr/share/libubox/jshn.sh
CONFIG="threat-analyst"
STATE_DIR="/var/lib/threat-analyst"
LIB_DIR="/usr/lib/threat-analyst"
log_info() { logger -t threat-analyst-rpcd "$*"; }
uci_get() { uci -q get "${CONFIG}.$1"; }
# Source libraries for analysis functions
[ -f "$LIB_DIR/analyzer.sh" ] && {
localai_url=$(uci_get main.localai_url)
localai_model=$(uci_get main.localai_model)
[ -z "$localai_url" ] && localai_url="http://127.0.0.1:8081"
[ -z "$localai_model" ] && localai_model="tinyllama-1.1b-chat-v1.0.Q4_K_M"
. "$LIB_DIR/analyzer.sh"
. "$LIB_DIR/appliers.sh"
}
case "$1" in
list)
cat <<'EOF'
{
"status": {},
"get_threats": {"limit": 50},
"get_alerts": {"limit": 20},
"get_pending": {},
"chat": {"message": "string"},
"analyze": {},
"generate_rules": {"target": "string"},
"approve_rule": {"id": "string"},
"reject_rule": {"id": "string"},
"run_cycle": {}
}
EOF
;;
call)
case "$2" in
status)
# Get agent status
enabled=$(uci_get main.enabled)
interval=$(uci_get main.interval)
last_run=""
[ -f "$STATE_DIR/last_run" ] && last_run=$(cat "$STATE_DIR/last_run")
# Check LocalAI
localai_status="offline"
wget -q -O /dev/null --timeout=2 "${localai_url}/v1/models" 2>/dev/null && localai_status="online"
# Check daemon
daemon_running="false"
pgrep -f "threat-analyst daemon" >/dev/null 2>&1 && daemon_running="true"
# Count pending
pending_count=0
[ -f "$STATE_DIR/pending_rules.json" ] && \
pending_count=$(jsonfilter -i "$STATE_DIR/pending_rules.json" -e '@[*]' 2>/dev/null | wc -l)
# Count recent threats and CVE alerts
threat_count=0
cve_count=0
if command -v cscli >/dev/null 2>&1; then
alerts_json=$(cscli alerts list -o json --since 1h 2>/dev/null)
threat_count=$(echo "$alerts_json" | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
# Count CVE-related alerts
cve_count=$(echo "$alerts_json" | grep -ic 'cve-' 2>/dev/null)
[ -z "$cve_count" ] && cve_count=0
fi
cat <<EOF
{
"enabled": $([ "$enabled" = "1" ] && echo "true" || echo "false"),
"daemon_running": $daemon_running,
"interval": ${interval:-300},
"last_run": "$last_run",
"localai_status": "$localai_status",
"localai_url": "$localai_url",
"pending_rules": $pending_count,
"recent_threats": $threat_count,
"cve_alerts": $cve_count
}
EOF
;;
get_threats)
read -r input
limit=$(echo "$input" | jsonfilter -e '@.limit' 2>/dev/null)
[ -z "$limit" ] && limit=50
# Get CrowdSec alerts
alerts='[]'
if command -v cscli >/dev/null 2>&1; then
alerts=$(cscli alerts list -o json --limit "$limit" 2>/dev/null || echo '[]')
fi
printf '{"threats":%s}' "$alerts"
;;
get_alerts)
read -r input
limit=$(echo "$input" | jsonfilter -e '@.limit' 2>/dev/null)
[ -z "$limit" ] && limit=20
# Get mitmproxy threats
threats='[]'
log_file="/srv/mitmproxy/threats.log"
if [ -f "$log_file" ]; then
# Build JSON array without jq
threats=$({
printf '['
first=1
tail -n "$limit" "$log_file" 2>/dev/null | while IFS= read -r line; do
# Skip empty or invalid JSON
echo "$line" | jsonfilter -e '@' >/dev/null 2>&1 || continue
[ $first -eq 0 ] && printf ','
first=0
printf '%s' "$line"
done
printf ']'
})
fi
printf '{"alerts":%s}' "$threats"
;;
get_pending)
pending='[]'
[ -f "$STATE_DIR/pending_rules.json" ] && pending=$(cat "$STATE_DIR/pending_rules.json")
printf '{"pending":%s}' "$pending"
;;
chat)
read -r input
message=$(echo "$input" | jsonfilter -e '@.message' 2>/dev/null)
if [ -z "$message" ]; then
echo '{"error":"No message provided"}'
exit 0
fi
# Check LocalAI
if ! wget -q -O /dev/null --timeout=2 "${localai_url}/v1/models" 2>/dev/null; then
echo '{"error":"LocalAI not available","suggestion":"Start LocalAI: localaictl start"}'
exit 0
fi
# Get threat context
threat_context=""
if command -v cscli >/dev/null 2>&1; then
threat_context=$(cscli alerts list -o json --limit 10 2>/dev/null | head -c 3000)
fi
# Build prompt
prompt="You are SecuBox Threat Analyst, an AI security assistant for an OpenWrt router.
Current security context:
- Recent CrowdSec alerts: $threat_context
User message: $message
Provide helpful, actionable security advice. If asked about threats, analyze the context. If asked to generate rules, provide specific patterns for mitmproxy/CrowdSec/WAF."
# Call LocalAI
request=$(cat <<AIEOF
{"model":"$localai_model","messages":[{"role":"system","content":"You are SecuBox Threat Analyst AI."},{"role":"user","content":"$(echo "$prompt" | sed 's/"/\\"/g' | tr '\n' ' ')"}],"max_tokens":1024,"temperature":0.5}
AIEOF
)
response=$(echo "$request" | wget -q -O - --post-data=- \
--header="Content-Type: application/json" \
"${localai_url}/v1/chat/completions" 2>/dev/null)
if [ -n "$response" ]; then
content=$(echo "$response" | jsonfilter -e '@.choices[0].message.content' 2>/dev/null)
# Escape for JSON
content=$(echo "$content" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ')
printf '{"response":"%s"}' "$content"
else
echo '{"error":"AI query failed"}'
fi
;;
analyze)
# Run analysis without rule generation
threats=$(collect_threats 2>/dev/null)
analysis=$(analyze_threats "$threats" 2>/dev/null)
if [ -n "$analysis" ]; then
escaped=$(echo "$analysis" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ')
printf '{"analysis":"%s"}' "$escaped"
else
echo '{"error":"Analysis failed"}'
fi
;;
generate_rules)
read -r input
target=$(echo "$input" | jsonfilter -e '@.target' 2>/dev/null)
[ -z "$target" ] && target="all"
# Source generators
[ -f "$LIB_DIR/generators.sh" ] && . "$LIB_DIR/generators.sh"
threats=$(collect_threats 2>/dev/null)
analysis=$(analyze_threats "$threats" 2>/dev/null)
result='{"rules":{'
first=1
if [ "$target" = "all" ] || [ "$target" = "mitmproxy" ]; then
[ $first -eq 0 ] && result="${result},"
first=0
mitm=$(generate_mitmproxy_filters "$analysis" "$threats" 2>/dev/null | base64 -w 0)
result="${result}\"mitmproxy\":\"$mitm\""
fi
if [ "$target" = "all" ] || [ "$target" = "crowdsec" ]; then
[ $first -eq 0 ] && result="${result},"
first=0
cs=$(generate_crowdsec_scenario "$analysis" "$threats" 2>/dev/null | base64 -w 0)
result="${result}\"crowdsec\":\"$cs\""
fi
if [ "$target" = "all" ] || [ "$target" = "waf" ]; then
[ $first -eq 0 ] && result="${result},"
first=0
waf=$(generate_waf_rules "$analysis" "$threats" 2>/dev/null | base64 -w 0)
result="${result}\"waf\":\"$waf\""
fi
result="${result}}}"
echo "$result"
;;
approve_rule)
read -r input
rule_id=$(echo "$input" | jsonfilter -e '@.id' 2>/dev/null)
if [ -z "$rule_id" ]; then
echo '{"error":"No rule ID provided"}'
exit 0
fi
if approve_pending_rule "$rule_id" 2>/dev/null; then
echo '{"success":true}'
else
echo '{"success":false,"error":"Failed to approve rule"}'
fi
;;
reject_rule)
read -r input
rule_id=$(echo "$input" | jsonfilter -e '@.id' 2>/dev/null)
if [ -z "$rule_id" ]; then
echo '{"error":"No rule ID provided"}'
exit 0
fi
reject_pending_rule "$rule_id" 2>/dev/null
echo '{"success":true}'
;;
run_cycle)
# Trigger analysis cycle
/usr/bin/threat-analyst run >/dev/null 2>&1 &
echo '{"started":true}'
;;
*)
echo '{"error":"Unknown method"}'
;;
esac
;;
esac