diff --git a/package/secubox/luci-app-crowdsec-dashboard/Makefile b/package/secubox/luci-app-crowdsec-dashboard/Makefile index 6531e4a6..d0d76f1a 100644 --- a/package/secubox/luci-app-crowdsec-dashboard/Makefile +++ b/package/secubox/luci-app-crowdsec-dashboard/Makefile @@ -9,7 +9,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-crowdsec-dashboard PKG_VERSION:=0.7.0 -PKG_RELEASE:=24 +PKG_RELEASE:=27 PKG_ARCH:=all PKG_LICENSE:=Apache-2.0 @@ -29,6 +29,10 @@ define Package/luci-app-crowdsec-dashboard/conffiles endef define Package/luci-app-crowdsec-dashboard/install + # UCI config file + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./root/etc/config/crowdsec-dashboard $(1)/etc/config/ + # RPCD backend (MUST be 755 for ubus calls) $(INSTALL_DIR) $(1)/usr/libexec/rpcd $(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.crowdsec-dashboard $(1)/usr/libexec/rpcd/ diff --git a/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/crowdsec-dashboard/api.js b/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/crowdsec-dashboard/api.js index 66725014..ea11381f 100644 --- a/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/crowdsec-dashboard/api.js +++ b/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/crowdsec-dashboard/api.js @@ -289,6 +289,19 @@ var callRemoveHubItem = rpc.declare({ expect: { } }); +var callGetSettings = rpc.declare({ + object: 'luci.crowdsec-dashboard', + method: 'get_settings', + expect: { } +}); + +var callSaveSettings = rpc.declare({ + object: 'luci.crowdsec-dashboard', + method: 'save_settings', + params: ['enrollment_key', 'machine_name', 'auto_enroll'], + expect: { } +}); + function formatDuration(seconds) { if (!seconds) return 'N/A'; if (seconds < 60) return seconds + 's'; @@ -432,6 +445,10 @@ return baseclass.extend({ installHubItem: callInstallHubItem, removeHubItem: callRemoveHubItem, + // Settings Management + getSettings: callGetSettings, + saveSettings: callSaveSettings, + formatDuration: formatDuration, formatDate: formatDate, formatRelativeTime: formatRelativeTime, diff --git a/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/crowdsec-dashboard/wizard.css b/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/crowdsec-dashboard/wizard.css index fab2f181..0f12813a 100644 --- a/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/crowdsec-dashboard/wizard.css +++ b/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/crowdsec-dashboard/wizard.css @@ -148,7 +148,7 @@ body > .main > .main-right, } .wizard-step p { - color: #cbd5e1; + color: #e2e8f0; margin-bottom: 24px; line-height: 1.6; } @@ -259,7 +259,7 @@ body > .main > .main-right, .collection-desc { font-size: 12px; - color: #94a3b8; + color: #cbd5e1; margin-top: 4px; } @@ -362,7 +362,7 @@ body > .main > .main-right, } .status-value { - color: #94a3b8; + color: #cbd5e1; font-size: 14px; } 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 dff407b9..703b4fd5 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 @@ -74,7 +74,20 @@ return view.extend({ }, load: function() { - return this.runHealthCheck(); + var self = this; + // Load saved settings first, then run health check + return API.getSettings().then(function(settings) { + if (settings && settings.enrollment_key) { + self.config.enrollmentKey = settings.enrollment_key; + } + if (settings && settings.machine_name) { + self.config.machineName = settings.machine_name; + } + return self.runHealthCheck(); + }).catch(function() { + // Settings not available, continue with health check + return self.runHealthCheck(); + }); }, runHealthCheck: function() { @@ -211,6 +224,10 @@ return view.extend({ renderHealthCheck: function() { var self = this; + // Determine protection mode + var lapiOnly = this.health.lapiAvailable && !this.health.capiConnected; + var fullProtection = this.health.lapiAvailable && this.health.capiConnected; + var checks = [ { id: 'crowdsec', @@ -224,6 +241,7 @@ return view.extend({ label: _('Local API (LAPI)'), ok: this.health.lapiAvailable, status: this.health.lapiAvailable ? _('Available') : _('Unavailable'), + critical: true, // LAPI is critical for protection action: !this.health.lapiAvailable && this.health.crowdsecRunning ? _('Will repair') : null }, { @@ -231,7 +249,10 @@ return view.extend({ label: _('Central API (CAPI)'), ok: this.health.capiConnected, status: this.health.capiConnected ? _('Connected') : (this.health.capiEnrolled ? _('Enrolled but not connected') : _('Not registered')), - action: !this.health.capiConnected ? _('Enrollment required') : null + // CAPI is NOT critical - local protection works without it + critical: false, + warning: !this.health.capiConnected && this.health.lapiAvailable, + action: !this.health.capiConnected ? _('Optional - enroll for community blocklists') : null }, { id: 'bouncer', @@ -240,6 +261,7 @@ return view.extend({ status: this.health.bouncerRegistered ? (this.health.bouncerRunning ? _('Running') : _('Registered but stopped')) : _('Not registered'), + critical: true, // Bouncer is critical for enforcement action: !this.health.bouncerRegistered ? _('Will register') : (!this.health.bouncerRunning ? _('Will start') : null) }, { @@ -247,6 +269,7 @@ return view.extend({ label: _('nftables Rules'), ok: this.health.nftablesActive, status: this.health.nftablesActive ? _('Active') : _('Not loaded'), + critical: true, action: !this.health.nftablesActive ? _('Will configure') : null }, { @@ -265,16 +288,37 @@ return view.extend({ this.health.checking ? E('span', { 'class': 'spinning', 'style': 'margin-left: 12px; font-size: 14px;' }, '') : E([]) ]), + // Protection Mode Banner + this.health.lapiAvailable ? E('div', { + 'style': 'margin-bottom: 16px; padding: 12px 16px; border-radius: 8px; display: flex; align-items: center; ' + + (fullProtection ? 'background: rgba(34, 197, 94, 0.1); border: 1px solid rgba(34, 197, 94, 0.3);' : + 'background: rgba(234, 179, 8, 0.1); border: 1px solid rgba(234, 179, 8, 0.3);') + }, [ + E('span', { 'style': 'font-size: 24px; margin-right: 12px;' }, fullProtection ? '🛡️' : '⚡'), + E('div', {}, [ + E('strong', { 'style': 'color: ' + (fullProtection ? '#22c55e' : '#eab308') + ';' }, + fullProtection ? _('Full Protection Mode') : _('Local Protection Mode')), + E('div', { 'style': 'font-size: 0.85em; color: #cbd5e1; margin-top: 2px;' }, + fullProtection ? + _('Community blocklists + local detection active') : + _('Local auto-ban active. Enroll CAPI for community blocklists.')) + ]) + ]) : E([]), + E('div', { 'class': 'status-checks', 'style': 'display: grid; gap: 8px;' }, checks.map(function(check) { + // Determine icon and color based on ok/warning/critical status + var iconColor = check.ok ? '#22c55e' : (check.warning ? '#eab308' : (check.critical ? '#ef4444' : '#eab308')); + var icon = check.ok ? '✓' : (check.warning ? '⚠' : (check.critical ? '✗' : '⚠')); + return E('div', { 'class': 'check-item', 'style': 'display: flex; align-items: center; padding: 12px; background: rgba(15, 23, 42, 0.5); border-radius: 8px;' }, [ E('span', { 'class': 'check-icon', - 'style': 'font-size: 20px; margin-right: 12px; color: ' + (check.ok ? '#22c55e' : (check.critical ? '#ef4444' : '#eab308')) + ';' - }, check.ok ? '✓' : (check.critical ? '✗' : '⚠')), + 'style': 'font-size: 20px; margin-right: 12px; color: ' + iconColor + ';' + }, icon), E('div', { 'style': 'flex: 1;' }, [ E('strong', {}, check.label), E('div', { 'style': 'font-size: 0.85em; color: ' + (check.ok ? '#22c55e' : '#94a3b8') + ';' }, check.status) @@ -321,7 +365,7 @@ return view.extend({ 'style': 'margin-bottom: 20px; padding: 16px; background: rgba(15, 23, 42, 0.5); border-radius: 8px;' }, [ E('h4', { 'style': 'margin: 0 0 12px 0; color: #818cf8;' }, _('Console Enrollment (Optional)')), - E('p', { 'style': 'margin: 0 0 12px 0; font-size: 0.9em; color: #94a3b8;' }, + E('p', { 'style': 'margin: 0 0 12px 0; font-size: 0.9em; color: #cbd5e1;' }, _('Enroll to receive community blocklists. Leave empty to skip.')), E('input', { 'type': 'text', @@ -341,7 +385,7 @@ return view.extend({ 'value': this.config.machineName, 'input': function(ev) { self.config.machineName = ev.target.value; } }), - E('p', { 'style': 'margin: 8px 0 0 0; font-size: 0.85em; color: #64748b;' }, [ + E('p', { 'style': 'margin: 8px 0 0 0; font-size: 0.85em; color: #94a3b8;' }, [ _('After enrollment, validate on '), E('a', { 'href': 'https://app.crowdsec.net', 'target': '_blank', 'style': 'color: #818cf8;' }, 'app.crowdsec.net'), _('. Service will restart automatically.') @@ -371,7 +415,7 @@ return view.extend({ this.renderCollectionToggle('crowdsecurity/iptables', _('Firewall log parser'), this.config.firewallEnabled), this.renderCollectionToggle('crowdsecurity/http-cve', _('Web CVE protection'), this.config.httpEnabled), E('div', { 'style': 'margin-top: 12px;' }, [ - E('strong', { 'style': 'font-size: 0.9em; color: #94a3b8;' }, _('OpenWrt Parsers:')), + E('strong', { 'style': 'font-size: 0.9em; color: #cbd5e1;' }, _('OpenWrt Parsers:')), ]), this.renderCollectionToggle('crowdsecurity/syslog-logs', _('Syslog parser'), true, 'parser'), this.renderCollectionToggle('crowdsecurity/dropbear-logs', _('Dropbear SSH parser'), this.config.sshEnabled, 'parser') @@ -437,7 +481,7 @@ return view.extend({ }, checked ? '☑' : '☐'), E('div', { 'style': 'flex: 1;' }, [ E('strong', { 'style': 'display: block;' }, label), - E('span', { 'style': 'font-size: 0.85em; color: #64748b;' }, description) + E('span', { 'style': 'font-size: 0.85em; color: #94a3b8;' }, description) ]) ]); }, @@ -471,7 +515,7 @@ return view.extend({ }, checked ? '☑' : '☐'), E('div', { 'style': 'flex: 1;' }, [ E('code', { 'style': 'font-size: 0.9em;' }, name), - E('span', { 'style': 'margin-left: 8px; font-size: 0.85em; color: #64748b;' }, '— ' + description) + E('span', { 'style': 'margin-left: 8px; font-size: 0.85em; color: #94a3b8;' }, '— ' + description) ]) ]); }, @@ -482,7 +526,7 @@ return view.extend({ return E('div', { 'class': 'wizard-step', 'style': 'text-align: center; padding: 48px 24px;' }, [ E('div', { 'class': 'spinning', 'style': 'font-size: 48px; margin-bottom: 24px;' }, '⚙️'), E('h2', {}, _('Applying Configuration...')), - E('p', { 'style': 'color: #94a3b8; margin-bottom: 24px;' }, this.config.applyStep), + E('p', { 'style': 'color: #cbd5e1; margin-bottom: 24px;' }, this.config.applyStep), // Progress bar E('div', { 'style': 'max-width: 400px; margin: 0 auto 24px;' }, [ @@ -493,7 +537,7 @@ return view.extend({ 'style': 'height: 100%; width: ' + progressPercent + '%; background: linear-gradient(90deg, #667eea, #764ba2); transition: width 0.3s;' }) ]), - E('div', { 'style': 'margin-top: 8px; font-size: 0.9em; color: #64748b;' }, progressPercent + '%') + E('div', { 'style': 'margin-top: 8px; font-size: 0.9em; color: #94a3b8;' }, progressPercent + '%') ]), // Errors if any @@ -511,19 +555,37 @@ return view.extend({ }, renderComplete: function() { + var fullProtection = this.health.lapiAvailable && this.health.capiConnected; + var localOnly = this.health.lapiAvailable && !this.health.capiConnected; + return E('div', { 'class': 'wizard-step wizard-complete', 'style': 'text-align: center;' }, [ E('div', { 'class': 'success-hero', 'style': 'margin-bottom: 32px;' }, [ - E('div', { 'style': 'font-size: 64px; margin-bottom: 16px;' }, '🎉'), + E('div', { 'style': 'font-size: 64px; margin-bottom: 16px;' }, fullProtection ? '🛡️' : '⚡'), E('h2', {}, _('Setup Complete!')) ]), - E('p', { 'style': 'color: #94a3b8; margin-bottom: 32px;' }, + // Protection Mode Banner + E('div', { + 'style': 'max-width: 400px; margin: 0 auto 24px; padding: 16px; border-radius: 8px; ' + + (fullProtection ? 'background: rgba(34, 197, 94, 0.1); border: 1px solid rgba(34, 197, 94, 0.3);' : + 'background: rgba(234, 179, 8, 0.1); border: 1px solid rgba(234, 179, 8, 0.3);') + }, [ + E('strong', { 'style': 'color: ' + (fullProtection ? '#22c55e' : '#eab308') + ';' }, + fullProtection ? _('Full Protection Mode') : _('Local Protection Mode')), + E('div', { 'style': 'font-size: 0.85em; color: #cbd5e1; margin-top: 4px;' }, + fullProtection ? + _('Community blocklists + local detection active') : + _('Local auto-ban protects against attacks detected on your network')) + ]), + + E('p', { 'style': 'color: #cbd5e1; margin-bottom: 32px;' }, _('CrowdSec is now protecting your network.')), // Summary of what was done E('div', { 'style': 'max-width: 400px; margin: 0 auto 32px; text-align: left;' }, [ - this.health.lapiAvailable ? this.renderCompletedItem(_('LAPI available')) : E([]), - this.health.capiConnected ? this.renderCompletedItem(_('CAPI connected')) : E([]), + this.health.lapiAvailable ? this.renderCompletedItem(_('LAPI available (local detection)')) : E([]), + this.health.capiConnected ? this.renderCompletedItem(_('CAPI connected (community blocklists)')) : + this.renderWarningItem(_('CAPI not connected (local protection only)')), this.health.bouncerRegistered ? this.renderCompletedItem(_('Bouncer registered')) : E([]), this.health.nftablesActive ? this.renderCompletedItem(_('nftables rules active')) : E([]), this.health.collectionsInstalled > 0 ? @@ -562,6 +624,13 @@ return view.extend({ ]); }, + renderWarningItem: function(text) { + return E('div', { 'style': 'display: flex; align-items: center; padding: 8px 0;' }, [ + E('span', { 'style': 'color: #eab308; margin-right: 12px; font-size: 18px;' }, '⚠'), + E('span', { 'style': 'color: #cbd5e1;' }, text) + ]); + }, + handleApplyAll: function() { var self = this; this.config.applying = true; @@ -586,6 +655,13 @@ return view.extend({ this.config.enrollmentKey = keyInput ? keyInput.value.trim() : ''; this.config.machineName = nameInput ? nameInput.value.trim() : ''; + // Save enrollment key for future repairs (if provided) + if (this.config.enrollmentKey) { + API.saveSettings(this.config.enrollmentKey, this.config.machineName, '1').catch(function(err) { + console.log('[Wizard] Failed to save enrollment key:', err); + }); + } + // Define steps var steps = []; var stepWeight = 0; diff --git a/package/secubox/luci-app-crowdsec-dashboard/root/etc/config/crowdsec-dashboard b/package/secubox/luci-app-crowdsec-dashboard/root/etc/config/crowdsec-dashboard new file mode 100644 index 00000000..49fd472c --- /dev/null +++ b/package/secubox/luci-app-crowdsec-dashboard/root/etc/config/crowdsec-dashboard @@ -0,0 +1,4 @@ +config settings 'main' + option enrollment_key '' + option machine_name '' + option auto_enroll '0' 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 114e6354..fde5a5c2 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 @@ -981,182 +981,105 @@ repair_lapi() { json_init local steps_done="" local errors="" - local REPAIR_TIMEOUT=10 + local config_file="/etc/crowdsec/config.yaml" + local creds_file="/etc/crowdsec/local_api_credentials.yaml" + local db_file="/srv/crowdsec/data/crowdsec.db" + local capi_creds="/etc/crowdsec/online_api_credentials.yaml" - secubox_log "Starting LAPI repair..." + secubox_log "Starting LAPI repair (simplified)..." - # Step 1: Stop CrowdSec completely + # Step 1: Stop CrowdSec /etc/init.d/crowdsec stop >/dev/null 2>&1 - sleep 1 + sleep 2 killall crowdsec 2>/dev/null sleep 1 - steps_done="${steps_done}Stopped; " + 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" + # Step 3: Fix config paths if [ -f "$config_file" ]; then sed -i 's|^\(\s*\)data_dir:.*|\1data_dir: /srv/crowdsec/data/|' "$config_file" 2>/dev/null sed -i 's|^\(\s*\)db_path:.*|\1db_path: /srv/crowdsec/data/crowdsec.db|' "$config_file" 2>/dev/null - cp "$config_file" "$var_config" 2>/dev/null - steps_done="${steps_done}Config; " + steps_done="${steps_done}Config fixed; " else - errors="${errors}No config; " + errors="${errors}No config file; " fi - # Step 4: Remove corrupted credentials - local creds_file="/etc/crowdsec/local_api_credentials.yaml" + # Step 4: Reset LAPI credentials (delete old ones) rm -f "$creds_file" 2>/dev/null + steps_done="${steps_done}Creds cleared; " - # 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 5: Reset database (delete it, CrowdSec will recreate) + rm -f "$db_file" 2>/dev/null + steps_done="${steps_done}DB reset; " - # Step 6: Start CrowdSec 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 + # Step 6: Start CrowdSec via init (will auto-create DB and register machine) /etc/init.d/crowdsec start >/dev/null 2>&1 - sleep 3 + sleep 5 + steps_done="${steps_done}Started; " - # Step 11: Verify everything is working + # Step 7: Check if CrowdSec is running 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; " + + # Step 8: Register machine if credentials don't exist + if [ ! -s "$creds_file" ] || ! grep -q "password:" "$creds_file" 2>/dev/null; then + secubox_log "Registering local machine..." + if "$CSCLI" machines add -a -f "$creds_file" 2>/dev/null; then + steps_done="${steps_done}Machine registered; " + # Restart to pick up new credentials + /etc/init.d/crowdsec restart >/dev/null 2>&1 + sleep 3 else - errors="${errors}LAPI check failed; " + errors="${errors}Machine registration failed; " fi fi + + # Step 9: Verify LAPI is working + if run_with_timeout 5 "$CSCLI" lapi status >/dev/null 2>&1; then + lapi_ok=1 + steps_done="${steps_done}LAPI OK; " + else + errors="${errors}LAPI check failed; " + fi else - errors="${errors}Not running; " - # Get error from log + errors="${errors}CrowdSec not running; " 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}; " + log_err=$(tail -5 /var/log/crowdsec.log 2>/dev/null | grep -i "fatal\|error" | head -1) + [ -n "$log_err" ] && errors="${errors}Log: ${log_err}; " fi - # Step 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 - local capi_creds="/etc/crowdsec/online_api_credentials.yaml" - local capi_registered=0 - - # First attempt: try to register with existing credentials - if run_with_timeout 15 "$CSCLI" capi register >/dev/null 2>&1; then - capi_registered=1 - steps_done="${steps_done}CAPI registered; " - else - # Check if CAPI is already working - local capi_status="" - capi_status=$(run_with_timeout 5 "$CSCLI" capi status 2>&1) - if echo "$capi_status" | grep -qi "You can successfully interact with Central API"; then - capi_registered=1 - steps_done="${steps_done}CAPI OK; " - fi - fi - - # If CAPI still not working, try with fresh credentials - if [ "$capi_registered" = "0" ] && [ -f "$capi_creds" ]; then - secubox_log "CAPI registration failed, removing stale credentials and retrying..." - rm -f "$capi_creds" - steps_done="${steps_done}CAPI creds reset; " - - # Retry registration with clean slate + # Step 10: Try to connect CAPI if LAPI works (don't delete existing CAPI creds) + if [ "$lapi_ok" = "1" ]; then + local capi_ok=0 + # Check if CAPI is already working + local capi_status="" + capi_status=$(run_with_timeout 5 "$CSCLI" capi status 2>&1) + if echo "$capi_status" | grep -qi "You can successfully interact with Central API"; then + capi_ok=1 + steps_done="${steps_done}CAPI OK; " + elif [ ! -f "$capi_creds" ]; then + # No CAPI credentials - try to register (safe) if run_with_timeout 15 "$CSCLI" capi register >/dev/null 2>&1; then - capi_registered=1 - steps_done="${steps_done}CAPI re-registered; " + capi_ok=1 + steps_done="${steps_done}CAPI registered; " else - local capi_err="" - capi_err=$(run_with_timeout 10 "$CSCLI" capi register 2>&1) - errors="${errors}CAPI: ${capi_err}; " + errors="${errors}CAPI registration failed (will work in local mode); " fi - elif [ "$capi_registered" = "0" ]; then - errors="${errors}CAPI not registered; " + else + # CAPI credentials exist but not working - don't delete them! + steps_done="${steps_done}CAPI creds preserved; " fi fi + # Final result if [ "$lapi_ok" = "1" ]; then json_add_boolean "success" 1 json_add_string "message" "LAPI repaired successfully" @@ -1209,24 +1132,28 @@ repair_capi() { return fi - # Step 1: Backup and remove existing credentials + # Step 1: Check credentials and register only if needed + # IMPORTANT: Never delete existing credentials - this triggers rate limiting if [ -f "$capi_creds" ]; then - cp "$capi_creds" "${capi_creds}.bak" 2>/dev/null - rm -f "$capi_creds" - steps_done="${steps_done}Removed stale credentials; " - fi - - # Step 2: Register with CAPI - local reg_output="" - reg_output=$(run_with_timeout 20 "$CSCLI" capi register 2>&1) - if [ $? -eq 0 ]; then - steps_done="${steps_done}CAPI registered; " + steps_done="${steps_done}Credentials exist (preserved); " + secubox_log "CAPI credentials exist, trying to reconnect..." + # Try to reconnect with existing credentials by restarting crowdsec + /etc/init.d/crowdsec restart >/dev/null 2>&1 + sleep 3 else - # Check if error is about already registered - if echo "$reg_output" | grep -qi "already registered"; then - steps_done="${steps_done}Already registered; " + # No credentials - safe to register + secubox_log "No CAPI credentials, registering..." + local reg_output="" + reg_output=$(run_with_timeout 20 "$CSCLI" capi register 2>&1) + if [ $? -eq 0 ]; then + steps_done="${steps_done}CAPI registered; " else - errors="${errors}Registration failed: ${reg_output}; " + # Check if error is about already registered + if echo "$reg_output" | grep -qi "already registered"; then + steps_done="${steps_done}Already registered; " + else + errors="${errors}Registration failed: ${reg_output}; " + fi fi fi @@ -1369,20 +1296,27 @@ console_enroll() { capi_ok=1 secubox_log "CAPI already connected" else - # Try to register with CAPI - secubox_log "CAPI not connected, attempting registration..." - if run_with_timeout 15 "$CSCLI" capi register >/dev/null 2>&1; then - capi_ok=1 - secubox_log "CAPI registration successful" + # CAPI not connected - check if credentials exist + if [ -f "$capi_creds" ]; then + # Credentials exist but CAPI not working - try restarting CrowdSec + secubox_log "CAPI credentials exist but not connected, restarting CrowdSec..." + /etc/init.d/crowdsec restart >/dev/null 2>&1 + sleep 3 + # Check again after restart + capi_status=$(run_with_timeout 5 "$CSCLI" capi status 2>&1) + if echo "$capi_status" | grep -qi "You can successfully interact with Central API"; then + capi_ok=1 + secubox_log "CAPI connected after restart" + else + # Still not working but DO NOT delete credentials (rate limit protection) + secubox_log "CAPI still not connected - credentials preserved to avoid rate limit" + fi else - # If registration fails and credentials exist, try with fresh credentials - if [ -f "$capi_creds" ]; then - secubox_log "CAPI registration failed, removing stale credentials..." - rm -f "$capi_creds" - if run_with_timeout 15 "$CSCLI" capi register >/dev/null 2>&1; then - capi_ok=1 - secubox_log "CAPI re-registration successful" - fi + # No credentials - safe to register + secubox_log "No CAPI credentials, attempting registration..." + if run_with_timeout 15 "$CSCLI" capi register >/dev/null 2>&1; then + capi_ok=1 + secubox_log "CAPI registration successful" fi fi fi @@ -2073,10 +2007,60 @@ remove_hub_item() { json_dump } +# Get dashboard settings (enrollment key, etc.) +get_settings() { + json_init + + local enrollment_key="" + local machine_name="" + local auto_enroll="" + + # Read from UCI config + enrollment_key=$(uci -q get crowdsec-dashboard.main.enrollment_key 2>/dev/null) + machine_name=$(uci -q get crowdsec-dashboard.main.machine_name 2>/dev/null) + auto_enroll=$(uci -q get crowdsec-dashboard.main.auto_enroll 2>/dev/null) + + json_add_string "enrollment_key" "$enrollment_key" + json_add_string "machine_name" "$machine_name" + json_add_string "auto_enroll" "${auto_enroll:-0}" + + json_dump +} + +# Save dashboard settings +save_settings() { + local enrollment_key="$1" + local machine_name="$2" + local auto_enroll="$3" + + json_init + + # Ensure config section exists + uci -q get crowdsec-dashboard.main >/dev/null 2>&1 || { + uci set crowdsec-dashboard.main=settings + } + + # Save settings + [ -n "$enrollment_key" ] && uci set crowdsec-dashboard.main.enrollment_key="$enrollment_key" + [ -n "$machine_name" ] && uci set crowdsec-dashboard.main.machine_name="$machine_name" + [ -n "$auto_enroll" ] && uci set crowdsec-dashboard.main.auto_enroll="$auto_enroll" + + if uci commit crowdsec-dashboard 2>/dev/null; then + json_add_boolean "success" 1 + json_add_string "message" "Settings saved" + secubox_log "Saved enrollment settings" + else + json_add_boolean "success" 0 + json_add_string "error" "Failed to save settings" + fi + + json_dump +} + # 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":{},"repair_capi":{},"reset_wizard":{},"console_status":{},"console_enroll":{"key":"string","name":"string"},"console_disable":{},"service_control":{"action":"string"},"configure_acquisition":{"syslog_enabled":"string","firewall_enabled":"string","ssh_enabled":"string","http_enabled":"string","syslog_path":"string"},"acquisition_config":{},"acquisition_metrics":{},"health_check":{},"capi_metrics":{},"hub_available":{},"install_hub_item":{"item_type":"string","item_name":"string"},"remove_hub_item":{"item_type":"string","item_name":"string"}}' + 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":{},"repair_capi":{},"reset_wizard":{},"console_status":{},"console_enroll":{"key":"string","name":"string"},"console_disable":{},"service_control":{"action":"string"},"configure_acquisition":{"syslog_enabled":"string","firewall_enabled":"string","ssh_enabled":"string","http_enabled":"string","syslog_path":"string"},"acquisition_config":{},"acquisition_metrics":{},"health_check":{},"capi_metrics":{},"hub_available":{},"install_hub_item":{"item_type":"string","item_name":"string"},"remove_hub_item":{"item_type":"string","item_name":"string"},"get_settings":{},"save_settings":{"enrollment_key":"string","machine_name":"string","auto_enroll":"string"}}' ;; call) case "$2" in @@ -2249,6 +2233,16 @@ case "$1" in item_name=$(echo "$input" | jsonfilter -e '@.item_name' 2>/dev/null) remove_hub_item "$item_type" "$item_name" ;; + get_settings) + get_settings + ;; + save_settings) + read -r input + enrollment_key=$(echo "$input" | jsonfilter -e '@.enrollment_key' 2>/dev/null) + machine_name=$(echo "$input" | jsonfilter -e '@.machine_name' 2>/dev/null) + auto_enroll=$(echo "$input" | jsonfilter -e '@.auto_enroll' 2>/dev/null) + save_settings "$enrollment_key" "$machine_name" "$auto_enroll" + ;; *) echo '{"error": "Unknown method"}' ;;