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:
parent
ca562f69cd
commit
da5b88110a
@ -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/
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
config settings 'main'
|
||||
option enrollment_key ''
|
||||
option machine_name ''
|
||||
option auto_enroll '0'
|
||||
@ -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"}'
|
||||
;;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user