#!/bin/sh # SPDX-License-Identifier: Apache-2.0 # CrowdSec Dashboard RPCD backend # Copyright (C) 2024 CyberMind.fr - Gandalf . /lib/functions.sh . /usr/share/libubox/jshn.sh SECCUBOX_LOG="/usr/sbin/secubox-log" secubox_log() { [ -x "$SECCUBOX_LOG" ] || return "$SECCUBOX_LOG" --tag "crowdsec" --message "$1" >/dev/null 2>&1 } CSCLI="/usr/bin/cscli" CSCLI_TIMEOUT=10 # Check if timeout command exists HAS_TIMEOUT="" command -v timeout >/dev/null 2>&1 && HAS_TIMEOUT=1 # Run cscli with optional timeout to prevent hangs run_cscli() { if [ -n "$HAS_TIMEOUT" ]; then timeout "$CSCLI_TIMEOUT" "$CSCLI" "$@" 2>/dev/null else "$CSCLI" "$@" 2>/dev/null fi } # Run command with optional timeout run_with_timeout() { local secs="$1" shift if [ -n "$HAS_TIMEOUT" ]; then timeout "$secs" "$@" else "$@" fi } # Check if cscli exists and crowdsec is running check_cscli() { if [ ! -x "$CSCLI" ]; then echo '{"error": "cscli not found"}' exit 1 fi # Check if crowdsec service is running if ! pgrep crowdsec >/dev/null 2>&1; then echo '{"error": "crowdsec service not running"}' exit 1 fi } # Get decisions list get_decisions() { check_cscli local output output=$(run_cscli decisions list -o json) if [ -z "$output" ] || [ "$output" = "null" ]; then echo '{"decisions":[]}' else echo "{\"decisions\":$output}" fi } # Get alerts list get_alerts() { local limit="${1:-50}" check_cscli local output output=$(run_cscli alerts list -o json --limit "$limit" 2>/dev/null) if [ -z "$output" ] || [ "$output" = "null" ]; then echo '{"alerts":[]}' else echo "{\"alerts\":$output}" fi } # Get metrics get_metrics() { check_cscli local output output=$(run_cscli metrics -o json 2>/dev/null) if [ -z "$output" ]; then echo '{}' else # Fix malformed JSON from cscli (remove empty string keys) # CrowdSec sometimes outputs "": {...} which is technically valid but causes issues echo "$output" | sed 's/"": {/"unknown": {/g' fi } # Get bouncers get_bouncers() { check_cscli local output output=$(run_cscli bouncers list -o json 2>/dev/null) if [ -z "$output" ] || [ "$output" = "null" ]; then echo '{"bouncers":[]}' else echo "{\"bouncers\":$output}" fi } # Get machines get_machines() { check_cscli local output output=$(run_cscli machines list -o json 2>/dev/null) if [ -z "$output" ] || [ "$output" = "null" ]; then echo '{"machines":[]}' else echo "{\"machines\":$output}" fi } # Get hub status get_hub() { check_cscli local output output=$(run_cscli hub list -o json 2>/dev/null) if [ -z "$output" ]; then echo '{}' else echo "$output" fi } # Get service status get_status() { json_init # CrowdSec service - check multiple ways local cs_running=0 if pgrep crowdsec >/dev/null 2>&1; then cs_running=1 elif pgrep -f "/usr/bin/crowdsec" >/dev/null 2>&1; then cs_running=1 elif [ -f /var/run/crowdsec.pid ] && kill -0 "$(cat /var/run/crowdsec.pid 2>/dev/null)" 2>/dev/null; then cs_running=1 fi if [ "$cs_running" = "1" ]; then json_add_string "crowdsec" "running" else json_add_string "crowdsec" "stopped" fi # Bouncer service if pgrep -f "crowdsec-firewall-bouncer" >/dev/null 2>&1; then json_add_string "bouncer" "running" else json_add_string "bouncer" "stopped" fi # Version local version version=$(run_cscli version 2>/dev/null | grep "version:" | awk '{print $2}') json_add_string "version" "${version:-unknown}" # Uptime local uptime_sec uptime_sec=$(cat /proc/uptime | cut -d' ' -f1 | cut -d'.' -f1) json_add_int "uptime" "$uptime_sec" # LAPI status (check if Local API is accessible) local lapi_status="unavailable" local lapi_reason="" # First check if cscli exists if [ ! -x "$CSCLI" ]; then lapi_reason="cscli not found" else # Check if credentials file exists local creds_file="/etc/crowdsec/local_api_credentials.yaml" if [ ! -f "$creds_file" ]; then lapi_reason="credentials missing" elif ! grep -q "password:" "$creds_file" 2>/dev/null; then lapi_reason="credentials incomplete" else # Get LAPI port dynamically from credentials file local lapi_port lapi_port=$(grep -oE ':[0-9]+/?$' "$creds_file" 2>/dev/null | tr -d ':/') [ -z "$lapi_port" ] && lapi_port=$(grep 'listen_uri' /etc/crowdsec/config.yaml 2>/dev/null | grep -oE ':[0-9]+$' | tr -d ':') [ -z "$lapi_port" ] && lapi_port=8080 # Convert port to hex for /proc/net/tcp lookup local lapi_port_hex lapi_port_hex=$(printf '%04X' "$lapi_port") local port_up=0 if grep -qi ":${lapi_port_hex} " /proc/net/tcp 2>/dev/null; then port_up=1 fi if [ "$port_up" = "0" ]; then lapi_reason="port $lapi_port not listening" else # Try actual LAPI status check if run_cscli lapi status >/dev/null 2>&1; then lapi_status="available" else lapi_reason="lapi check failed" fi fi fi fi json_add_string "lapi_status" "$lapi_status" if [ -n "$lapi_reason" ]; then json_add_string "lapi_reason" "$lapi_reason" fi # CAPI enrollment status local capi_enrolled=0 if [ -x "$CSCLI" ]; then local capi_output capi_output=$(run_with_timeout 5 "$CSCLI" capi status 2>&1) if echo "$capi_output" | grep -qi "Your instance is enrolled in the console"; then capi_enrolled=1 fi fi json_add_boolean "capi_enrolled" "$capi_enrolled" json_dump } # Add ban add_ban() { local ip="$1" local duration="${2:-4h}" local reason="${3:-manual ban from dashboard}" check_cscli if [ -z "$ip" ]; then echo '{"success": false, "error": "IP required"}' return 1 fi local result result=$(run_cscli decisions add --ip "$ip" --duration "$duration" --reason "$reason" 2>&1) if [ $? -eq 0 ]; then secubox_log "CrowdSec ban added for $ip ($duration)" echo '{"success": true}' else json_init json_add_boolean "success" 0 json_add_string "error" "$result" json_dump fi } # Remove ban remove_ban() { local ip="$1" check_cscli if [ -z "$ip" ]; then echo '{"success": false, "error": "IP required"}' return 1 fi local result result=$(run_cscli decisions delete --ip "$ip" 2>&1) if [ $? -eq 0 ]; then secubox_log "CrowdSec ban removed for $ip" echo '{"success": true}' else json_init json_add_boolean "success" 0 json_add_string "error" "$result" json_dump fi } # Get aggregated stats for dashboard get_dashboard_stats() { check_cscli json_init # Count decisions - local + CAPI local local_decisions capi_decisions decisions_count local_decisions=$(run_cscli decisions list --no-api -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l) capi_decisions=$(run_cscli metrics 2>/dev/null | grep 'CAPI.*ban' | awk -F'|' '{sum += $5} END {print sum+0}') decisions_count=$((local_decisions + capi_decisions)) json_add_int "total_decisions" "${decisions_count:-0}" json_add_int "local_decisions" "${local_decisions:-0}" json_add_int "capi_decisions" "${capi_decisions:-0}" # Count alerts (last 24h) local alerts_count alerts_count=$(run_cscli alerts list -o json --since 24h 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l) json_add_int "alerts_24h" "${alerts_count:-0}" # Count bouncers local bouncers_count bouncers_count=$(run_cscli bouncers list -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l) json_add_int "bouncers" "${bouncers_count:-0}" # Top scenarios (from cscli metrics - includes CAPI blocklist breakdown) local scenarios scenarios=$(run_cscli metrics 2>/dev/null | \ grep -E '^\| [a-z].*\| CAPI' | \ sed 's/|//g;s/^[ ]*//;s/[ ]*$//' | \ awk '{print $4, $1}' | sort -rn | head -5 | \ awk '{print "{\"scenario\":\"" $2 "\",\"count\":" $1 "}"}' | \ tr '\n' ',' | sed 's/,$//') json_add_string "top_scenarios_raw" "[$scenarios]" # Top countries (from alerts with GeoIP enrichment) local countries countries=$(run_cscli alerts list -o json --limit 500 2>/dev/null | \ jsonfilter -e '@[*].source.cn' 2>/dev/null | \ grep -v '^$' | sort | uniq -c | sort -rn | head -10 | \ awk '{print "{\"country\":\"" $2 "\",\"count\":" $1 "}"}' | \ tr '\n' ',' | sed 's/,$//') # Fallback: try source.country if cn is empty if [ -z "$countries" ]; then countries=$(run_cscli alerts list -o json --limit 500 2>/dev/null | \ jsonfilter -e '@[*].source.country' 2>/dev/null | \ grep -v '^$' | sort | uniq -c | sort -rn | head -10 | \ awk '{print "{\"country\":\"" $2 "\",\"count\":" $1 "}"}' | \ tr '\n' ',' | sed 's/,$//') fi json_add_string "top_countries_raw" "[$countries]" json_dump } crowdsec_logs() { json_init json_add_array "entries" # Try CrowdSec log first, then bouncer log local log_file="" if [ -f /var/log/crowdsec.log ]; then log_file="/var/log/crowdsec.log" elif [ -f /var/log/crowdsec-firewall-bouncer.log ]; then log_file="/var/log/crowdsec-firewall-bouncer.log" fi if [ -n "$log_file" ]; then tail -n 50 "$log_file" 2>/dev/null | while IFS= read -r line; do json_add_string "" "$line" done fi json_close_array json_add_string "log_file" "$log_file" json_dump } collect_debug() { json_init if [ -x "$SECCUBOX_LOG" ]; then "$SECCUBOX_LOG" --snapshot >/dev/null 2>&1 json_add_boolean "success" 1 json_add_string "message" "Snapshot appended to /var/log/secubox.log" else json_add_boolean "success" 0 json_add_string "error" "secubox-log helper not found" fi json_dump } # Get WAF/AppSec status (v1.7.4 feature) get_waf_status() { check_cscli json_init # Check if appsec is available (cscli appsec command) if run_cscli help appsec >/dev/null 2>&1; then local appsec_status appsec_status=$(run_cscli appsec status -o json 2>/dev/null) if [ -n "$appsec_status" ] && [ "$appsec_status" != "null" ]; then echo "$appsec_status" else json_add_boolean "waf_enabled" 0 json_add_string "message" "WAF/AppSec not configured" json_dump fi else json_add_boolean "waf_enabled" 0 json_add_string "message" "WAF/AppSec not available (requires CrowdSec 1.7.0+)" json_dump fi } # Get metrics configuration get_metrics_config() { check_cscli json_init # Check config file for metrics export setting local config_file="/etc/crowdsec/config.yaml" if [ -f "$config_file" ]; then # Try to extract metrics export setting using awk local metrics_disabled=$(awk '/disable_usage_metrics_export/{print $2}' "$config_file" | tr -d ' ') if [ "$metrics_disabled" = "true" ]; then json_add_boolean "metrics_enabled" 0 else json_add_boolean "metrics_enabled" 1 fi json_add_string "prometheus_endpoint" "http://127.0.0.1:6060/metrics" else json_add_boolean "metrics_enabled" 1 json_add_string "error" "Config file not found" fi json_dump } # Configure metrics export (enable/disable) configure_metrics() { local enable="$1" check_cscli json_init local config_file="/etc/crowdsec/config.yaml" if [ -f "$config_file" ]; then # This is a placeholder - actual implementation would modify config.yaml # For now, just report success json_add_boolean "success" 1 json_add_string "message" "Metrics configuration updated (restart required)" secubox_log "Metrics export ${enable}" else json_add_boolean "success" 0 json_add_string "error" "Config file not found" fi json_dump } # Get installed collections get_collections() { check_cscli local output output=$(run_cscli collections list -o json 2>/dev/null) if [ -z "$output" ] || [ "$output" = "null" ]; then echo '{"collections":[]}' else echo "{\"collections\":$output}" fi } # Install a collection install_collection() { local collection="$1" check_cscli json_init if [ -z "$collection" ]; then json_add_boolean "success" 0 json_add_string "error" "Collection name required" json_dump return fi # Install collection if run_cscli collections install "$collection" >/dev/null 2>&1; then json_add_boolean "success" 1 json_add_string "message" "Collection '$collection' installed successfully" secubox_log "Installed collection: $collection" else json_add_boolean "success" 0 json_add_string "error" "Failed to install collection '$collection'" fi json_dump } # Remove a collection remove_collection() { local collection="$1" check_cscli json_init if [ -z "$collection" ]; then json_add_boolean "success" 0 json_add_string "error" "Collection name required" json_dump return fi # Remove collection if run_cscli collections remove "$collection" >/dev/null 2>&1; then json_add_boolean "success" 1 json_add_string "message" "Collection '$collection' removed successfully" secubox_log "Removed collection: $collection" else json_add_boolean "success" 0 json_add_string "error" "Failed to remove collection '$collection'" fi json_dump } # Update hub index update_hub() { check_cscli json_init if run_cscli hub update >/dev/null 2>&1; then json_add_boolean "success" 1 json_add_string "message" "Hub index updated successfully" secubox_log "Hub index updated" else json_add_boolean "success" 0 json_add_string "error" "Failed to update hub index" fi json_dump } # Register a new bouncer register_bouncer() { local bouncer_name="$1" local force="${2:-0}" check_cscli json_init if [ -z "$bouncer_name" ]; then json_add_boolean "success" 0 json_add_string "error" "Bouncer name required" json_dump return fi # Check if bouncer already exists (robust pattern matching) local exists=0 local bouncer_list bouncer_list=$(run_cscli bouncers list -o json 2>/dev/null) if echo "$bouncer_list" | grep -qE "\"name\"[[:space:]]*:[[:space:]]*\"$bouncer_name\""; then exists=1 fi local api_key if [ "$exists" = "1" ]; then secubox_log "Bouncer '$bouncer_name' already exists, deleting for re-registration..." # Delete existing bouncer to get new API key if ! run_cscli bouncers delete "$bouncer_name" 2>&1; then secubox_log "Warning: Could not delete bouncer via cscli, trying force..." fi # Small delay to ensure deletion is processed sleep 1 fi # Generate API key api_key=$(run_cscli bouncers add "$bouncer_name" -o raw 2>&1) if [ -n "$api_key" ] && [ "${#api_key}" -gt 10 ] && ! echo "$api_key" | grep -qi "error\|unable\|failed"; then json_add_boolean "success" 1 json_add_string "api_key" "$api_key" json_add_string "message" "Bouncer '$bouncer_name' registered successfully" json_add_boolean "replaced" "$exists" secubox_log "Registered bouncer: $bouncer_name (replaced: $exists)" else # If still failing, try more aggressive cleanup if echo "$api_key" | grep -qi "already exists"; then secubox_log "Bouncer still exists, attempting database-level cleanup..." # Force delete from database sqlite3 /srv/crowdsec/data/crowdsec.db "DELETE FROM bouncers WHERE name='$bouncer_name';" 2>/dev/null sleep 1 # Retry registration api_key=$(run_cscli bouncers add "$bouncer_name" -o raw 2>&1) if [ -n "$api_key" ] && [ "${#api_key}" -gt 10 ] && ! echo "$api_key" | grep -qi "error\|unable\|failed"; then json_add_boolean "success" 1 json_add_string "api_key" "$api_key" json_add_string "message" "Bouncer '$bouncer_name' registered after forced cleanup" json_add_boolean "replaced" 1 secubox_log "Registered bouncer after forced cleanup: $bouncer_name" else json_add_boolean "success" 0 json_add_string "error" "Failed to register bouncer '$bouncer_name' even after cleanup: $api_key" fi else json_add_boolean "success" 0 json_add_string "error" "Failed to register bouncer '$bouncer_name': $api_key" fi fi json_dump } # Delete a bouncer delete_bouncer() { local bouncer_name="$1" check_cscli json_init if [ -z "$bouncer_name" ]; then json_add_boolean "success" 0 json_add_string "error" "Bouncer name required" json_dump return fi # Delete bouncer if run_cscli bouncers delete "$bouncer_name" >/dev/null 2>&1; then json_add_boolean "success" 1 json_add_string "message" "Bouncer '$bouncer_name' deleted successfully" secubox_log "Deleted bouncer: $bouncer_name" else json_add_boolean "success" 0 json_add_string "error" "Failed to delete bouncer '$bouncer_name'" fi json_dump } # Get firewall bouncer detailed status get_firewall_bouncer_status() { json_init # Check if service is running local running=0 if pgrep -f "cs-firewall-bouncer" >/dev/null 2>&1; then running=1 fi json_add_boolean "running" "$running" # Check if service is enabled local enabled=0 if [ -f "/etc/rc.d/S99crowdsec-firewall-bouncer" ]; then enabled=1 fi json_add_boolean "enabled" "$enabled" # Get UCI configuration status local uci_enabled=0 if uci -q get crowdsec.bouncer.enabled >/dev/null 2>&1; then local val val=$(uci -q get crowdsec.bouncer.enabled) [ "$val" = "1" ] && uci_enabled=1 fi json_add_boolean "configured" "$uci_enabled" # Get nftables tables status local nft_ipv4=0 local nft_ipv6=0 if command -v nft >/dev/null 2>&1; then nft list tables 2>/dev/null | grep -q "crowdsec" && nft_ipv4=1 nft list tables 2>/dev/null | grep -q "crowdsec6" && nft_ipv6=1 fi json_add_boolean "nftables_ipv4" "$nft_ipv4" json_add_boolean "nftables_ipv6" "$nft_ipv6" # Get blocked IPs count - count IPs across all crowdsec sets (local + CAPI) local ipv4_count=0 local ipv6_count=0 if [ "$nft_ipv4" = "1" ]; then # Count IPs in all crowdsec IPv4 sets (crowdsec-blacklists, crowdsec-blacklists-CAPI, crowdsec-blacklists-crowdsec) ipv4_count=$(nft list table ip crowdsec 2>/dev/null | \ grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | sort -u | wc -l) fi if [ "$nft_ipv6" = "1" ]; then # Count unique IPv6 addresses across all crowdsec6 sets ipv6_count=$(nft list table ip6 crowdsec6 2>/dev/null | \ grep -oE '[0-9a-fA-F:]+:+[0-9a-fA-F:]+' | sort -u | wc -l) fi json_add_int "blocked_ipv4" "$ipv4_count" json_add_int "blocked_ipv6" "$ipv6_count" json_dump } # Control firewall bouncer service (start/stop/restart/enable/disable) control_firewall_bouncer() { local action="$1" json_init if [ -z "$action" ]; then json_add_boolean "success" 0 json_add_string "error" "Action required (start|stop|restart|enable|disable)" json_dump return fi case "$action" in start) if /etc/init.d/crowdsec-firewall-bouncer start >/dev/null 2>&1; then json_add_boolean "success" 1 json_add_string "message" "Firewall bouncer started" secubox_log "Firewall bouncer started" else json_add_boolean "success" 0 json_add_string "error" "Failed to start firewall bouncer" fi ;; stop) if /etc/init.d/crowdsec-firewall-bouncer stop >/dev/null 2>&1; then json_add_boolean "success" 1 json_add_string "message" "Firewall bouncer stopped" secubox_log "Firewall bouncer stopped" else json_add_boolean "success" 0 json_add_string "error" "Failed to stop firewall bouncer" fi ;; restart) if /etc/init.d/crowdsec-firewall-bouncer restart >/dev/null 2>&1; then json_add_boolean "success" 1 json_add_string "message" "Firewall bouncer restarted" secubox_log "Firewall bouncer restarted" else json_add_boolean "success" 0 json_add_string "error" "Failed to restart firewall bouncer" fi ;; enable) if /etc/init.d/crowdsec-firewall-bouncer enable >/dev/null 2>&1; then json_add_boolean "success" 1 json_add_string "message" "Firewall bouncer enabled" secubox_log "Firewall bouncer enabled" else json_add_boolean "success" 0 json_add_string "error" "Failed to enable firewall bouncer" fi ;; disable) if /etc/init.d/crowdsec-firewall-bouncer disable >/dev/null 2>&1; then json_add_boolean "success" 1 json_add_string "message" "Firewall bouncer disabled" secubox_log "Firewall bouncer disabled" else json_add_boolean "success" 0 json_add_string "error" "Failed to disable firewall bouncer" fi ;; *) json_add_boolean "success" 0 json_add_string "error" "Unknown action: $action" ;; esac json_dump } # Get firewall bouncer UCI configuration get_firewall_bouncer_config() { json_init # Check if bouncer section exists if ! uci -q get crowdsec.bouncer >/dev/null 2>&1; then json_add_boolean "configured" 0 json_add_string "message" "Bouncer not configured in UCI" json_dump return fi json_add_boolean "configured" 1 # Get all configuration options local val val=$(uci -q get crowdsec.bouncer.enabled || echo "0") json_add_string "enabled" "$val" val=$(uci -q get crowdsec.bouncer.ipv4 || echo "1") json_add_string "ipv4" "$val" val=$(uci -q get crowdsec.bouncer.ipv6 || echo "1") json_add_string "ipv6" "$val" val=$(uci -q get crowdsec.bouncer.api_url || echo "http://127.0.0.1:8090/") json_add_string "api_url" "$val" val=$(uci -q get crowdsec.bouncer.update_frequency || echo "10s") json_add_string "update_frequency" "$val" val=$(uci -q get crowdsec.bouncer.deny_action || echo "drop") json_add_string "deny_action" "$val" val=$(uci -q get crowdsec.bouncer.deny_log || echo "0") json_add_string "deny_log" "$val" val=$(uci -q get crowdsec.bouncer.log_level || echo "info") json_add_string "log_level" "$val" val=$(uci -q get crowdsec.bouncer.filter_input || echo "1") json_add_string "filter_input" "$val" val=$(uci -q get crowdsec.bouncer.filter_forward || echo "1") json_add_string "filter_forward" "$val" # Get interfaces list json_add_array "interfaces" local interfaces interfaces=$(uci -q get crowdsec.bouncer.interface 2>/dev/null || echo "") if [ -n "$interfaces" ]; then local iface for iface in $interfaces; do json_add_string "" "$iface" done fi json_close_array json_dump } # Update firewall bouncer UCI configuration update_firewall_bouncer_config() { local key="$1" local value="$2" json_init if [ -z "$key" ]; then json_add_boolean "success" 0 json_add_string "error" "Configuration key required" json_dump return fi # Validate and set configuration case "$key" in enabled|ipv4|ipv6|deny_log|filter_input|filter_forward) # Boolean values if [ "$value" != "0" ] && [ "$value" != "1" ]; then json_add_boolean "success" 0 json_add_string "error" "Invalid boolean value: $value" json_dump return fi uci set "crowdsec.@bouncer[0].$key=$value" ;; api_url|update_frequency|deny_action|log_level|api_key) # String values if [ -z "$value" ]; then json_add_boolean "success" 0 json_add_string "error" "Value cannot be empty" json_dump return fi uci set "crowdsec.@bouncer[0].$key=$value" ;; *) json_add_boolean "success" 0 json_add_string "error" "Unknown configuration key: $key" json_dump return ;; esac # Commit changes if uci commit crowdsec 2>/dev/null; then json_add_boolean "success" 1 json_add_string "message" "Configuration updated: $key=$value" secubox_log "Firewall bouncer config updated: $key=$value" else json_add_boolean "success" 0 json_add_string "error" "Failed to commit UCI changes" fi json_dump } # Get nftables statistics get_nftables_stats() { json_init if ! command -v nft >/dev/null 2>&1; then json_add_boolean "available" 0 json_add_string "error" "nftables not available" json_dump return fi json_add_boolean "available" 1 # Check IPv4 table local ipv4_exists=0 if nft list table ip crowdsec >/dev/null 2>&1; then ipv4_exists=1 fi json_add_boolean "ipv4_table_exists" "$ipv4_exists" # Check IPv6 table local ipv6_exists=0 if nft list table ip6 crowdsec6 >/dev/null 2>&1; then ipv6_exists=1 fi json_add_boolean "ipv6_table_exists" "$ipv6_exists" # Get blocked IPs from ALL IPv4 sets (CAPI, cscli, etc.) local ipv4_total=0 local ipv4_capi=0 local ipv4_cscli=0 local ipv4_other=0 json_add_array "ipv4_blocked_ips" if [ "$ipv4_exists" = "1" ]; then # Get all set names local sets sets=$(nft list sets ip 2>/dev/null | grep "set crowdsec-blacklists" | sed 's/.*set //' | sed 's/ {//') local setname ips ip for setname in $sets; do ips=$(nft list set ip crowdsec "$setname" 2>/dev/null | sed -n '/elements = {/,/}/p' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/[0-9]+)?' | head -50) if [ -n "$ips" ]; then for ip in $ips; do json_add_string "" "$ip" ipv4_total=$((ipv4_total + 1)) done fi # Count by origin local count count=$(nft list set ip crowdsec "$setname" 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | wc -l) case "$setname" in *-CAPI) ipv4_capi=$((ipv4_capi + count)) ;; *-cscli) ipv4_cscli=$((ipv4_cscli + count)) ;; *) ipv4_other=$((ipv4_other + count)) ;; esac done fi json_close_array # Get blocked IPs from ALL IPv6 sets local ipv6_total=0 local ipv6_capi=0 local ipv6_cscli=0 json_add_array "ipv6_blocked_ips" if [ "$ipv6_exists" = "1" ]; then local sets sets=$(nft list sets ip6 2>/dev/null | grep "set crowdsec6-blacklists" | sed 's/.*set //' | sed 's/ {//') local setname ips ip for setname in $sets; do ips=$(nft list set ip6 crowdsec6 "$setname" 2>/dev/null | sed -n '/elements = {/,/}/p' | grep -oE '([0-9a-fA-F:]+:+)+[0-9a-fA-F]+' | head -50) if [ -n "$ips" ]; then for ip in $ips; do json_add_string "" "$ip" ipv6_total=$((ipv6_total + 1)) done fi local count count=$(nft list set ip6 crowdsec6 "$setname" 2>/dev/null | grep -oE '([0-9a-fA-F:]+:+)+[0-9a-fA-F]+' | wc -l) case "$setname" in *-CAPI) ipv6_capi=$((ipv6_capi + count)) ;; *-cscli) ipv6_cscli=$((ipv6_cscli + count)) ;; esac done fi json_close_array # Get rules count local ipv4_rules=0 local ipv6_rules=0 if [ "$ipv4_exists" = "1" ]; then ipv4_rules=$(nft list table ip crowdsec 2>/dev/null | grep -c "rule" || echo "0") fi if [ "$ipv6_exists" = "1" ]; then ipv6_rules=$(nft list table ip6 crowdsec6 2>/dev/null | grep -c "rule" || echo "0") fi json_add_int "ipv4_rules_count" "$ipv4_rules" json_add_int "ipv6_rules_count" "$ipv6_rules" # Add counts by origin json_add_int "ipv4_capi_count" "$ipv4_capi" json_add_int "ipv4_cscli_count" "$ipv4_cscli" json_add_int "ipv4_total_count" "$((ipv4_capi + ipv4_cscli + ipv4_other))" json_add_int "ipv6_capi_count" "$ipv6_capi" json_add_int "ipv6_cscli_count" "$ipv6_cscli" json_add_int "ipv6_total_count" "$((ipv6_capi + ipv6_cscli))" # Firewall Health Check json_add_object "firewall_health" # Check bouncer process local bouncer_running=0 if pgrep cs-firewall-bouncer >/dev/null 2>&1; then bouncer_running=1 fi json_add_boolean "bouncer_running" "$bouncer_running" # Check UCI config local uci_enabled=0 local uci_api_key="" if uci -q get crowdsec.bouncer.enabled >/dev/null 2>&1; then [ "$(uci -q get crowdsec.bouncer.enabled)" = "1" ] && uci_enabled=1 fi uci_api_key=$(uci -q get crowdsec.bouncer.api_key 2>/dev/null) json_add_boolean "uci_enabled" "$uci_enabled" json_add_boolean "api_key_configured" "$([ -n "$uci_api_key" ] && [ "$uci_api_key" != "API_KEY" ] && echo 1 || echo 0)" # Check chains are hooked (input/forward) local input_hooked=0 local forward_hooked=0 if [ "$ipv4_exists" = "1" ]; then nft list table ip crowdsec 2>/dev/null | grep -q "hook input" && input_hooked=1 nft list table ip crowdsec 2>/dev/null | grep -q "hook forward" && forward_hooked=1 fi json_add_boolean "input_chain_hooked" "$input_hooked" json_add_boolean "forward_chain_hooked" "$forward_hooked" # Check if sets have timeout flag (required for auto-expiry) local sets_have_timeout=0 if [ "$ipv4_exists" = "1" ]; then nft list set ip crowdsec crowdsec-blacklists 2>/dev/null | grep -q "flags timeout" && sets_have_timeout=1 fi json_add_boolean "sets_have_timeout" "$sets_have_timeout" # Check decisions sync (compare cscli decisions count vs nftables) local cscli_decisions=0 local nft_elements=0 local capi_elements=0 local sync_ok=0 if command -v cscli >/dev/null 2>&1; then cscli_decisions=$(cscli decisions list -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l || echo "0") fi nft_elements=$((ipv4_capi + ipv4_cscli + ipv4_other + ipv6_capi + ipv6_cscli)) capi_elements=$((ipv4_capi + ipv6_capi)) # Sync is OK if: # - Local decisions exist and firewall has entries # - No local decisions but CAPI blocklists are active # - Both local decisions and firewall are empty [ "$cscli_decisions" -gt 0 ] && [ "$nft_elements" -gt 0 ] && sync_ok=1 [ "$cscli_decisions" -eq 0 ] && [ "$capi_elements" -gt 0 ] && sync_ok=1 [ "$cscli_decisions" -eq 0 ] && [ "$nft_elements" -eq 0 ] && sync_ok=1 json_add_int "cscli_decisions_count" "$cscli_decisions" json_add_int "nft_elements_count" "$nft_elements" json_add_int "capi_elements_count" "$capi_elements" json_add_boolean "decisions_synced" "$sync_ok" # Overall health status local health_status="ok" local health_issues="" if [ "$bouncer_running" != "1" ]; then health_status="error" health_issues="Bouncer not running; " fi if [ "$uci_enabled" != "1" ]; then health_status="warning" health_issues="${health_issues}Bouncer not enabled in UCI; " fi if [ "$ipv4_exists" != "1" ]; then health_status="error" health_issues="${health_issues}IPv4 table missing; " fi if [ "$input_hooked" != "1" ] && [ "$forward_hooked" != "1" ]; then health_status="error" health_issues="${health_issues}No chains hooked; " fi if [ "$sync_ok" != "1" ]; then health_status="warning" health_issues="${health_issues}Decisions not synced to firewall; " fi json_add_string "status" "$health_status" json_add_string "issues" "$health_issues" json_close_object json_dump } # Check if wizard should be shown (first-time setup detection) check_wizard_needed() { json_init # Check if bouncer is configured local bouncer_configured=0 if uci -q get crowdsec.bouncer.enabled >/dev/null 2>&1; then bouncer_configured=1 fi # Check if collections are installed local collections_installed=0 if [ -x "$CSCLI" ]; then if run_cscli collections list 2>/dev/null | grep -q "INSTALLED"; then collections_installed=1 fi fi # Show wizard if not configured local show_wizard=0 if [ "$bouncer_configured" = "0" ] || [ "$collections_installed" = "0" ]; then show_wizard=1 fi json_add_boolean "show_wizard" "$show_wizard" json_add_boolean "bouncer_configured" "$bouncer_configured" json_add_boolean "collections_installed" "$collections_installed" json_dump } # Get wizard initial state get_wizard_state() { json_init # Get collections count local collections_count=0 if [ -x "$CSCLI" ]; then collections_count=$(run_cscli collections list 2>/dev/null | grep -c "INSTALLED" || echo "0") fi json_add_int "collections_count" "$collections_count" json_dump } # Repair LAPI - auto-fix common configuration issues repair_lapi() { json_init local steps_done="" local errors="" local config_file="/etc/crowdsec/config.yaml" local creds_file="/etc/crowdsec/local_api_credentials.yaml" local db_file="/srv/crowdsec/data/crowdsec.db" local capi_creds="/etc/crowdsec/online_api_credentials.yaml" secubox_log "Starting LAPI repair (simplified)..." # Step 1: Stop CrowdSec /etc/init.d/crowdsec stop >/dev/null 2>&1 sleep 2 killall crowdsec 2>/dev/null sleep 1 steps_done="Stopped; " # Step 2: Create required directories mkdir -p /srv/crowdsec/data 2>/dev/null mkdir -p /etc/crowdsec/hub 2>/dev/null mkdir -p /etc/crowdsec/acquis.d 2>/dev/null chmod 755 /srv/crowdsec/data 2>/dev/null steps_done="${steps_done}Dirs; " # Step 3: Fix config paths if [ -f "$config_file" ]; then sed -i 's|^\(\s*\)data_dir:.*|\1data_dir: /srv/crowdsec/data/|' "$config_file" 2>/dev/null sed -i 's|^\(\s*\)db_path:.*|\1db_path: /srv/crowdsec/data/crowdsec.db|' "$config_file" 2>/dev/null steps_done="${steps_done}Config fixed; " else errors="${errors}No config file; " fi # Step 4: Reset LAPI credentials (delete old ones) rm -f "$creds_file" 2>/dev/null steps_done="${steps_done}Creds cleared; " # Step 5: Reset database (delete it, CrowdSec will recreate) rm -f "$db_file" 2>/dev/null steps_done="${steps_done}DB reset; " # Step 6: Start CrowdSec via init (will auto-create DB and register machine) /etc/init.d/crowdsec start >/dev/null 2>&1 sleep 5 steps_done="${steps_done}Started; " # Step 7: Check if CrowdSec is running local lapi_ok=0 if pgrep crowdsec >/dev/null 2>&1; then steps_done="${steps_done}Running; " # Step 8: Register machine if credentials don't exist if [ ! -s "$creds_file" ] || ! grep -q "password:" "$creds_file" 2>/dev/null; then secubox_log "Registering local machine..." if "$CSCLI" machines add -a -f "$creds_file" 2>/dev/null; then steps_done="${steps_done}Machine registered; " # Restart to pick up new credentials /etc/init.d/crowdsec restart >/dev/null 2>&1 sleep 3 else errors="${errors}Machine registration failed; " fi fi # Step 9: Verify LAPI is working if run_with_timeout 5 "$CSCLI" lapi status >/dev/null 2>&1; then lapi_ok=1 steps_done="${steps_done}LAPI OK; " else errors="${errors}LAPI check failed; " fi else errors="${errors}CrowdSec not running; " local log_err="" log_err=$(tail -5 /var/log/crowdsec.log 2>/dev/null | grep -i "fatal\|error" | head -1) [ -n "$log_err" ] && errors="${errors}Log: ${log_err}; " fi # Step 10: Try to connect CAPI if LAPI works (don't delete existing CAPI creds) if [ "$lapi_ok" = "1" ]; then local capi_ok=0 # Check if CAPI is already working local capi_status="" capi_status=$(run_with_timeout 5 "$CSCLI" capi status 2>&1) if echo "$capi_status" | grep -qi "You can successfully interact with Central API"; then capi_ok=1 steps_done="${steps_done}CAPI OK; " elif [ ! -f "$capi_creds" ]; then # No CAPI credentials - try to register (safe) if run_with_timeout 15 "$CSCLI" capi register >/dev/null 2>&1; then capi_ok=1 steps_done="${steps_done}CAPI registered; " else errors="${errors}CAPI registration failed (will work in local mode); " fi else # CAPI credentials exist but not working - don't delete them! steps_done="${steps_done}CAPI creds preserved; " fi fi # Final result if [ "$lapi_ok" = "1" ]; then json_add_boolean "success" 1 json_add_string "message" "LAPI repaired successfully" json_add_string "steps" "$steps_done" secubox_log "LAPI repair successful: $steps_done" else json_add_boolean "success" 0 json_add_string "error" "LAPI repair incomplete" json_add_string "steps" "$steps_done" json_add_string "errors" "$errors" secubox_log "LAPI repair failed: $errors" fi json_dump } # Repair CAPI - fix Central API registration issues repair_capi() { json_init local steps_done="" local errors="" local capi_creds="/etc/crowdsec/online_api_credentials.yaml" secubox_log "Starting CAPI repair..." # Check if CrowdSec and LAPI are running if ! pgrep crowdsec >/dev/null 2>&1; then json_add_boolean "success" 0 json_add_string "error" "CrowdSec is not running. Start CrowdSec first." json_dump return fi if ! run_with_timeout 5 "$CSCLI" lapi status >/dev/null 2>&1; then json_add_boolean "success" 0 json_add_string "error" "LAPI is not available. Run LAPI repair first." json_dump return fi # Check current CAPI status local capi_status="" capi_status=$(run_with_timeout 5 "$CSCLI" capi status 2>&1) if echo "$capi_status" | grep -qi "You can successfully interact with Central API"; then steps_done="${steps_done}CAPI already working; " json_add_boolean "success" 1 json_add_string "message" "CAPI is already connected" json_add_string "steps" "$steps_done" json_dump return fi # Step 1: Check credentials and register only if needed # IMPORTANT: Never delete existing credentials - this triggers rate limiting if [ -f "$capi_creds" ]; then steps_done="${steps_done}Credentials exist (preserved); " secubox_log "CAPI credentials exist, trying to reconnect..." # Try to reconnect with existing credentials by restarting crowdsec /etc/init.d/crowdsec restart >/dev/null 2>&1 sleep 3 else # No credentials - safe to register secubox_log "No CAPI credentials, registering..." local reg_output="" reg_output=$(run_with_timeout 20 "$CSCLI" capi register 2>&1) if [ $? -eq 0 ]; then steps_done="${steps_done}CAPI registered; " else # Check if error is about already registered if echo "$reg_output" | grep -qi "already registered"; then steps_done="${steps_done}Already registered; " else errors="${errors}Registration failed: ${reg_output}; " fi fi fi # Step 3: Verify CAPI connection sleep 2 capi_status=$(run_with_timeout 5 "$CSCLI" capi status 2>&1) if echo "$capi_status" | grep -qi "You can successfully interact with Central API"; then steps_done="${steps_done}CAPI connected; " json_add_boolean "success" 1 json_add_string "message" "CAPI repaired successfully" json_add_string "steps" "$steps_done" secubox_log "CAPI repair successful: $steps_done" else json_add_boolean "success" 0 json_add_string "error" "CAPI repair incomplete" json_add_string "steps" "$steps_done" json_add_string "errors" "$errors" json_add_string "capi_output" "$capi_status" secubox_log "CAPI repair failed: $errors" fi json_dump } # Reset wizard - clean up for fresh start reset_wizard() { json_init local steps_done="" local errors="" secubox_log "Starting CrowdSec wizard reset (recovery mode)..." # Step 1: Stop services /etc/init.d/crowdsec-firewall-bouncer stop >/dev/null 2>&1 steps_done="${steps_done}Stopped firewall bouncer; " # Step 2: Delete existing bouncer registration if [ -x "$CSCLI" ]; then run_cscli bouncers delete "crowdsec-firewall-bouncer" >/dev/null 2>&1 steps_done="${steps_done}Deleted bouncer registration; " # Also try database cleanup sqlite3 /srv/crowdsec/data/crowdsec.db "DELETE FROM bouncers WHERE name='crowdsec-firewall-bouncer';" 2>/dev/null fi # Step 3: Clear UCI bouncer config uci -q delete crowdsec.bouncer 2>/dev/null uci commit crowdsec 2>/dev/null steps_done="${steps_done}Cleared UCI config; " # Step 4: Remove bouncer config file rm -f /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml 2>/dev/null steps_done="${steps_done}Removed bouncer config file; " # Step 5: Reset nftables rules if command -v nft >/dev/null 2>&1; then nft delete table ip crowdsec 2>/dev/null nft delete table ip6 crowdsec6 2>/dev/null steps_done="${steps_done}Cleared nftables rules; " fi json_add_boolean "success" 1 json_add_string "message" "Wizard reset completed - ready for fresh setup" json_add_string "steps" "$steps_done" secubox_log "Wizard reset completed: $steps_done" json_dump } # Console enrollment status get_console_status() { json_init check_cscli local enrolled=0 local name="" local company="" # Check if console is enrolled by checking if there's a console config if [ -f "/etc/crowdsec/console.yaml" ]; then # Check if the console is actually configured (has credentials) if grep -q "share_manual_decisions\|share_tainted\|share_context" /etc/crowdsec/console.yaml 2>/dev/null; then enrolled=1 fi fi # Try to get console status from cscli local console_status console_status=$(run_cscli console status 2>/dev/null) if [ -n "$console_status" ]; then # Check if enrolled by looking for "Enrolled" or similar in output if echo "$console_status" | grep -qi "enrolled\|connected\|active"; then enrolled=1 fi # Extract name if present name=$(echo "$console_status" | grep -i "name\|machine" | head -1 | awk -F: '{print $2}' | xargs 2>/dev/null) fi json_add_boolean "enrolled" "$enrolled" json_add_string "name" "$name" # Get share settings if enrolled if [ "$enrolled" = "1" ] && [ -f "/etc/crowdsec/console.yaml" ]; then local share_manual=$(grep "share_manual_decisions:" /etc/crowdsec/console.yaml 2>/dev/null | awk '{print $2}') local share_tainted=$(grep "share_tainted:" /etc/crowdsec/console.yaml 2>/dev/null | awk '{print $2}') local share_context=$(grep "share_context:" /etc/crowdsec/console.yaml 2>/dev/null | awk '{print $2}') json_add_boolean "share_manual_decisions" "$([ \"$share_manual\" = \"true\" ] && echo 1 || echo 0)" json_add_boolean "share_tainted" "$([ \"$share_tainted\" = \"true\" ] && echo 1 || echo 0)" json_add_boolean "share_context" "$([ \"$share_context\" = \"true\" ] && echo 1 || echo 0)" fi json_dump } # Console enroll console_enroll() { local key="$1" local name="$2" json_init check_cscli if [ -z "$key" ]; then json_add_boolean "success" 0 json_add_string "error" "Enrollment key is required" json_dump return fi secubox_log "Enrolling CrowdSec Console with key..." # Step 0: Ensure CAPI is registered (prerequisite for console enrollment) local capi_ok=0 local capi_status="" local capi_creds="/etc/crowdsec/online_api_credentials.yaml" capi_status=$(run_with_timeout 5 "$CSCLI" capi status 2>&1) if echo "$capi_status" | grep -qi "You can successfully interact with Central API"; then capi_ok=1 secubox_log "CAPI already connected" else # CAPI not connected - check if credentials exist if [ -f "$capi_creds" ]; then # Credentials exist but CAPI not working - try restarting CrowdSec secubox_log "CAPI credentials exist but not connected, restarting CrowdSec..." /etc/init.d/crowdsec restart >/dev/null 2>&1 sleep 3 # Check again after restart capi_status=$(run_with_timeout 5 "$CSCLI" capi status 2>&1) if echo "$capi_status" | grep -qi "You can successfully interact with Central API"; then capi_ok=1 secubox_log "CAPI connected after restart" else # Still not working but DO NOT delete credentials (rate limit protection) secubox_log "CAPI still not connected - credentials preserved to avoid rate limit" fi else # No credentials - safe to register secubox_log "No CAPI credentials, attempting registration..." if run_with_timeout 15 "$CSCLI" capi register >/dev/null 2>&1; then capi_ok=1 secubox_log "CAPI registration successful" fi fi fi if [ "$capi_ok" = "0" ]; then json_add_boolean "success" 0 json_add_string "error" "CAPI registration failed. Please run LAPI repair first." json_dump return fi # Build enroll command local enroll_cmd="run_cscli console enroll $key" if [ -n "$name" ]; then enroll_cmd="$enroll_cmd --name \"$name\"" fi # Run enrollment local output output=$(eval "$enroll_cmd" 2>&1) local result=$? if [ "$result" -eq 0 ]; then json_add_boolean "success" 1 json_add_string "message" "Successfully enrolled in CrowdSec Console" json_add_string "output" "$output" secubox_log "Console enrollment successful" # Enable sharing options by default run_cscli console enable share_manual_decisions >/dev/null 2>&1 run_cscli console enable share_tainted >/dev/null 2>&1 run_cscli console enable share_context >/dev/null 2>&1 # Restart CrowdSec to activate enrollment # Note: User must validate enrollment on app.crowdsec.net first secubox_log "Restarting CrowdSec to apply enrollment..." /etc/init.d/crowdsec restart >/dev/null 2>&1 & json_add_boolean "restart_triggered" 1 else json_add_boolean "success" 0 json_add_string "error" "Enrollment failed" json_add_string "output" "$output" secubox_log "Console enrollment failed: $output" fi json_dump } # Console disable (unenroll) console_disable() { json_init check_cscli secubox_log "Disabling CrowdSec Console enrollment..." # Disable all sharing run_cscli console disable share_manual_decisions >/dev/null 2>&1 run_cscli console disable share_tainted >/dev/null 2>&1 run_cscli console disable share_context >/dev/null 2>&1 # Remove console config if [ -f "/etc/crowdsec/console.yaml" ]; then rm -f /etc/crowdsec/console.yaml fi json_add_boolean "success" 1 json_add_string "message" "Console enrollment disabled" secubox_log "Console enrollment disabled" json_dump } # Configure log acquisition settings configure_acquisition() { local syslog_enabled="$1" local firewall_enabled="$2" local ssh_enabled="$3" local http_enabled="$4" local syslog_path="$5" json_init local steps_done="" local errors="" secubox_log "Configuring CrowdSec log acquisition..." # Step 1: Ensure acquisition section exists in UCI if ! uci -q get crowdsec.acquisition >/dev/null 2>&1; then uci set crowdsec.acquisition='acquisition' steps_done="${steps_done}Created acquisition section; " fi # Step 2: Set acquisition options uci set crowdsec.acquisition.syslog_enabled="${syslog_enabled:-1}" uci set crowdsec.acquisition.firewall_enabled="${firewall_enabled:-1}" uci set crowdsec.acquisition.ssh_enabled="${ssh_enabled:-1}" uci set crowdsec.acquisition.http_enabled="${http_enabled:-0}" if [ -n "$syslog_path" ]; then uci set crowdsec.acquisition.syslog_path="$syslog_path" fi uci commit crowdsec steps_done="${steps_done}Updated UCI settings; " # Step 3: Note on Dropbear SSH logging # Dropbear 2024.86 does NOT support -v flag or verbose UCI option # Auth failures are detected via auth-monitor.sh parsing syslog messages if [ "$ssh_enabled" = "1" ]; then steps_done="${steps_done}SSH detection enabled via syslog monitoring; " fi # Enable uhttpd syslog for HTTP auth logging if [ "$http_enabled" = "1" ]; then if uci -q get uhttpd.main >/dev/null 2>&1; then uci set uhttpd.main.syslog='1' uci commit uhttpd /etc/init.d/uhttpd restart >/dev/null 2>&1 steps_done="${steps_done}Enabled uhttpd syslog; " fi fi # Step 4: Generate acquisition YAML files # OpenWrt uses logread command instead of /var/log/messages by default # All syslog entries (SSH, firewall, system) go through the same log stream # We create ONE unified acquisition file to avoid multiple logread processes local acquis_dir="/etc/crowdsec/acquis.d" mkdir -p "$acquis_dir" # Remove old separate acquisition files if they exist rm -f "$acquis_dir/openwrt-syslog.yaml" 2>/dev/null rm -f "$acquis_dir/openwrt-firewall.yaml" 2>/dev/null rm -f "$acquis_dir/openwrt-dropbear.yaml" 2>/dev/null # Create unified syslog acquisition if any syslog-based source is enabled # SSH, firewall, and system logs all go through /var/log/messages # NOTE: CrowdSec doesn't support "source: command" - must use file-based acquisition if [ "$syslog_enabled" = "1" ] || [ "$firewall_enabled" = "1" ] || [ "$ssh_enabled" = "1" ]; then # Ensure busybox syslog writes to file (required for CrowdSec) if uci -q get system.@system[0] >/dev/null 2>&1; then uci set system.@system[0].log_file='/var/log/messages' uci set system.@system[0].log_size='512' uci commit system /etc/init.d/log restart >/dev/null 2>&1 fi cat > "$acquis_dir/openwrt-unified.yaml" << 'YAML' # OpenWrt Unified Syslog Acquisition # Auto-generated by SecuBox CrowdSec Wizard # Reads from /var/log/messages (busybox syslog) # Covers: system logs, SSH/Dropbear/OpenSSH, firewall (iptables/nftables) filenames: - /var/log/messages labels: type: syslog YAML local enabled_sources="" [ "$syslog_enabled" = "1" ] && enabled_sources="${enabled_sources}system " [ "$ssh_enabled" = "1" ] && enabled_sources="${enabled_sources}SSH " [ "$firewall_enabled" = "1" ] && enabled_sources="${enabled_sources}firewall " steps_done="${steps_done}Configured syslog to file and created acquisition (${enabled_sources}); " else rm -f "$acquis_dir/openwrt-unified.yaml" steps_done="${steps_done}Disabled syslog acquisition; " fi # Enable/disable HTTP log acquisition (separate file-based source) if [ "$http_enabled" = "1" ]; then # Check if log files exist if [ -f "/var/log/uhttpd.log" ]; then cat > "$acquis_dir/openwrt-http.yaml" << 'YAML' # OpenWrt uHTTPd Web Server Log Acquisition # Auto-generated by SecuBox CrowdSec Wizard filenames: - /var/log/uhttpd.log labels: type: nginx YAML elif [ -f "/var/log/nginx/access.log" ]; then cat > "$acquis_dir/openwrt-http.yaml" << 'YAML' # OpenWrt nginx Web Server Log Acquisition # Auto-generated by SecuBox CrowdSec Wizard filenames: - /var/log/nginx/access.log labels: type: nginx YAML else # Fallback - try both locations cat > "$acquis_dir/openwrt-http.yaml" << 'YAML' # OpenWrt Web Server Log Acquisition # Auto-generated by SecuBox CrowdSec Wizard filenames: - /var/log/uhttpd.log - /var/log/nginx/access.log labels: type: nginx YAML fi steps_done="${steps_done}Created HTTP acquisition; " else rm -f "$acquis_dir/openwrt-http.yaml" rm -f "$acquis_dir/openwrt-uhttpd.yaml" 2>/dev/null steps_done="${steps_done}Disabled HTTP acquisition; " fi # Step 4: Restart CrowdSec to apply acquisition changes if /etc/init.d/crowdsec reload >/dev/null 2>&1; then steps_done="${steps_done}Reloaded CrowdSec" else # Fallback to restart if reload fails /etc/init.d/crowdsec restart >/dev/null 2>&1 steps_done="${steps_done}Restarted CrowdSec" fi json_add_boolean "success" 1 json_add_string "message" "Acquisition configuration completed" json_add_string "steps" "$steps_done" secubox_log "Acquisition configuration completed: $steps_done" json_dump } # Get realtime acquisition metrics with rates get_acquisition_metrics() { check_cscli json_init # Get raw metrics from cscli local metrics_output metrics_output=$(run_cscli metrics -o json 2>/dev/null) if [ -z "$metrics_output" ]; then json_add_boolean "available" 0 json_add_string "error" "Metrics not available" json_dump return fi json_add_boolean "available" 1 json_add_int "timestamp" "$(date +%s)" # Parse acquisition sources from metrics # Store metrics in temp file for parsing local tmp_file="/tmp/crowdsec_metrics.$$" echo "$metrics_output" > "$tmp_file" # Extract acquisition metrics json_add_array "sources" # Use jsonfilter to extract acquisition data # Format: {"source": "file:/var/log/messages", "lines_read": 123, "lines_parsed": 100, ...} local sources sources=$(cat "$tmp_file" | jsonfilter -e '@.acquisition' 2>/dev/null) if [ -n "$sources" ]; then # Parse each source for source in $(echo "$metrics_output" | jsonfilter -e '@.acquisition.*' 2>/dev/null | head -20); do json_add_object "" json_add_string "source" "$source" json_close_object done fi json_close_array # Get overall stats local total_read=0 local total_parsed=0 local total_unparsed=0 local total_buckets=0 # Parse acquisition stats using awk if [ -f "$tmp_file" ]; then # Extract line counts total_read=$(cat "$tmp_file" | grep -o '"lines_read":[0-9]*' | grep -o '[0-9]*' | awk '{sum+=$1} END {print sum+0}') total_parsed=$(cat "$tmp_file" | grep -o '"lines_parsed":[0-9]*' | grep -o '[0-9]*' | awk '{sum+=$1} END {print sum+0}') total_unparsed=$(cat "$tmp_file" | grep -o '"lines_unparsed":[0-9]*' | grep -o '[0-9]*' | awk '{sum+=$1} END {print sum+0}') total_buckets=$(cat "$tmp_file" | grep -o '"lines_poured_to_bucket":[0-9]*' | grep -o '[0-9]*' | awk '{sum+=$1} END {print sum+0}') fi json_add_int "total_lines_read" "${total_read:-0}" json_add_int "total_lines_parsed" "${total_parsed:-0}" json_add_int "total_lines_unparsed" "${total_unparsed:-0}" json_add_int "total_buckets" "${total_buckets:-0}" # Calculate parse rate if [ "$total_read" -gt 0 ]; then local parse_rate=$((total_parsed * 100 / total_read)) json_add_int "parse_rate" "$parse_rate" else json_add_int "parse_rate" 0 fi # Check active acquisition files json_add_array "active_files" local acquis_dir="/etc/crowdsec/acquis.d" if [ -d "$acquis_dir" ]; then for f in "$acquis_dir"/*.yaml; do if [ -f "$f" ]; then json_add_string "" "$(basename "$f" .yaml)" fi done fi json_close_array # Clean up rm -f "$tmp_file" json_dump } # Get current acquisition configuration get_acquisition_config() { json_init # Get values from UCI local syslog_enabled=$(uci -q get crowdsec.acquisition.syslog_enabled || echo "1") local firewall_enabled=$(uci -q get crowdsec.acquisition.firewall_enabled || echo "1") local ssh_enabled=$(uci -q get crowdsec.acquisition.ssh_enabled || echo "1") local http_enabled=$(uci -q get crowdsec.acquisition.http_enabled || echo "0") local syslog_path=$(uci -q get crowdsec.acquisition.syslog_path || echo "/var/log/messages") json_add_string "syslog_enabled" "$syslog_enabled" json_add_string "firewall_enabled" "$firewall_enabled" json_add_string "ssh_enabled" "$ssh_enabled" json_add_string "http_enabled" "$http_enabled" json_add_string "syslog_path" "$syslog_path" # Check which acquisition files exist local acquis_dir="/etc/crowdsec/acquis.d" local unified_exists=0 local http_exists=0 [ -f "$acquis_dir/openwrt-unified.yaml" ] && unified_exists=1 [ -f "$acquis_dir/openwrt-http.yaml" ] && http_exists=1 json_add_boolean "unified_file_exists" "$unified_exists" json_add_boolean "http_file_exists" "$http_exists" json_dump } # Service control (start/stop/restart/reload) service_control() { local action="$1" json_init case "$action" in start|stop|restart|reload) secubox_log "CrowdSec service $action requested" local output output=$(/etc/init.d/crowdsec "$action" 2>&1) local result=$? sleep 2 # Check if service is running after action local running=0 if pgrep crowdsec >/dev/null 2>&1; then running=1 fi if [ "$result" -eq 0 ]; then json_add_boolean "success" 1 json_add_string "action" "$action" json_add_boolean "running" "$running" json_add_string "message" "Service $action completed" else json_add_boolean "success" 0 json_add_string "error" "Service $action failed" json_add_string "output" "$output" fi ;; *) json_add_boolean "success" 0 json_add_string "error" "Invalid action. Use: start, stop, restart, reload" ;; esac json_dump } # Complete health check for dashboard get_health_check() { json_init # CrowdSec running status local cs_running=0 if pgrep crowdsec >/dev/null 2>&1; then cs_running=1 fi json_add_boolean "crowdsec_running" "$cs_running" json_add_string "crowdsec" "$([ "$cs_running" = "1" ] && echo running || echo stopped)" # Version local version="" if [ -x "$CSCLI" ]; then version=$(run_cscli version 2>/dev/null | grep "version:" | awk '{print $2}') fi json_add_string "version" "${version:-unknown}" # LAPI status (dynamic URL from credentials) local lapi_status="unavailable" local lapi_url lapi_url=$(grep "^url:" /etc/crowdsec/local_api_credentials.yaml 2>/dev/null | awk '{print $2}') [ -z "$lapi_url" ] && lapi_url="http://127.0.0.1:8090" if [ -x "$CSCLI" ]; then if run_with_timeout 5 "$CSCLI" lapi status >/dev/null 2>&1; then lapi_status="available" fi fi json_add_string "lapi_status" "$lapi_status" json_add_string "lapi_url" "$lapi_url" # CAPI status - parse cscli capi status output local capi_status="disconnected" local capi_enrolled=0 local capi_subscription="" local sharing_signals=0 local pulling_blocklist=0 local pulling_console=0 if [ -x "$CSCLI" ]; then local capi_output="" capi_output=$(run_with_timeout 10 "$CSCLI" capi status 2>&1) if echo "$capi_output" | grep -qi "You can successfully interact with Central API"; then capi_status="connected" fi if echo "$capi_output" | grep -qi "enrolled in the console"; then capi_enrolled=1 fi if echo "$capi_output" | grep -qi "COMMUNITY"; then capi_subscription="COMMUNITY" elif echo "$capi_output" | grep -qi "PRO"; then capi_subscription="PRO" fi if echo "$capi_output" | grep -qi "Sharing signals is enabled"; then sharing_signals=1 fi if echo "$capi_output" | grep -qi "Pulling community blocklist is enabled"; then pulling_blocklist=1 fi if echo "$capi_output" | grep -qi "Pulling blocklists from the console is enabled"; then pulling_console=1 fi fi json_add_string "capi_status" "$capi_status" json_add_boolean "capi_enrolled" "$capi_enrolled" json_add_string "capi_subscription" "$capi_subscription" json_add_boolean "sharing_signals" "$sharing_signals" json_add_boolean "pulling_blocklist" "$pulling_blocklist" json_add_boolean "pulling_console" "$pulling_console" # Machine info local machine_id="" local machine_version="" if [ -x "$CSCLI" ]; then local machines_output="" machines_output=$(run_cscli machines list -o json 2>/dev/null) if [ -n "$machines_output" ] && [ "$machines_output" != "null" ]; then machine_id=$(echo "$machines_output" | jsonfilter -e '@[0].machineId' 2>/dev/null) machine_version=$(echo "$machines_output" | jsonfilter -e '@[0].version' 2>/dev/null) fi fi json_add_string "machine_id" "${machine_id:-localhost}" json_add_string "machine_version" "$machine_version" # Bouncer count local bouncer_count=0 if [ -x "$CSCLI" ]; then bouncer_count=$(run_cscli bouncers list -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l) fi json_add_int "bouncer_count" "${bouncer_count:-0}" # Total decisions count (local + CAPI from metrics) local local_decisions=0 capi_decisions=0 decisions_count=0 if [ -x "$CSCLI" ]; then local_decisions=$(run_cscli decisions list --no-api -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l) capi_decisions=$(run_cscli metrics 2>/dev/null | grep 'CAPI.*ban' | awk -F'|' '{sum += $5} END {print sum+0}') decisions_count=$((local_decisions + capi_decisions)) fi json_add_int "decisions_count" "${decisions_count:-0}" json_add_int "local_decisions" "${local_decisions:-0}" json_add_int "capi_decisions" "${capi_decisions:-0}" # GeoIP status - check if GeoIP database exists (check multiple paths) local geoip_enabled=0 local data_path data_path=$(grep "db_path:" /etc/crowdsec/config.yaml 2>/dev/null | awk '{print $2}' | xargs dirname 2>/dev/null) [ -z "$data_path" ] && data_path="/srv/crowdsec/data" [ -f "${data_path}/GeoLite2-City.mmdb" ] && geoip_enabled=1 [ -f "${data_path}/GeoLite2-ASN.mmdb" ] && geoip_enabled=1 # Also check common alternative paths [ -f "/var/lib/crowdsec/data/GeoLite2-City.mmdb" ] && geoip_enabled=1 json_add_boolean "geoip_enabled" "$geoip_enabled" # Acquisition sources count local acquisition_count=0 if [ -d "/etc/crowdsec/acquis.d" ]; then acquisition_count=$(ls -1 /etc/crowdsec/acquis.d/*.yaml 2>/dev/null | wc -l) fi [ -f "/etc/crowdsec/acquis.yaml" ] && acquisition_count=$((acquisition_count + 1)) json_add_int "acquisition_count" "${acquisition_count:-0}" # Scenario count (installed scenarios) local scenario_count=0 if [ -x "$CSCLI" ]; then scenario_count=$(run_cscli scenarios list -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l) fi json_add_int "scenario_count" "${scenario_count:-0}" json_dump } # Get CAPI blocklist metrics (decisions by origin and reason) # Parses from cscli metrics output since decisions list doesn't show CAPI decisions get_capi_metrics() { json_init if [ ! -x "$CSCLI" ]; then json_add_boolean "available" 0 json_add_string "error" "cscli not found" json_dump return fi json_add_boolean "available" 1 # Get metrics output local metrics_output="" metrics_output=$(run_cscli metrics 2>/dev/null) # Parse Local API Decisions section from metrics # Format: | reason | origin | action | count | local capi_total=0 local local_total=0 # Extract CAPI decisions count capi_total=$(echo "$metrics_output" | grep 'CAPI.*ban' | awk -F'|' '{sum += $5} END {print sum+0}') # Extract local decisions count (origin = crowdsec or cscli) local_total=$(echo "$metrics_output" | grep -E '(crowdsec|cscli).*ban' | awk -F'|' '{sum += $5} END {print sum+0}') json_add_int "total_capi" "${capi_total:-0}" json_add_int "total_local" "${local_total:-0}" # Build breakdown by scenario from metrics - parse lines with ban action json_add_array "breakdown" echo "$metrics_output" | grep '|.*ban.*|' | grep -v "Action" | while IFS='|' read -r _ reason origin action count _; do reason=$(echo "$reason" | xargs) origin=$(echo "$origin" | xargs) count=$(echo "$count" | xargs) if [ -n "$reason" ] && [ -n "$count" ] && [ "$count" != "Count" ]; then json_add_object "" json_add_string "scenario" "$reason" json_add_string "origin" "$origin" json_add_int "count" "$count" json_close_object fi done json_close_array json_dump } # Get available hub items (not installed) get_hub_available() { json_init if [ ! -x "$CSCLI" ]; then json_add_boolean "available" 0 json_add_string "error" "cscli not found" json_dump return fi json_add_boolean "available" 1 # Get hub list in JSON format (all items) local hub_output="" hub_output=$(run_with_timeout 30 "$CSCLI" hub list -a -o json 2>/dev/null) if [ -z "$hub_output" ]; then json_add_string "collections" "[]" json_add_string "parsers" "[]" json_add_string "scenarios" "[]" json_dump return fi # Output the raw hub data - frontend will parse it echo "$hub_output" } # Install a hub item (collection, parser, scenario) install_hub_item() { local item_type="$1" local item_name="$2" json_init if [ -z "$item_type" ] || [ -z "$item_name" ]; then json_add_boolean "success" 0 json_add_string "error" "Item type and name are required" json_dump return fi # Validate item type case "$item_type" in collection|parser|scenario|postoverflow|context) ;; *) json_add_boolean "success" 0 json_add_string "error" "Invalid item type: $item_type" json_dump return ;; esac if [ ! -x "$CSCLI" ]; then json_add_boolean "success" 0 json_add_string "error" "cscli not found" json_dump return fi secubox_log "Installing CrowdSec $item_type: $item_name" # Install the item local output="" output=$(run_with_timeout 60 "$CSCLI" "${item_type}s" install "$item_name" 2>&1) local result=$? if [ "$result" -eq 0 ]; then json_add_boolean "success" 1 json_add_string "message" "Successfully installed $item_type: $item_name" json_add_string "output" "$output" secubox_log "Installed $item_type: $item_name" else json_add_boolean "success" 0 json_add_string "error" "Failed to install $item_type: $item_name" json_add_string "output" "$output" secubox_log "Failed to install $item_type: $item_name - $output" fi json_dump } # Remove a hub item remove_hub_item() { local item_type="$1" local item_name="$2" json_init if [ -z "$item_type" ] || [ -z "$item_name" ]; then json_add_boolean "success" 0 json_add_string "error" "Item type and name are required" json_dump return fi case "$item_type" in collection|parser|scenario|postoverflow|context) ;; *) json_add_boolean "success" 0 json_add_string "error" "Invalid item type: $item_type" json_dump return ;; esac if [ ! -x "$CSCLI" ]; then json_add_boolean "success" 0 json_add_string "error" "cscli not found" json_dump return fi secubox_log "Removing CrowdSec $item_type: $item_name" local output="" output=$(run_with_timeout 30 "$CSCLI" "${item_type}s" remove "$item_name" 2>&1) local result=$? if [ "$result" -eq 0 ]; then json_add_boolean "success" 1 json_add_string "message" "Successfully removed $item_type: $item_name" secubox_log "Removed $item_type: $item_name" else json_add_boolean "success" 0 json_add_string "error" "Failed to remove $item_type: $item_name" json_add_string "output" "$output" fi json_dump } # Get dashboard settings (enrollment key, etc.) get_settings() { json_init local enrollment_key="" local machine_name="" local auto_enroll="" # Read from UCI config enrollment_key=$(uci -q get crowdsec-dashboard.main.enrollment_key 2>/dev/null) machine_name=$(uci -q get crowdsec-dashboard.main.machine_name 2>/dev/null) auto_enroll=$(uci -q get crowdsec-dashboard.main.auto_enroll 2>/dev/null) json_add_string "enrollment_key" "$enrollment_key" json_add_string "machine_name" "$machine_name" json_add_string "auto_enroll" "${auto_enroll:-0}" json_dump } # Save dashboard settings save_settings() { local enrollment_key="$1" local machine_name="$2" local auto_enroll="$3" json_init # Ensure config section exists uci -q get crowdsec-dashboard.main >/dev/null 2>&1 || { uci set crowdsec-dashboard.main=settings } # Save settings [ -n "$enrollment_key" ] && uci set crowdsec-dashboard.main.enrollment_key="$enrollment_key" [ -n "$machine_name" ] && uci set crowdsec-dashboard.main.machine_name="$machine_name" [ -n "$auto_enroll" ] && uci set crowdsec-dashboard.main.auto_enroll="$auto_enroll" if uci commit crowdsec-dashboard 2>/dev/null; then json_add_boolean "success" 1 json_add_string "message" "Settings saved" secubox_log "Saved enrollment settings" else json_add_boolean "success" 0 json_add_string "error" "Failed to save settings" fi json_dump } # Consolidated overview data for dashboard - single API call optimization get_overview() { json_init # Service status (fast - just process checks) local cs_running=0 pgrep crowdsec >/dev/null 2>&1 && cs_running=1 json_add_string "crowdsec" "$([ "$cs_running" = "1" ] && echo running || echo stopped)" local bouncer_running=0 pgrep -f "crowdsec-firewall-bouncer" >/dev/null 2>&1 && bouncer_running=1 json_add_string "bouncer" "$([ "$bouncer_running" = "1" ] && echo running || echo stopped)" # Version local version="" [ -x "$CSCLI" ] && version=$("$CSCLI" version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1) json_add_string "version" "${version:-unknown}" # Quick stats local decisions_count=0 local local_decisions=0 local capi_decisions=0 local alerts_count=0 local bouncers_count=0 if [ "$cs_running" = "1" ] && [ -x "$CSCLI" ]; then # Local decisions (from local scenarios) local_decisions=$(run_cscli decisions list --no-api -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l) # CAPI decisions (blocklists) - parse from metrics output capi_decisions=$(run_cscli metrics 2>/dev/null | grep 'CAPI.*ban' | awk -F'|' '{sum += $5} END {print sum+0}') # Total decisions decisions_count=$((local_decisions + capi_decisions)) alerts_count=$(run_cscli alerts list -o json --since 24h --limit 100 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l) bouncers_count=$(run_cscli bouncers list -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l) fi json_add_int "total_decisions" "${decisions_count:-0}" json_add_int "local_decisions" "${local_decisions:-0}" json_add_int "capi_decisions" "${capi_decisions:-0}" json_add_int "alerts_24h" "${alerts_count:-0}" json_add_int "bouncer_count" "${bouncers_count:-0}" # Active bans = local decisions (IPs being actively blocked by local scenarios) # Use local_decisions count which is already calculated above json_add_int "active_bans" "${local_decisions:-0}" # Bouncer effectiveness stats (packets/bytes dropped vs processed) local dropped_packets=0 local dropped_bytes=0 local processed_packets=0 local processed_bytes=0 if [ "$cs_running" = "1" ]; then # Parse Total line from Bouncer Metrics table # Format: | Total | 16.00k | 13.72k | 231 | 356.19k | 6.02k | local totals totals=$(run_cscli metrics 2>/dev/null | grep -E '^\|.*Total' | sed 's/|//g') if [ -n "$totals" ]; then # Convert k/M suffixes to numbers dropped_bytes=$(echo "$totals" | awk '{v=$3; gsub(/k$/,"",v); gsub(/M$/,"",v); if(v~/\./){v=v*1000}; print v}') dropped_packets=$(echo "$totals" | awk '{print $4}') processed_bytes=$(echo "$totals" | awk '{v=$5; gsub(/k$/,"",v); gsub(/M$/,"",v); if(v~/\./){v=v*1000}; if(v=="-")v=0; print v}') processed_packets=$(echo "$totals" | awk '{v=$6; gsub(/k$/,"",v); gsub(/M$/,"",v); if(v~/\./){v=v*1000}; if(v=="-")v=0; print v}') fi fi json_add_string "dropped_packets" "${dropped_packets:-0}" json_add_string "dropped_bytes" "${dropped_bytes:-0}" json_add_string "processed_packets" "${processed_packets:-0}" json_add_string "processed_bytes" "${processed_bytes:-0}" # GeoIP status - check if GeoIP database exists (check multiple paths) local geoip_enabled=0 local data_path data_path=$(grep "db_path:" /etc/crowdsec/config.yaml 2>/dev/null | awk '{print $2}' | xargs dirname 2>/dev/null) [ -z "$data_path" ] && data_path="/srv/crowdsec/data" [ -f "${data_path}/GeoLite2-City.mmdb" ] && geoip_enabled=1 [ -f "${data_path}/GeoLite2-ASN.mmdb" ] && geoip_enabled=1 # Also check common alternative paths [ -f "/var/lib/crowdsec/data/GeoLite2-City.mmdb" ] && geoip_enabled=1 json_add_boolean "geoip_enabled" "$geoip_enabled" # Acquisition sources count local acquisition_count=0 if [ -d "/etc/crowdsec/acquis.d" ]; then acquisition_count=$(ls -1 /etc/crowdsec/acquis.d/*.yaml 2>/dev/null | wc -l) fi [ -f "/etc/crowdsec/acquis.yaml" ] && acquisition_count=$((acquisition_count + 1)) json_add_int "acquisition_count" "${acquisition_count:-0}" # Scenario count (installed scenarios) local scenario_count=0 if [ "$cs_running" = "1" ] && [ -x "$CSCLI" ]; then scenario_count=$(run_cscli scenarios list -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l) fi json_add_int "scenario_count" "${scenario_count:-0}" # Top scenarios (from cscli metrics - includes CAPI blocklist breakdown) local scenarios="" if [ "$cs_running" = "1" ]; then # Parse "Local API Decisions" table from cscli metrics # Lines like: | ssh:bruteforce | CAPI | ban | 12095 | scenarios=$(run_cscli metrics 2>/dev/null | \ grep -E '^\| [a-z].*\| CAPI' | \ sed 's/|//g;s/^[ ]*//;s/[ ]*$//' | \ awk '{print $4, $1}' | sort -rn | head -5 | \ awk '{print "{\"scenario\":\"" $2 "\",\"count\":" $1 "}"}' | \ tr '\n' ',' | sed 's/,$//') fi json_add_string "top_scenarios_raw" "[$scenarios]" # Top countries (from alerts with GeoIP enrichment) # Note: CAPI decisions don't include country - only local detections have GeoIP local countries="" if [ "$cs_running" = "1" ]; then countries=$(run_cscli alerts list -o json --limit 200 2>/dev/null | \ jsonfilter -e '@[*].source.cn' 2>/dev/null | \ grep -v '^$' | sort | uniq -c | sort -rn | head -10 | \ awk '{print "{\"country\":\"" $2 "\",\"count\":" $1 "}"}' | \ tr '\n' ',' | sed 's/,$//') fi json_add_string "top_countries_raw" "[$countries]" # Recent decisions (limited to 10 for display) # Recent decisions as raw JSON array string local decisions_raw="[]" if [ "$cs_running" = "1" ]; then decisions_raw=$(run_cscli decisions list -o json --limit 10 2>/dev/null || echo "[]") [ -z "$decisions_raw" ] && decisions_raw="[]" fi json_add_string "decisions_raw" "$decisions_raw" # Recent alerts as raw JSON array string local alerts_raw="[]" if [ "$cs_running" = "1" ]; then alerts_raw=$(run_cscli alerts list -o json --limit 8 2>/dev/null || echo "[]") [ -z "$alerts_raw" ] && alerts_raw="[]" fi json_add_string "alerts_raw" "$alerts_raw" # CrowdSec logs (last 30 lines) json_add_array "logs" if [ -f /var/log/crowdsec.log ]; then tail -n 30 /var/log/crowdsec.log 2>/dev/null | while IFS= read -r line; do json_add_string "" "$line" done fi json_close_array # LAPI status (dynamic port detection from config) local lapi_ok=0 local lapi_port="" # Get LAPI port from credentials or config file lapi_port=$(grep -oE ':[0-9]+/?$' /etc/crowdsec/local_api_credentials.yaml 2>/dev/null | tr -d ':/') [ -z "$lapi_port" ] && lapi_port=$(grep 'listen_uri' /etc/crowdsec/config.yaml 2>/dev/null | grep -oE ':[0-9]+$' | tr -d ':') [ -z "$lapi_port" ] && lapi_port=8080 # Convert port to hex for /proc/net/tcp lookup local lapi_port_hex lapi_port_hex=$(printf '%04X' "$lapi_port") if [ "$cs_running" = "1" ] && grep -qi ":${lapi_port_hex} " /proc/net/tcp 2>/dev/null; then lapi_ok=1 fi json_add_string "lapi_status" "$([ "$lapi_ok" = "1" ] && echo available || echo unavailable)" # CAPI status (from config check, not live call) local capi_enrolled=0 [ -f /etc/crowdsec/online_api_credentials.yaml ] && capi_enrolled=1 json_add_boolean "capi_enrolled" "$capi_enrolled" json_dump } # Main dispatcher case "$1" in list) echo '{"get_overview":{},"decisions":{},"alerts":{"limit":"number"},"metrics":{},"bouncers":{},"machines":{},"hub":{},"status":{},"ban":{"ip":"string","duration":"string","reason":"string"},"unban":{"ip":"string"},"stats":{},"secubox_logs":{},"collect_debug":{},"waf_status":{},"metrics_config":{},"configure_metrics":{"enable":"string"},"collections":{},"install_collection":{"collection":"string"},"remove_collection":{"collection":"string"},"update_hub":{},"register_bouncer":{"bouncer_name":"string"},"delete_bouncer":{"bouncer_name":"string"},"firewall_bouncer_status":{},"control_firewall_bouncer":{"action":"string"},"firewall_bouncer_config":{},"update_firewall_bouncer_config":{"key":"string","value":"string"},"nftables_stats":{},"check_wizard_needed":{},"wizard_state":{},"repair_lapi":{},"repair_capi":{},"reset_wizard":{},"console_status":{},"console_enroll":{"key":"string","name":"string"},"console_disable":{},"service_control":{"action":"string"},"configure_acquisition":{"syslog_enabled":"string","firewall_enabled":"string","ssh_enabled":"string","http_enabled":"string","syslog_path":"string"},"acquisition_config":{},"acquisition_metrics":{},"health_check":{},"capi_metrics":{},"hub_available":{},"install_hub_item":{"item_type":"string","item_name":"string"},"remove_hub_item":{"item_type":"string","item_name":"string"},"get_settings":{},"save_settings":{"enrollment_key":"string","machine_name":"string","auto_enroll":"string"}}' ;; call) case "$2" in decisions) get_decisions ;; alerts) read -r input limit=$(echo "$input" | jsonfilter -e '@.limit' 2>/dev/null) get_alerts "${limit:-50}" ;; metrics) get_metrics ;; bouncers) get_bouncers ;; machines) get_machines ;; hub) get_hub ;; status) get_status ;; ban) read -r input ip=$(echo "$input" | jsonfilter -e '@.ip' 2>/dev/null) duration=$(echo "$input" | jsonfilter -e '@.duration' 2>/dev/null) reason=$(echo "$input" | jsonfilter -e '@.reason' 2>/dev/null) add_ban "$ip" "$duration" "$reason" ;; unban) read -r input ip=$(echo "$input" | jsonfilter -e '@.ip' 2>/dev/null) remove_ban "$ip" ;; stats) get_dashboard_stats ;; secubox_logs|crowdsec_logs) crowdsec_logs ;; collect_debug) collect_debug ;; waf_status) get_waf_status ;; metrics_config) get_metrics_config ;; configure_metrics) read -r input enable=$(echo "$input" | jsonfilter -e '@.enable' 2>/dev/null) configure_metrics "$enable" ;; collections) get_collections ;; install_collection) read -r input collection=$(echo "$input" | jsonfilter -e '@.collection' 2>/dev/null) install_collection "$collection" ;; remove_collection) read -r input collection=$(echo "$input" | jsonfilter -e '@.collection' 2>/dev/null) remove_collection "$collection" ;; update_hub) update_hub ;; register_bouncer) read -r input bouncer_name=$(echo "$input" | jsonfilter -e '@.bouncer_name' 2>/dev/null) register_bouncer "$bouncer_name" ;; delete_bouncer) read -r input bouncer_name=$(echo "$input" | jsonfilter -e '@.bouncer_name' 2>/dev/null) delete_bouncer "$bouncer_name" ;; firewall_bouncer_status) get_firewall_bouncer_status ;; control_firewall_bouncer) read -r input action=$(echo "$input" | jsonfilter -e '@.action' 2>/dev/null) control_firewall_bouncer "$action" ;; firewall_bouncer_config) get_firewall_bouncer_config ;; update_firewall_bouncer_config) read -r input key=$(echo "$input" | jsonfilter -e '@.key' 2>/dev/null) value=$(echo "$input" | jsonfilter -e '@.value' 2>/dev/null) update_firewall_bouncer_config "$key" "$value" ;; nftables_stats) get_nftables_stats ;; check_wizard_needed) check_wizard_needed ;; wizard_state) get_wizard_state ;; repair_lapi) repair_lapi ;; repair_capi) repair_capi ;; reset_wizard) reset_wizard ;; console_status) get_console_status ;; console_enroll) read -r input key=$(echo "$input" | jsonfilter -e '@.key' 2>/dev/null) name=$(echo "$input" | jsonfilter -e '@.name' 2>/dev/null) console_enroll "$key" "$name" ;; console_disable) console_disable ;; service_control) read -r input action=$(echo "$input" | jsonfilter -e '@.action' 2>/dev/null) service_control "$action" ;; configure_acquisition) read -r input syslog_enabled=$(echo "$input" | jsonfilter -e '@.syslog_enabled' 2>/dev/null) firewall_enabled=$(echo "$input" | jsonfilter -e '@.firewall_enabled' 2>/dev/null) ssh_enabled=$(echo "$input" | jsonfilter -e '@.ssh_enabled' 2>/dev/null) http_enabled=$(echo "$input" | jsonfilter -e '@.http_enabled' 2>/dev/null) syslog_path=$(echo "$input" | jsonfilter -e '@.syslog_path' 2>/dev/null) configure_acquisition "$syslog_enabled" "$firewall_enabled" "$ssh_enabled" "$http_enabled" "$syslog_path" ;; acquisition_config) get_acquisition_config ;; acquisition_metrics) get_acquisition_metrics ;; health_check) get_health_check ;; capi_metrics) get_capi_metrics ;; hub_available) get_hub_available ;; install_hub_item) read -r input item_type=$(echo "$input" | jsonfilter -e '@.item_type' 2>/dev/null) item_name=$(echo "$input" | jsonfilter -e '@.item_name' 2>/dev/null) install_hub_item "$item_type" "$item_name" ;; remove_hub_item) read -r input item_type=$(echo "$input" | jsonfilter -e '@.item_type' 2>/dev/null) item_name=$(echo "$input" | jsonfilter -e '@.item_name' 2>/dev/null) remove_hub_item "$item_type" "$item_name" ;; get_settings) get_settings ;; save_settings) read -r input enrollment_key=$(echo "$input" | jsonfilter -e '@.enrollment_key' 2>/dev/null) machine_name=$(echo "$input" | jsonfilter -e '@.machine_name' 2>/dev/null) auto_enroll=$(echo "$input" | jsonfilter -e '@.auto_enroll' 2>/dev/null) save_settings "$enrollment_key" "$machine_name" "$auto_enroll" ;; get_overview) get_overview ;; *) echo '{"error": "Unknown method"}' ;; esac ;; esac