diff --git a/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/view/crowdsec-dashboard/wizard.js b/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/view/crowdsec-dashboard/wizard.js index 511df4d9..bbe95c2a 100644 --- a/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/view/crowdsec-dashboard/wizard.js +++ b/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/view/crowdsec-dashboard/wizard.js @@ -644,10 +644,10 @@ return view.extend({ ]; var renderCheckboxItem = function(item, type) { - return E('div', { + var attrs = { 'class': 'collection-item', - 'data-' + type: item.name, 'data-type': type, + 'data-name': item.name, 'data-checked': item.preselected ? '1' : '0', 'style': 'display: flex; align-items: center; cursor: pointer;', 'click': function(ev) { @@ -661,7 +661,8 @@ return view.extend({ checkbox.style.color = newState ? '#22c55e' : '#94a3b8'; } } - }, [ + }; + return E('div', attrs, [ E('span', { 'class': 'checkbox-indicator', 'style': 'display: inline-block; font-size: 28px; margin-right: 16px; user-select: none; color: ' + (item.preselected ? '#22c55e' : '#94a3b8') + '; line-height: 1; min-width: 28px;' @@ -1143,16 +1144,16 @@ return view.extend({ handleInstallCollections: function() { // Read collections from data-checked attributes - var collectionItems = document.querySelectorAll('.collection-item[data-collection]'); + var collectionItems = document.querySelectorAll('.collection-item[data-type="collection"]'); var selectedCollections = Array.from(collectionItems) .filter(function(item) { return item.getAttribute('data-checked') === '1'; }) - .map(function(item) { return item.getAttribute('data-collection'); }); + .map(function(item) { return item.getAttribute('data-name'); }); // Read parsers from data-checked attributes - var parserItems = document.querySelectorAll('.collection-item[data-parser]'); + var parserItems = document.querySelectorAll('.collection-item[data-type="parser"]'); var selectedParsers = Array.from(parserItems) .filter(function(item) { return item.getAttribute('data-checked') === '1'; }) - .map(function(item) { return item.getAttribute('data-parser'); }); + .map(function(item) { return item.getAttribute('data-name'); }); console.log('[Wizard] Selected collections:', selectedCollections); console.log('[Wizard] Selected parsers:', selectedParsers); diff --git a/package/secubox/luci-app-crowdsec-dashboard/root/usr/libexec/rpcd/luci.crowdsec-dashboard b/package/secubox/luci-app-crowdsec-dashboard/root/usr/libexec/rpcd/luci.crowdsec-dashboard index c00c30bc..f805663a 100755 --- a/package/secubox/luci-app-crowdsec-dashboard/root/usr/libexec/rpcd/luci.crowdsec-dashboard +++ b/package/secubox/luci-app-crowdsec-dashboard/root/usr/libexec/rpcd/luci.crowdsec-dashboard @@ -1364,15 +1364,11 @@ configure_acquisition() { 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 + # 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 - 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 + steps_done="${steps_done}SSH detection enabled via syslog monitoring; " fi # Enable uhttpd syslog for HTTP auth logging diff --git a/package/secubox/secubox-auth-logger/Makefile b/package/secubox/secubox-auth-logger/Makefile new file mode 100644 index 00000000..5b0c87d9 --- /dev/null +++ b/package/secubox/secubox-auth-logger/Makefile @@ -0,0 +1,52 @@ +# Copyright (C) 2024 CyberMind.fr +# Licensed under Apache-2.0 + +include $(TOPDIR)/rules.mk + +PKG_NAME:=secubox-auth-logger +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 +PKG_ARCH:=all +PKG_LICENSE:=Apache-2.0 +PKG_MAINTAINER:=CyberMind + +include $(INCLUDE_DIR)/package.mk + +define Package/secubox-auth-logger + SECTION:=secubox + CATEGORY:=SecuBox + TITLE:=Authentication Failure Logger for CrowdSec + DEPENDS:=+rpcd +uhttpd + PKGARCH:=all +endef + +define Package/secubox-auth-logger/description + Logs authentication failures from LuCI/rpcd and Dropbear SSH + for CrowdSec detection. Patches rpcd to emit auth failure logs + to syslog in a format CrowdSec can parse. +endef + +define Build/Compile +endef + +define Package/secubox-auth-logger/install + $(INSTALL_DIR) $(1)/usr/lib/secubox + $(INSTALL_BIN) ./files/auth-monitor.sh $(1)/usr/lib/secubox/ + $(INSTALL_DIR) $(1)/etc/init.d + $(INSTALL_BIN) ./files/secubox-auth-logger.init $(1)/etc/init.d/secubox-auth-logger + $(INSTALL_DIR) $(1)/etc/crowdsec/parsers/s01-parse + $(INSTALL_DATA) ./files/openwrt-luci-auth.yaml $(1)/etc/crowdsec/parsers/s01-parse/ + $(INSTALL_DIR) $(1)/etc/uci-defaults + $(INSTALL_BIN) ./files/99-secubox-auth-logger $(1)/etc/uci-defaults/ +endef + +define Package/secubox-auth-logger/postinst +#!/bin/sh +[ -n "$${IPKG_INSTROOT}" ] || { + /etc/init.d/secubox-auth-logger enable + /etc/init.d/secubox-auth-logger start +} +exit 0 +endef + +$(eval $(call BuildPackage,secubox-auth-logger)) diff --git a/package/secubox/secubox-auth-logger/files/99-secubox-auth-logger b/package/secubox/secubox-auth-logger/files/99-secubox-auth-logger new file mode 100644 index 00000000..90e72f71 --- /dev/null +++ b/package/secubox/secubox-auth-logger/files/99-secubox-auth-logger @@ -0,0 +1,34 @@ +#!/bin/sh +# SecuBox Auth Logger - Post-install configuration +# Enables verbose logging for Dropbear and uhttpd + +# Note: Dropbear 2024.86 does NOT support -v flag +# Auth monitoring relies on parsing existing syslog messages +# The auth-monitor.sh script watches logread for auth failures + +# Enable uhttpd syslog +if [ -f /etc/config/uhttpd ]; then + uci set uhttpd.main.syslog='1' + uci commit uhttpd + /etc/init.d/uhttpd restart 2>/dev/null +fi + +# Create auth failures log file +touch /var/log/auth-failures.log +chmod 644 /var/log/auth-failures.log + +# Add acquisition for CrowdSec if installed +if [ -d /etc/crowdsec/acquis.d ]; then + cat > /etc/crowdsec/acquis.d/secubox-auth.yaml << 'EOF' +# SecuBox Auth Failure Acquisition +# Reads from /var/log/messages for secubox-auth tagged messages +filenames: + - /var/log/messages +labels: + type: syslog +EOF + # Restart CrowdSec to pick up new acquisition + /etc/init.d/crowdsec restart 2>/dev/null +fi + +exit 0 diff --git a/package/secubox/secubox-auth-logger/files/auth-monitor.sh b/package/secubox/secubox-auth-logger/files/auth-monitor.sh new file mode 100644 index 00000000..f6a30caa --- /dev/null +++ b/package/secubox/secubox-auth-logger/files/auth-monitor.sh @@ -0,0 +1,107 @@ +#!/bin/sh +# SecuBox Auth Monitor - Lightweight auth failure detection +# Monitors SSH and LuCI login failures, logs to syslog for CrowdSec +# Copyright (C) 2024 CyberMind.fr + +LOG_TAG="secubox-auth" + +# Track recent IPs to avoid duplicate logging +TRACK_FILE="/tmp/auth-monitor-track" +touch "$TRACK_FILE" + +log_failure() { + local service="$1" + local ip="$2" + local user="${3:-root}" + + # Avoid duplicate logs within 60 seconds + local key="${service}:${ip}" + local now=$(date +%s) + + if grep -q "^${key}:" "$TRACK_FILE" 2>/dev/null; then + local last=$(grep "^${key}:" "$TRACK_FILE" | cut -d: -f3) + if [ $((now - last)) -lt 60 ]; then + return + fi + sed -i "/^${key}:/d" "$TRACK_FILE" + fi + echo "${key}:${now}" >> "$TRACK_FILE" + + # Log to syslog - CrowdSec will parse this + logger -t "$LOG_TAG" -p auth.warning "authentication failure for $user from $ip via $service" +} + +# Monitor logread for auth failures +monitor_logs() { + logread -f 2>/dev/null | while read line; do + + # OpenSSH failures + # Format: sshd[xxx]: Failed password for root from 1.2.3.4 port xxx + if echo "$line" | grep -qi "sshd.*failed.*password"; then + ip=$(echo "$line" | grep -oE 'from [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | awk '{print $2}') + user=$(echo "$line" | grep -oE 'for [^ ]+' | awk '{print $2}') + [ -n "$ip" ] && log_failure "ssh" "$ip" "$user" + fi + + # OpenSSH invalid user + # Format: sshd[xxx]: Invalid user xxx from 1.2.3.4 + if echo "$line" | grep -qi "sshd.*invalid.user"; then + ip=$(echo "$line" | grep -oE 'from [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | awk '{print $2}') + user=$(echo "$line" | grep -oE 'user [^ ]+' | awk '{print $2}') + [ -n "$ip" ] && log_failure "ssh" "$ip" "${user:-unknown}" + fi + + # Dropbear failures (if ever enabled) + # Format: dropbear[xxx]: Bad password attempt for 'root' from x.x.x.x:port + if echo "$line" | grep -qi "dropbear.*bad.password"; then + ip=$(echo "$line" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | head -1) + user=$(echo "$line" | grep -oE "for '[^']+'" | tr -d "'" | awk '{print $2}') + [ -n "$ip" ] && log_failure "ssh" "$ip" "$user" + fi + + # uhttpd/LuCI - Look for failed POST to login + # When uhttpd syslog is enabled, failed logins redirect with 302/403 + if echo "$line" | grep -qi "uhttpd.*POST.*/cgi-bin/luci"; then + ip=$(echo "$line" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | head -1) + # Check if this is followed by a failure indicator + if echo "$line" | grep -qE "403|401"; then + [ -n "$ip" ] && log_failure "luci" "$ip" + fi + fi + + # rpcd session errors + if echo "$line" | grep -qi "rpcd.*access.denied\|ubus.*error"; then + ip=$(echo "$line" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | head -1) + [ -n "$ip" ] && log_failure "luci" "$ip" + fi + + done +} + +# Cleanup old tracking entries (older than 5 minutes) +cleanup_tracking() { + while true; do + sleep 300 + local now=$(date +%s) + local tmp=$(mktemp) + while read line; do + local ts=$(echo "$line" | cut -d: -f3) + if [ $((now - ts)) -lt 300 ]; then + echo "$line" + fi + done < "$TRACK_FILE" > "$tmp" + mv "$tmp" "$TRACK_FILE" + done +} + +case "$1" in + start) + cleanup_tracking & + monitor_logs + ;; + *) + echo "Usage: $0 start" + echo "Monitors auth failures and logs to syslog for CrowdSec" + exit 1 + ;; +esac diff --git a/package/secubox/secubox-auth-logger/files/openwrt-luci-auth.yaml b/package/secubox/secubox-auth-logger/files/openwrt-luci-auth.yaml new file mode 100644 index 00000000..379011e0 --- /dev/null +++ b/package/secubox/secubox-auth-logger/files/openwrt-luci-auth.yaml @@ -0,0 +1,20 @@ +# CrowdSec Parser for SecuBox Auth Logger +# Parses authentication failures from LuCI/uhttpd and Dropbear +# Format: secubox-auth: Authentication failure for from via + +name: secubox/openwrt-luci-auth +description: "Parse SecuBox auth failure logs for LuCI and SSH" +filter: "evt.Parsed.program == 'secubox-auth'" +onsuccess: next_stage + +nodes: + - grok: + pattern: "Authentication failure for %{USERNAME:user} from %{IP:source_ip} via %{WORD:service}" + apply_on: message + statics: + - meta: log_type + value: auth_failure + - meta: service + expression: evt.Parsed.service + - meta: source_ip + expression: evt.Parsed.source_ip diff --git a/package/secubox/secubox-auth-logger/files/secubox-auth-logger.init b/package/secubox/secubox-auth-logger/files/secubox-auth-logger.init new file mode 100644 index 00000000..ce2c98bb --- /dev/null +++ b/package/secubox/secubox-auth-logger/files/secubox-auth-logger.init @@ -0,0 +1,23 @@ +#!/bin/sh /etc/rc.common +# SecuBox Authentication Logger +# Monitors SSH and LuCI auth failures for CrowdSec +# Copyright (C) 2024 CyberMind.fr + +START=99 +STOP=10 +USE_PROCD=1 + +PROG=/usr/lib/secubox/auth-monitor.sh + +start_service() { + procd_open_instance + procd_set_param command "$PROG" start + procd_set_param respawn + procd_set_param stdout 0 + procd_set_param stderr 1 + procd_close_instance +} + +service_triggers() { + procd_add_reload_trigger "secubox-auth-logger" +}