secubox-openwrt/package/secubox/secubox-network-anomaly/files/usr/bin/network-anomalyctl
CyberMind-FR f2dfb5c144 feat(ai): Add v0.19 AI agent packages
Network Anomaly Agent (secubox-network-anomaly):
- 5 detection modules: bandwidth, connection flood, port scan, DNS, protocol
- EMA-based baseline comparison
- LocalAI integration for threat assessment
- network-anomalyctl CLI

LocalRecall Memory System (secubox-localrecall):
- Persistent memory for AI agents
- Categories: threats, decisions, patterns, configs, conversations
- EMA-based importance scoring
- LocalAI integration for summarization
- localrecallctl CLI with 13 commands

AI Insights Dashboard (luci-app-ai-insights):
- Unified view across all AI agents
- Security posture scoring (0-100)
- Agent status grid with alert counts
- Aggregated alerts from all agents
- Run All Agents and AI Analysis actions

LuCI Dashboards:
- luci-app-network-anomaly with real-time stats
- luci-app-localrecall with memory management

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-05 18:58:08 +01:00

296 lines
6.9 KiB
Bash

#!/bin/sh
# Network Anomaly Detection Controller
# Package: secubox-network-anomaly
VERSION="1.0.0"
CONFIG="network-anomaly"
STATE_DIR="/var/lib/network-anomaly"
LIB_DIR="/usr/lib/network-anomaly"
# Load detection library
. "$LIB_DIR/detector.sh"
usage() {
cat <<EOF
Network Anomaly Detection Controller v$VERSION
Usage: $(basename "$0") <command> [options]
Commands:
status Show detection status
run Run single detection cycle
daemon Start background daemon
analyze Analyze with AI (requires LocalAI)
list-alerts List recent alerts
ack <id> Acknowledge an alert
clear-alerts Clear all alerts
baseline Show/reset baseline
help Show this help
Options:
-q, --quiet Suppress output
-j, --json Output in JSON format
EOF
}
uci_get() {
uci -q get "${CONFIG}.$1"
}
cmd_status() {
local json_mode="$1"
local enabled=$(uci_get main.enabled)
local interval=$(uci_get main.interval)
local auto_block=$(uci_get main.auto_block)
local daemon_running="false"
pgrep -f "network-anomalyctl daemon" >/dev/null 2>&1 && daemon_running="true"
local alert_count=0
[ -f "$ALERTS_FILE" ] && alert_count=$(jsonfilter -i "$ALERTS_FILE" -e '@[*]' 2>/dev/null | wc -l)
local last_run=""
[ -f "$STATE_DIR/last_run" ] && last_run=$(cat "$STATE_DIR/last_run")
local localai_status="offline"
local localai_url=$(uci_get main.localai_url)
[ -z "$localai_url" ] && localai_url="http://127.0.0.1:8091"
wget -q -O /dev/null --timeout=2 "${localai_url}/v1/models" 2>/dev/null && localai_status="online"
if [ "$json_mode" = "1" ]; then
cat <<EOF
{
"enabled": $([ "$enabled" = "1" ] && echo "true" || echo "false"),
"daemon_running": $daemon_running,
"interval": ${interval:-60},
"auto_block": $([ "$auto_block" = "1" ] && echo "true" || echo "false"),
"alert_count": $alert_count,
"last_run": "$last_run",
"localai_status": "$localai_status"
}
EOF
else
echo "Network Anomaly Detection Status"
echo "================================="
echo "Enabled: $([ "$enabled" = "1" ] && echo "Yes" || echo "No")"
echo "Daemon: $([ "$daemon_running" = "true" ] && echo "Running" || echo "Stopped")"
echo "Interval: ${interval:-60}s"
echo "Auto-block: $([ "$auto_block" = "1" ] && echo "Yes" || echo "No")"
echo "Alerts: $alert_count"
echo "Last run: ${last_run:-Never}"
echo "LocalAI: $localai_status"
fi
}
cmd_run() {
local quiet="$1"
init_state
[ "$quiet" != "1" ] && echo "Running detection cycle..."
local alerts_found=$(run_detection)
# Save last run timestamp
date -Iseconds > "$STATE_DIR/last_run"
[ "$quiet" != "1" ] && echo "Detection complete. Alerts found: $alerts_found"
return 0
}
cmd_daemon() {
local interval=$(uci_get main.interval)
[ -z "$interval" ] && interval=60
logger -t network-anomaly "Starting daemon (interval: ${interval}s)"
init_state
while true; do
run_detection >/dev/null 2>&1
date -Iseconds > "$STATE_DIR/last_run"
sleep "$interval"
done
}
cmd_analyze() {
local localai_url=$(uci_get main.localai_url)
local localai_model=$(uci_get main.localai_model)
[ -z "$localai_url" ] && localai_url="http://127.0.0.1:8091"
[ -z "$localai_model" ] && localai_model="tinyllama-1.1b-chat-v1.0.Q4_K_M"
echo "Collecting current network stats..."
local stats=$(collect_stats)
echo "Analyzing with AI..."
# Get recent alerts for context
local recent_alerts=""
[ -f "$ALERTS_FILE" ] && recent_alerts=$(head -c 2000 "$ALERTS_FILE")
local prompt="Analyze this network traffic data for security anomalies. Current stats: $stats. Recent alerts: $recent_alerts. Provide a brief security assessment."
prompt=$(printf '%s' "$prompt" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ')
local response=$(curl -s --max-time 120 -X POST "${localai_url}/v1/chat/completions" \
-H "Content-Type: application/json" \
-d "{\"model\":\"$localai_model\",\"messages\":[{\"role\":\"user\",\"content\":\"$prompt\"}],\"max_tokens\":256,\"temperature\":0.7}" 2>/dev/null)
if [ -n "$response" ]; then
local content=$(echo "$response" | jsonfilter -e '@.choices[0].message.content' 2>/dev/null)
if [ -n "$content" ]; then
echo ""
echo "AI Analysis:"
echo "============"
echo "$content"
else
echo "Error: Empty AI response"
return 1
fi
else
echo "Error: AI query failed. Is LocalAI running?"
return 1
fi
}
cmd_list_alerts() {
local json_mode="$1"
local limit="$2"
[ -z "$limit" ] && limit=20
if [ ! -f "$ALERTS_FILE" ]; then
[ "$json_mode" = "1" ] && echo '{"alerts":[]}' || echo "No alerts"
return 0
fi
if [ "$json_mode" = "1" ]; then
printf '{"alerts":'
cat "$ALERTS_FILE"
printf '}'
else
echo "Recent Alerts"
echo "============="
local count=0
jsonfilter -i "$ALERTS_FILE" -e '@[*]' 2>/dev/null | while read -r alert; do
[ $count -ge $limit ] && break
local id=$(echo "$alert" | jsonfilter -e '@.id' 2>/dev/null)
local type=$(echo "$alert" | jsonfilter -e '@.type' 2>/dev/null)
local severity=$(echo "$alert" | jsonfilter -e '@.severity' 2>/dev/null)
local message=$(echo "$alert" | jsonfilter -e '@.message' 2>/dev/null)
local timestamp=$(echo "$alert" | jsonfilter -e '@.timestamp' 2>/dev/null)
local acked=$(echo "$alert" | jsonfilter -e '@.acknowledged' 2>/dev/null)
printf "[%s] %s (%s) - %s" "$severity" "$type" "${timestamp:-unknown}" "$message"
[ "$acked" = "true" ] && printf " [ACK]"
printf "\n"
count=$((count + 1))
done
fi
}
cmd_ack() {
local alert_id="$1"
if [ -z "$alert_id" ]; then
echo "Error: Alert ID required"
return 1
fi
if [ ! -f "$ALERTS_FILE" ]; then
echo "Error: No alerts file"
return 1
fi
# Mark alert as acknowledged
# For simplicity, we rewrite the file with sed
local tmp_file="$ALERTS_FILE.tmp"
sed "s/\"id\":\"$alert_id\",\\(.*\\)\"acknowledged\":false/\"id\":\"$alert_id\",\\1\"acknowledged\":true/" \
"$ALERTS_FILE" > "$tmp_file"
mv "$tmp_file" "$ALERTS_FILE"
echo "Alert $alert_id acknowledged"
}
cmd_clear_alerts() {
echo '[]' > "$ALERTS_FILE"
echo "All alerts cleared"
}
cmd_baseline() {
local action="$1"
case "$action" in
reset)
rm -f "$BASELINE_FILE"
echo "Baseline reset. New baseline will be established on next run."
;;
*)
if [ -f "$BASELINE_FILE" ]; then
echo "Current Baseline:"
cat "$BASELINE_FILE"
else
echo "No baseline established yet. Run detection to create one."
fi
;;
esac
}
# Parse global options
QUIET=0
JSON=0
while [ $# -gt 0 ]; do
case "$1" in
-q|--quiet)
QUIET=1
shift
;;
-j|--json)
JSON=1
shift
;;
*)
break
;;
esac
done
# Parse command
case "$1" in
status)
cmd_status "$JSON"
;;
run)
cmd_run "$QUIET"
;;
daemon)
cmd_daemon
;;
analyze)
cmd_analyze
;;
list-alerts|alerts)
cmd_list_alerts "$JSON" "$2"
;;
ack|acknowledge)
cmd_ack "$2"
;;
clear-alerts|clear)
cmd_clear_alerts
;;
baseline)
cmd_baseline "$2"
;;
help|--help|-h|"")
usage
;;
*)
echo "Unknown command: $1"
usage
exit 1
;;
esac