When SSH logging is enabled in the wizard, automatically: - Set dropbear.@dropbear[0].verbose=1 to log auth failures - Restart dropbear to apply changes This ensures CrowdSec can detect SSH brute force attempts. Without verbose mode, Dropbear doesn't log failed auth to syslog. Also enable uhttpd syslog when HTTP logging is enabled. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2129 lines
58 KiB
Bash
Executable File
2129 lines
58 KiB
Bash
Executable File
#!/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 '{"alerts":[]}'
|
|
else
|
|
echo "{\"alerts\":$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
|
|
# Check if LAPI port is listening (8080 hex = 1F90)
|
|
local port_up=0
|
|
if grep -qi ":1F90 " /proc/net/tcp 2>/dev/null; then
|
|
port_up=1
|
|
fi
|
|
|
|
if [ "$port_up" = "0" ]; then
|
|
lapi_reason="port 8080 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
|
|
|
|
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 decisions_count
|
|
decisions_count=$(run_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=$(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 alerts)
|
|
local scenarios
|
|
scenarios=$(run_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=$(run_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 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
|
|
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[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))"
|
|
|
|
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 REPAIR_TIMEOUT=10
|
|
|
|
secubox_log "Starting LAPI repair..."
|
|
|
|
# Step 1: Stop CrowdSec completely
|
|
/etc/init.d/crowdsec stop >/dev/null 2>&1
|
|
sleep 1
|
|
killall crowdsec 2>/dev/null
|
|
sleep 1
|
|
steps_done="${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
|
|
mkdir -p /var/etc/crowdsec 2>/dev/null
|
|
chmod 755 /srv/crowdsec/data 2>/dev/null
|
|
steps_done="${steps_done}Dirs; "
|
|
|
|
# Step 3: Fix config.yaml
|
|
local config_file="/etc/crowdsec/config.yaml"
|
|
local var_config="/var/etc/crowdsec/config.yaml"
|
|
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
|
|
cp "$config_file" "$var_config" 2>/dev/null
|
|
steps_done="${steps_done}Config; "
|
|
else
|
|
errors="${errors}No config; "
|
|
fi
|
|
|
|
# Step 4: Remove corrupted credentials
|
|
local creds_file="/etc/crowdsec/local_api_credentials.yaml"
|
|
rm -f "$creds_file" 2>/dev/null
|
|
|
|
# Step 5: Clean up orphaned machines from database
|
|
local db_file="/srv/crowdsec/data/crowdsec.db"
|
|
if [ -f "$db_file" ]; then
|
|
if command -v sqlite3 >/dev/null 2>&1; then
|
|
# Remove all machines - we'll re-register
|
|
sqlite3 "$db_file" "DELETE FROM machines;" 2>/dev/null
|
|
steps_done="${steps_done}DB cleaned; "
|
|
else
|
|
# No sqlite3 - delete the database file, CrowdSec will recreate it
|
|
rm -f "$db_file" 2>/dev/null
|
|
steps_done="${steps_done}DB reset; "
|
|
fi
|
|
fi
|
|
|
|
# Step 6: Start CrowdSec in LAPI-only mode (no agent)
|
|
# This allows LAPI to start without needing valid credentials
|
|
local crowdsec_bin="/usr/bin/crowdsec"
|
|
if [ -x "$crowdsec_bin" ]; then
|
|
# Start LAPI-only mode in background
|
|
"$crowdsec_bin" -c "$var_config" -no-cs &
|
|
local cs_pid=$!
|
|
steps_done="${steps_done}LAPI-only started; "
|
|
|
|
# Wait for LAPI to be ready (max 8 seconds)
|
|
local retries=8
|
|
local lapi_ready=0
|
|
while [ $retries -gt 0 ]; do
|
|
if grep -qi ":1F90 " /proc/net/tcp 2>/dev/null; then
|
|
lapi_ready=1
|
|
break
|
|
fi
|
|
sleep 1
|
|
retries=$((retries - 1))
|
|
done
|
|
|
|
if [ "$lapi_ready" = "1" ]; then
|
|
steps_done="${steps_done}LAPI up; "
|
|
sleep 1
|
|
|
|
# Step 7: Register machine while LAPI is running
|
|
if [ -x "$CSCLI" ]; then
|
|
local reg_output=""
|
|
reg_output=$("$CSCLI" -c "$var_config" machines add localhost --auto --force -f "$creds_file" 2>&1)
|
|
if [ $? -eq 0 ]; then
|
|
steps_done="${steps_done}Registered; "
|
|
else
|
|
errors="${errors}Reg failed: ${reg_output}; "
|
|
fi
|
|
fi
|
|
else
|
|
errors="${errors}LAPI timeout; "
|
|
fi
|
|
|
|
# Step 8: Stop LAPI-only mode
|
|
kill $cs_pid 2>/dev/null
|
|
sleep 1
|
|
killall crowdsec 2>/dev/null
|
|
steps_done="${steps_done}LAPI stopped; "
|
|
else
|
|
errors="${errors}No crowdsec binary; "
|
|
fi
|
|
|
|
# Step 9: Verify credentials were created
|
|
if [ -s "$creds_file" ] && grep -q "password:" "$creds_file"; then
|
|
steps_done="${steps_done}Creds OK; "
|
|
else
|
|
errors="${errors}No creds; "
|
|
fi
|
|
|
|
# Step 10: Start CrowdSec normally via init
|
|
/etc/init.d/crowdsec start >/dev/null 2>&1
|
|
sleep 3
|
|
|
|
# Step 11: Verify everything is working
|
|
local lapi_ok=0
|
|
if pgrep crowdsec >/dev/null 2>&1; then
|
|
steps_done="${steps_done}Running; "
|
|
if [ -x "$CSCLI" ]; then
|
|
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
|
|
fi
|
|
else
|
|
errors="${errors}Not running; "
|
|
# Get error from log
|
|
local log_err=""
|
|
log_err=$(tail -3 /var/log/crowdsec.log 2>/dev/null | grep -i "fatal\|error" | head -1)
|
|
[ -n "$log_err" ] && errors="${errors}${log_err}; "
|
|
fi
|
|
|
|
# Step 12: Update hub index (required for collections to work)
|
|
if [ "$lapi_ok" = "1" ] && [ -x "$CSCLI" ]; then
|
|
if run_with_timeout 30 "$CSCLI" hub update >/dev/null 2>&1; then
|
|
steps_done="${steps_done}Hub updated; "
|
|
else
|
|
errors="${errors}Hub update failed; "
|
|
fi
|
|
fi
|
|
|
|
# Step 13: Register with CAPI (required for console enrollment)
|
|
if [ "$lapi_ok" = "1" ] && [ -x "$CSCLI" ]; then
|
|
if run_with_timeout 15 "$CSCLI" capi register >/dev/null 2>&1; then
|
|
steps_done="${steps_done}CAPI registered; "
|
|
else
|
|
# CAPI registration may fail if already registered, which is OK
|
|
local capi_status=""
|
|
capi_status=$(run_with_timeout 5 "$CSCLI" capi status 2>&1)
|
|
if echo "$capi_status" | grep -qi "registered\|online"; then
|
|
steps_done="${steps_done}CAPI OK; "
|
|
else
|
|
errors="${errors}CAPI not registered; "
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
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
|
|
}
|
|
|
|
# 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=""
|
|
capi_status=$(run_with_timeout 5 "$CSCLI" capi status 2>&1)
|
|
if echo "$capi_status" | grep -qi "registered\|online"; then
|
|
capi_ok=1
|
|
else
|
|
# Try to register with CAPI
|
|
secubox_log "CAPI not registered, attempting registration..."
|
|
if run_with_timeout 15 "$CSCLI" capi register >/dev/null 2>&1; then
|
|
capi_ok=1
|
|
secubox_log "CAPI registration successful"
|
|
else
|
|
json_add_boolean "success" 0
|
|
json_add_string "error" "CAPI registration failed. Please run LAPI repair first."
|
|
json_dump
|
|
return
|
|
fi
|
|
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
|
|
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: Enable verbose logging for services that need it
|
|
# Dropbear SSH needs verbose mode to log authentication failures
|
|
if [ "$ssh_enabled" = "1" ]; then
|
|
if uci -q get dropbear.@dropbear[0] >/dev/null 2>&1; then
|
|
uci set dropbear.@dropbear[0].verbose='1'
|
|
uci commit dropbear
|
|
/etc/init.d/dropbear restart >/dev/null 2>&1
|
|
steps_done="${steps_done}Enabled Dropbear verbose logging; "
|
|
fi
|
|
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 OpenWrt's logread
|
|
if [ "$syslog_enabled" = "1" ] || [ "$firewall_enabled" = "1" ] || [ "$ssh_enabled" = "1" ]; then
|
|
cat > "$acquis_dir/openwrt-unified.yaml" << 'YAML'
|
|
# OpenWrt Unified Syslog Acquisition
|
|
# Auto-generated by SecuBox CrowdSec Wizard
|
|
# Uses logread -f to stream all syslog entries
|
|
# Covers: system logs, SSH/Dropbear, firewall (iptables/nftables)
|
|
source: command
|
|
command: /sbin/logread -f
|
|
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}Created unified syslog 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"
|
|
|
|
# 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
|
|
local lapi_status="unavailable"
|
|
local lapi_url="http://127.0.0.1:8080"
|
|
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 decisions_count=0
|
|
if [ -x "$CSCLI" ]; then
|
|
decisions_count=$(run_cscli decisions list -o json 2>/dev/null | jsonfilter -e '@[*].decisions[*]' 2>/dev/null | wc -l)
|
|
fi
|
|
json_add_int "decisions_count" "${decisions_count:-0}"
|
|
|
|
json_dump
|
|
}
|
|
|
|
# Get CAPI blocklist metrics (decisions by origin and reason)
|
|
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
|
|
|
|
# Get all decisions
|
|
local decisions_output=""
|
|
decisions_output=$(run_cscli decisions list -o json 2>/dev/null)
|
|
|
|
if [ -z "$decisions_output" ] || [ "$decisions_output" = "null" ]; then
|
|
json_add_boolean "available" 1
|
|
json_add_int "total_capi" 0
|
|
json_add_int "total_local" 0
|
|
json_add_string "breakdown" "[]"
|
|
json_dump
|
|
return
|
|
fi
|
|
|
|
json_add_boolean "available" 1
|
|
|
|
# Count by origin
|
|
local capi_count=0
|
|
local local_count=0
|
|
|
|
# Parse decisions and count by origin
|
|
# The structure is: [{decisions: [...], ...}, ...]
|
|
# We need to count decisions where origin = "CAPI" or "crowdsec"
|
|
|
|
# Use a temp file for aggregation
|
|
local tmp_file="/tmp/capi_metrics.$$"
|
|
|
|
# Extract all decisions with their origin and scenario
|
|
echo "$decisions_output" | jsonfilter -e '@[*].decisions[*]' 2>/dev/null | while read -r decision; do
|
|
local origin=$(echo "$decisions_output" | jsonfilter -e '@[*].decisions[*].origin' 2>/dev/null | head -1)
|
|
local scenario=$(echo "$decisions_output" | jsonfilter -e '@[*].scenario' 2>/dev/null | head -1)
|
|
echo "$origin|$scenario"
|
|
done > "$tmp_file" 2>/dev/null
|
|
|
|
# Count CAPI decisions by scenario using awk
|
|
capi_count=$(echo "$decisions_output" | grep -o '"origin":"CAPI"' 2>/dev/null | wc -l)
|
|
local_count=$(echo "$decisions_output" | grep -o '"origin":"crowdsec"' 2>/dev/null | wc -l)
|
|
|
|
json_add_int "total_capi" "${capi_count:-0}"
|
|
json_add_int "total_local" "${local_count:-0}"
|
|
|
|
# Build breakdown by scenario for CAPI decisions
|
|
# Parse the JSON more carefully
|
|
json_add_array "breakdown"
|
|
|
|
# Extract unique scenarios and their counts from CAPI decisions
|
|
local scenarios=""
|
|
scenarios=$(echo "$decisions_output" | grep -oE '"scenario":"[^"]*"' | sort | uniq -c | sort -rn | head -10)
|
|
|
|
echo "$scenarios" | while read -r count scenario; do
|
|
if [ -n "$count" ] && [ -n "$scenario" ]; then
|
|
local name=$(echo "$scenario" | sed 's/"scenario":"//; s/"$//')
|
|
json_add_object ""
|
|
json_add_string "scenario" "$name"
|
|
json_add_int "count" "$count"
|
|
json_close_object
|
|
fi
|
|
done
|
|
|
|
json_close_array
|
|
|
|
rm -f "$tmp_file" 2>/dev/null
|
|
|
|
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
|
|
}
|
|
|
|
# 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":{},"repair_lapi":{},"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"}}'
|
|
;;
|
|
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
|
|
;;
|
|
repair_lapi)
|
|
repair_lapi
|
|
;;
|
|
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"
|
|
;;
|
|
*)
|
|
echo '{"error": "Unknown method"}'
|
|
;;
|
|
esac
|
|
;;
|
|
esac
|