secubox-openwrt/package/secubox/secubox-dns-guard/files/usr/bin/dns-guard
CyberMind-FR 0e0749ed08 feat: Add threat-analyst, dns-guard, mcp-server and DNS provider DynDNS
New packages:
- secubox-threat-analyst: AI-powered threat analysis with CrowdSec integration
- luci-app-threat-analyst: LuCI dashboard for threat intelligence
- secubox-dns-guard: DNS security monitoring and blocking
- secubox-mcp-server: Model Context Protocol server for AI assistant integration

Enhancements:
- dns-provider: Add DynDNS support (dyndns, get, update, domains commands)
- gandi.sh: Full DynDNS with WAN IP detection and record updates
- luci-app-dnsguard: Upgrade to v1.1.0 with improved dashboard

Infrastructure:
- BIND9 DNS setup for secubox.in with CAA records
- Wildcard SSL certificates via DNS-01 challenge
- HAProxy config fixes for secubox.in subdomains
- Mail server setup with Roundcube webmail

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

403 lines
11 KiB
Bash

#!/bin/sh
# SecuBox DNS Guard - AI-Powered DNS Anomaly Detection
# Copyright (C) 2026 CyberMind.fr
#
# Monitors DNS queries for anomalies using ML pattern detection
# and LocalAI for intelligent threat analysis
CONFIG="dns-guard"
LIB_DIR="/usr/lib/dns-guard"
STATE_DIR="/var/lib/dns-guard"
BLOCKLIST_DIR="/etc/dns-guard/blocklists"
LOG_TAG="dns-guard"
# Source libraries
. "$LIB_DIR/analyzer.sh"
. "$LIB_DIR/detector.sh"
. "$LIB_DIR/blocklist.sh"
usage() {
cat <<'EOF'
Usage: dns-guard <command> [options]
Commands:
run Run single analysis cycle
daemon Run as background daemon
status Show agent status and statistics
analyze Analyze recent DNS queries (no blocking)
detect Run all detectors on recent queries
Block Management:
list-pending List pending blocks awaiting approval
approve <id> Approve pending block
reject <id> Reject pending block
approve-all Approve all pending blocks
clear-pending Clear all pending blocks
show-blocklist Show current AI-generated blocklist
Query Analysis:
check <domain> Check if a domain is suspicious
stats Show DNS query statistics
top-domains Show top queried domains
top-clients Show top DNS clients
Configuration: /etc/config/dns-guard
EOF
}
log_info() { logger -t "$LOG_TAG" "$*"; echo "[INFO] $*"; }
log_warn() { logger -t "$LOG_TAG" -p warning "$*"; echo "[WARN] $*" >&2; }
log_error() { logger -t "$LOG_TAG" -p err "$*"; echo "[ERROR] $*" >&2; }
uci_get() { uci -q get "${CONFIG}.$1"; }
load_config() {
enabled=$(uci_get main.enabled)
interval=$(uci_get main.interval)
localai_url=$(uci_get main.localai_url)
localai_model=$(uci_get main.localai_model)
auto_apply_blocks=$(uci_get main.auto_apply_blocks)
min_confidence=$(uci_get main.min_confidence)
max_blocks=$(uci_get main.max_blocks_per_cycle)
alert_retention=$(uci_get main.alert_retention)
# Defaults
[ -z "$interval" ] && interval=60
[ -z "$min_confidence" ] && min_confidence=80
[ -z "$max_blocks" ] && max_blocks=10
[ -z "$alert_retention" ] && alert_retention=24
# Ensure state directories exist
mkdir -p "$STATE_DIR"
mkdir -p "$BLOCKLIST_DIR"
}
# =============================================================================
# COMMANDS
# =============================================================================
cmd_status() {
load_config
echo "=== DNS Guard Status ==="
echo ""
echo "Enabled: $([ "$enabled" = "1" ] && echo "Yes" || echo "No")"
echo "Interval: ${interval}s"
echo "LocalAI: $localai_url"
echo "Model: $localai_model"
echo ""
# Check LocalAI availability
if wget -q -O /dev/null --timeout=2 "${localai_url}/v1/models" 2>/dev/null; then
echo "LocalAI Status: ONLINE"
else
echo "LocalAI Status: OFFLINE"
fi
echo ""
echo "Auto-apply blocks: $([ "$auto_apply_blocks" = "1" ] && echo "Yes" || echo "No (queued)")"
echo "Min confidence: ${min_confidence}%"
echo "Max blocks/cycle: $max_blocks"
echo ""
echo "=== Detectors ==="
for detector in dga tunneling rate_anomaly known_bad tld_anomaly; do
local det_enabled=$(uci -q get "dns-guard.${detector}.enabled")
local det_desc=$(uci -q get "dns-guard.${detector}.description")
printf " %-15s %s (%s)\n" "$detector" \
"$([ "$det_enabled" = "1" ] && echo "[ENABLED]" || echo "[DISABLED]")" \
"$det_desc"
done
echo ""
# Count pending blocks
local pending_file="$STATE_DIR/pending_blocks.json"
if [ -f "$pending_file" ]; then
local count=$(jsonfilter -i "$pending_file" -e '@[*]' 2>/dev/null | wc -l)
echo "Pending blocks: $count"
else
echo "Pending blocks: 0"
fi
# Count active blocks
local blocklist_file=$(uci_get target_dnsmasq_blocklist.output_path)
[ -z "$blocklist_file" ] && blocklist_file="/etc/dnsmasq.d/dns-guard-blocklist.conf"
if [ -f "$blocklist_file" ]; then
local active=$(grep -c "^address=" "$blocklist_file" 2>/dev/null || echo "0")
echo "Active blocks: $active"
else
echo "Active blocks: 0"
fi
# Alert count (last 24h)
local alerts_file="$STATE_DIR/alerts.json"
if [ -f "$alerts_file" ]; then
local alert_count=$(jsonfilter -i "$alerts_file" -e '@[*]' 2>/dev/null | wc -l)
echo "Alerts (24h): $alert_count"
else
echo "Alerts (24h): 0"
fi
# Last run
if [ -f "$STATE_DIR/last_run" ]; then
echo ""
echo "Last run: $(cat "$STATE_DIR/last_run")"
fi
}
cmd_run() {
load_config
if [ "$enabled" != "1" ]; then
log_warn "DNS Guard disabled in config"
return 1
fi
log_info "Starting analysis cycle..."
# 1. Collect DNS queries
local queries=$(collect_dns_queries)
local query_count=$(echo "$queries" | wc -l)
log_info "Collected $query_count DNS queries"
if [ "$query_count" -eq 0 ]; then
log_info "No new queries to analyze"
date > "$STATE_DIR/last_run"
return 0
fi
# 2. Run all enabled detectors
local anomalies=$(run_all_detectors "$queries")
local anomaly_count=$(echo "$anomalies" | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
if [ "$anomaly_count" -eq 0 ]; then
log_info "No anomalies detected"
date > "$STATE_DIR/last_run"
return 0
fi
log_info "Detected $anomaly_count anomalies"
# 3. Store alerts
store_alerts "$anomalies"
# 4. Analyze with LocalAI for blocking recommendations
local analysis=$(analyze_anomalies "$anomalies")
if [ -z "$analysis" ]; then
log_warn "AI analysis unavailable, using rule-based blocking only"
# Fall back to rule-based blocking for high-confidence detections
local rule_blocks=$(extract_high_confidence_blocks "$anomalies")
if [ -n "$rule_blocks" ]; then
process_blocks "$rule_blocks"
fi
else
log_info "AI analysis complete"
local ai_blocks=$(extract_ai_recommendations "$analysis")
process_blocks "$ai_blocks"
fi
date > "$STATE_DIR/last_run"
log_info "Analysis cycle complete"
}
process_blocks() {
local blocks="$1"
[ -z "$blocks" ] && return
local block_count=$(echo "$blocks" | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
[ "$block_count" -eq 0 ] && return
# Limit blocks per cycle
if [ "$block_count" -gt "$max_blocks" ]; then
log_info "Limiting blocks to $max_blocks (found $block_count)"
blocks=$(echo "$blocks" | jsonfilter -e "@[0:$max_blocks]")
fi
if [ "$auto_apply_blocks" = "1" ]; then
apply_blocks "$blocks"
log_info "Applied $block_count domain blocks"
else
queue_blocks "$blocks"
log_info "Queued $block_count domain blocks for approval"
fi
}
cmd_daemon() {
load_config
if [ "$enabled" != "1" ]; then
log_error "DNS Guard disabled in config"
exit 1
fi
log_info "Starting daemon (interval: ${interval}s)"
# Cleanup old alerts on startup
cleanup_old_alerts
while true; do
cmd_run
sleep "$interval"
done
}
cmd_analyze() {
load_config
log_info "Analyzing DNS queries (no blocking)..."
local queries=$(collect_dns_queries)
local anomalies=$(run_all_detectors "$queries")
echo "=== DNS Query Analysis ==="
echo ""
echo "Total queries analyzed: $(echo "$queries" | wc -l)"
echo ""
local anomaly_count=$(echo "$anomalies" | jsonfilter -e '@[*]' 2>/dev/null | wc -l)
echo "Anomalies detected: $anomaly_count"
echo ""
if [ "$anomaly_count" -gt 0 ]; then
echo "=== Anomaly Details ==="
echo "$anomalies" | jsonfilter -e '@[*]' 2>/dev/null | while read -r anomaly; do
local domain=$(echo "$anomaly" | jsonfilter -e '@.domain' 2>/dev/null)
local type=$(echo "$anomaly" | jsonfilter -e '@.type' 2>/dev/null)
local confidence=$(echo "$anomaly" | jsonfilter -e '@.confidence' 2>/dev/null)
local reason=$(echo "$anomaly" | jsonfilter -e '@.reason' 2>/dev/null)
printf "[%s%%] %-12s %s\n" "$confidence" "$type" "$domain"
[ -n "$reason" ] && printf " Reason: %s\n" "$reason"
done
echo ""
echo "=== AI Analysis ==="
local analysis=$(analyze_anomalies "$anomalies")
if [ -n "$analysis" ]; then
echo "$analysis"
else
echo "(LocalAI not available)"
fi
fi
}
cmd_detect() {
load_config
local queries=$(collect_dns_queries)
run_all_detectors "$queries"
}
cmd_check() {
local domain="$1"
[ -z "$domain" ] && { echo "Usage: dns-guard check <domain>"; return 1; }
load_config
echo "=== Domain Check: $domain ==="
echo ""
# Run each detector individually
local result=$(check_domain_all_detectors "$domain")
echo "$result"
# AI analysis for single domain
if wget -q -O /dev/null --timeout=2 "${localai_url}/v1/models" 2>/dev/null; then
echo ""
echo "=== AI Analysis ==="
local ai_result=$(analyze_single_domain "$domain")
echo "$ai_result"
fi
}
cmd_stats() {
load_config
show_query_stats
}
cmd_top_domains() {
load_config
local limit="${1:-20}"
show_top_domains "$limit"
}
cmd_top_clients() {
load_config
local limit="${1:-10}"
show_top_clients "$limit"
}
cmd_list_pending() {
local pending_file="$STATE_DIR/pending_blocks.json"
if [ ! -f "$pending_file" ]; then
echo "No pending blocks"
return 0
fi
echo "=== Pending Domain Blocks ==="
echo ""
jsonfilter -i "$pending_file" -e '@[*]' 2>/dev/null | while read -r block; do
local id=$(echo "$block" | jsonfilter -e '@.id' 2>/dev/null)
local domain=$(echo "$block" | jsonfilter -e '@.domain' 2>/dev/null)
local type=$(echo "$block" | jsonfilter -e '@.type' 2>/dev/null)
local confidence=$(echo "$block" | jsonfilter -e '@.confidence' 2>/dev/null)
local created=$(echo "$block" | jsonfilter -e '@.created' 2>/dev/null)
printf "[%s] %s (%s, %s%% confidence) - %s\n" "$id" "$domain" "$type" "$confidence" "$created"
done
}
cmd_approve() {
local block_id="$1"
[ -z "$block_id" ] && { echo "Usage: dns-guard approve <id>"; return 1; }
load_config
approve_pending_block "$block_id"
}
cmd_approve_all() {
load_config
approve_all_pending_blocks
}
cmd_reject() {
local block_id="$1"
[ -z "$block_id" ] && { echo "Usage: dns-guard reject <id>"; return 1; }
reject_pending_block "$block_id"
}
cmd_show_blocklist() {
local blocklist_file=$(uci_get target_dnsmasq_blocklist.output_path)
[ -z "$blocklist_file" ] && blocklist_file="/etc/dnsmasq.d/dns-guard-blocklist.conf"
if [ -f "$blocklist_file" ]; then
echo "=== DNS Guard Blocklist ==="
echo "File: $blocklist_file"
echo ""
cat "$blocklist_file"
else
echo "No blocklist file found"
fi
}
# =============================================================================
# MAIN
# =============================================================================
case "${1:-}" in
run) cmd_run ;;
daemon) cmd_daemon ;;
status) cmd_status ;;
analyze) cmd_analyze ;;
detect) cmd_detect ;;
check) shift; cmd_check "$@" ;;
stats) cmd_stats ;;
top-domains) shift; cmd_top_domains "$@" ;;
top-clients) shift; cmd_top_clients "$@" ;;
list-pending) cmd_list_pending ;;
approve) shift; cmd_approve "$@" ;;
approve-all) cmd_approve_all ;;
reject) shift; cmd_reject "$@" ;;
clear-pending) rm -f "$STATE_DIR/pending_blocks.json"; echo "Cleared" ;;
show-blocklist) cmd_show_blocklist ;;
help|--help|-h|"") usage ;;
*) echo "Unknown: $1" >&2; usage >&2; exit 1 ;;
esac