#!/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" # 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=$($CSCLI decisions list -o json 2>/dev/null) if [ -z "$output" ] || [ "$output" = "null" ]; then echo '[]' else echo "$output" fi } # Get alerts list get_alerts() { local limit="${1:-50}" check_cscli local output output=$($CSCLI alerts list -o json --limit "$limit" 2>/dev/null) if [ -z "$output" ] || [ "$output" = "null" ]; then echo '[]' else echo "$output" fi } # Get metrics get_metrics() { check_cscli local output output=$($CSCLI metrics -o json 2>/dev/null) if [ -z "$output" ]; then echo '{}' else echo "$output" fi } # Get bouncers get_bouncers() { check_cscli local output output=$($CSCLI bouncers list -o json 2>/dev/null) if [ -z "$output" ] || [ "$output" = "null" ]; then echo '[]' else echo "$output" fi } # Get machines get_machines() { check_cscli local output output=$($CSCLI machines list -o json 2>/dev/null) if [ -z "$output" ] || [ "$output" = "null" ]; then echo '[]' else echo "$output" fi } # Get hub status get_hub() { check_cscli local output output=$($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 if pgrep crowdsec >/dev/null 2>&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=$($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" if [ -x "$CSCLI" ]; then if $CSCLI lapi status >/dev/null 2>&1; then lapi_status="available" fi fi json_add_string "lapi_status" "$lapi_status" 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=$($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=$($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 decisions_count decisions_count=$($CSCLI decisions list -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l) json_add_int "total_decisions" "${decisions_count:-0}" # Count alerts (last 24h) local alerts_count alerts_count=$($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=$($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 alerts) local scenarios scenarios=$($CSCLI alerts list -o json --limit 100 2>/dev/null | \ jsonfilter -e '@[*].scenario' 2>/dev/null | \ sort | uniq -c | sort -rn | head -5 | \ awk '{print "{\"scenario\":\"" $2 "\",\"count\":" $1 "}"}' | \ tr '\n' ',' | sed 's/,$//') json_add_string "top_scenarios_raw" "[$scenarios]" # Top countries (from decisions) local countries countries=$($CSCLI decisions list -o json 2>/dev/null | \ jsonfilter -e '@[*].country' 2>/dev/null | \ sort | uniq -c | sort -rn | head -10 | \ awk '{print "{\"country\":\"" $2 "\",\"count\":" $1 "}"}' | \ tr '\n' ',' | sed 's/,$//') json_add_string "top_countries_raw" "[$countries]" json_dump } seccubox_logs() { json_init json_add_array "entries" if [ -f /var/log/seccubox.log ]; then tail -n 80 /var/log/seccubox.log | while IFS= read -r line; do json_add_string "" "$line" done fi json_close_array 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/seccubox.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 $CSCLI help appsec >/dev/null 2>&1; then local appsec_status appsec_status=$($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=$($CSCLI collections list -o json 2>/dev/null) if [ -z "$output" ] || [ "$output" = "null" ]; then echo '[]' else echo "$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 $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 $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 $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" 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 # Generate API key local api_key api_key=$($CSCLI bouncers add "$bouncer_name" -o raw 2>&1) if [ -n "$api_key" ] && [ "${#api_key}" -gt 10 ]; then json_add_boolean "success" 1 json_add_string "api_key" "$api_key" json_add_string "message" "Bouncer '$bouncer_name' registered successfully" secubox_log "Registered bouncer: $bouncer_name" else json_add_boolean "success" 0 json_add_string "error" "Failed to register bouncer '$bouncer_name'" 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 $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 local ipv4_count=0 local ipv6_count=0 if [ "$nft_ipv4" = "1" ]; then ipv4_count=$(nft list set ip crowdsec crowdsec-blacklists 2>/dev/null | grep -c "elements = {" || echo "0") fi if [ "$nft_ipv6" = "1" ]; then ipv6_count=$(nft list set ip6 crowdsec6 crowdsec6-blacklists 2>/dev/null | grep -c "elements = {" || echo "0") 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:8080/") 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.$key=$value" ;; api_url|update_frequency|deny_action|log_level) # 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.$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 IPv4 set json_add_array "ipv4_blocked_ips" if [ "$ipv4_exists" = "1" ]; then local ips ips=$(nft list set ip crowdsec crowdsec-blacklists 2>/dev/null | sed -n '/elements = {/,/}/p' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' || echo "") if [ -n "$ips" ]; then local ip for ip in $ips; do json_add_string "" "$ip" done fi fi json_close_array # Get blocked IPs from IPv6 set json_add_array "ipv6_blocked_ips" if [ "$ipv6_exists" = "1" ]; then local ips ips=$(nft list set ip6 crowdsec6 crowdsec6-blacklists 2>/dev/null | sed -n '/elements = {/,/}/p' | grep -oE '([0-9a-fA-F]{0,4}:){7}[0-9a-fA-F]{0,4}' || echo "") if [ -n "$ips" ]; then local ip for ip in $ips; do json_add_string "" "$ip" done fi 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" 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 $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=$($CSCLI collections list 2>/dev/null | grep -c "INSTALLED" || echo "0") fi json_add_int "collections_count" "$collections_count" json_dump } # Main dispatcher case "$1" in list) echo '{"decisions":{},"alerts":{"limit":"number"},"metrics":{},"bouncers":{},"machines":{},"hub":{},"status":{},"ban":{"ip":"string","duration":"string","reason":"string"},"unban":{"ip":"string"},"stats":{},"seccubox_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":{}}' ;; 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 ;; seccubox_logs) seccubox_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 ;; *) echo '{"error": "Unknown method"}' ;; esac ;; esac