feat(crowdsec-dashboard): Add LAPI-only mode, enrollment key storage, improved contrast

- Add Local Protection Mode banner when CAPI unavailable (LAPI still works)
- Save enrollment key to UCI config for future repairs
- Improve text contrast in wizard (better readability)
- Simplify LAPI repair function based on official OpenWrt approach
- Never delete CAPI credentials to avoid rate-limiting
- Add get_settings/save_settings RPC methods
- Bump version to 0.7.0-r27

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-13 14:30:17 +01:00
parent ca562f69cd
commit da5b88110a
6 changed files with 279 additions and 184 deletions

View File

@ -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/

View File

@ -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,

View File

@ -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;
}

View File

@ -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;

View File

@ -0,0 +1,4 @@
config settings 'main'
option enrollment_key ''
option machine_name ''
option auto_enroll '0'

View File

@ -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"}'
;;