feat: Add CrowdSec Console enrollment to setup wizard (v0.6.0-r28)

- Add console_status, console_enroll, console_disable RPCD methods
- Insert Console enrollment as Step 2 in the 7-step wizard
- Add API declarations and ACL permissions for console operations
- Enable share_manual_decisions, share_tainted, share_context by default on enrollment

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-08 14:40:24 +01:00
parent 4078b4d7a4
commit e5b6d1dd87
4 changed files with 392 additions and 63 deletions

View File

@ -197,6 +197,26 @@ var callRepairLapi = rpc.declare({
expect: { }
});
// Console Methods
var callConsoleStatus = rpc.declare({
object: 'luci.crowdsec-dashboard',
method: 'console_status',
expect: { }
});
var callConsoleEnroll = rpc.declare({
object: 'luci.crowdsec-dashboard',
method: 'console_enroll',
params: ['key', 'name'],
expect: { }
});
var callConsoleDisable = rpc.declare({
object: 'luci.crowdsec-dashboard',
method: 'console_disable',
expect: { }
});
function formatDuration(seconds) {
if (!seconds) return 'N/A';
if (seconds < 60) return seconds + 's';
@ -318,6 +338,11 @@ return baseclass.extend({
getWizardState: callWizardState,
repairLapi: callRepairLapi,
// Console Methods
getConsoleStatus: callConsoleStatus,
consoleEnroll: callConsoleEnroll,
consoleDisable: callConsoleDisable,
formatDuration: formatDuration,
formatDate: formatDate,
formatRelativeTime: formatRelativeTime,

View File

@ -10,7 +10,7 @@
return view.extend({
wizardData: {
currentStep: 1,
totalSteps: 6,
totalSteps: 7,
// Step 1 data
crowdsecRunning: false,
@ -18,23 +18,29 @@ return view.extend({
lapiRepairing: false,
lapiRepairAttempted: false,
// Step 2 data
// Step 2 data (Console Enrollment)
consoleEnrolled: false,
enrolling: false,
enrollmentKey: '',
machineName: '',
// Step 3 data (Hub Update)
hubUpdating: false,
hubUpdated: false,
// Step 3 data
// Step 4 data (Collections)
collections: [],
installing: false,
installed: false,
installStatus: '',
installedCount: 0,
// Step 4 data
// Step 5 data (Bouncer)
configuring: false,
bouncerConfigured: false,
apiKey: '',
// Step 5 data
// Step 6 data (Services)
starting: false,
enabling: false,
enabled: false,
@ -42,7 +48,7 @@ return view.extend({
nftablesActive: false,
lapiConnected: false,
// Step 6 data
// Step 7 data (Complete)
blockedIPs: 0,
activeDecisions: 0
},
@ -50,14 +56,17 @@ return view.extend({
load: function() {
return Promise.all([
API.getStatus(),
API.checkWizardNeeded()
API.checkWizardNeeded(),
API.getConsoleStatus()
]).then(L.bind(function(results) {
var status = results[0];
var wizardNeeded = results[1];
var consoleStatus = results[2];
// Update wizard data from status
this.wizardData.crowdsecRunning = status && status.crowdsec === 'running';
this.wizardData.lapiAvailable = status && status.lapi_status === 'available';
this.wizardData.consoleEnrolled = consoleStatus && consoleStatus.enrolled;
// Auto-repair LAPI if CrowdSec is running but LAPI is not available
if (this.wizardData.crowdsecRunning && !this.wizardData.lapiAvailable && !this.wizardData.lapiRepairAttempted) {
@ -78,6 +87,7 @@ return view.extend({
return {
status: newStatus,
wizardNeeded: wizardNeeded,
consoleStatus: consoleStatus,
repaired: true
};
}, this));
@ -86,6 +96,7 @@ return view.extend({
return {
status: status,
wizardNeeded: wizardNeeded,
consoleStatus: consoleStatus,
repairFailed: true
};
}
@ -94,12 +105,16 @@ return view.extend({
return {
status: status,
wizardNeeded: wizardNeeded
wizardNeeded: wizardNeeded,
consoleStatus: consoleStatus
};
}, this));
},
render: function(data) {
// Initialize theme
Theme.init();
// Load wizard CSS
var head = document.head || document.getElementsByTagName('head')[0];
var cssLink = E('link', {
@ -109,6 +124,14 @@ return view.extend({
});
head.appendChild(cssLink);
// Load SecuBox theme CSS
var themeLink = E('link', {
'rel': 'stylesheet',
'type': 'text/css',
'href': L.resource('secubox-theme/secubox-theme.css')
});
head.appendChild(themeLink);
var container = E('div', { 'class': 'wizard-container' });
// Create stepper
@ -123,11 +146,12 @@ return view.extend({
createStepper: function() {
var steps = [
{ number: 1, title: _('Welcome') },
{ number: 2, title: _('Update Hub') },
{ number: 3, title: _('Install Packs') },
{ number: 4, title: _('Configure Bouncer') },
{ number: 5, title: _('Enable Services') },
{ number: 6, title: _('Complete') }
{ number: 2, title: _('Console') },
{ number: 3, title: _('Update Hub') },
{ number: 4, title: _('Install Packs') },
{ number: 5, title: _('Configure Bouncer') },
{ number: 6, title: _('Enable Services') },
{ number: 7, title: _('Complete') }
];
var stepper = E('div', { 'class': 'wizard-stepper' });
@ -158,15 +182,17 @@ return view.extend({
case 1:
return this.renderStep1(data);
case 2:
return this.renderStep2(data);
return this.renderStep2Console(data);
case 3:
return this.renderStep3(data);
return this.renderStep3Hub(data);
case 4:
return this.renderStep4(data);
return this.renderStep4Collections(data);
case 5:
return this.renderStep5(data);
return this.renderStep5Bouncer(data);
case 6:
return this.renderStep6(data);
return this.renderStep6Services(data);
case 7:
return this.renderStep7Complete(data);
default:
return E('div', {}, _('Invalid step'));
}
@ -225,18 +251,18 @@ return view.extend({
E('button', {
'class': 'cbi-button cbi-button-action',
'click': L.bind(this.handleManualRepair, this)
}, _('🔧 Retry Repair'))
}, _('Retry Repair'))
]) : E([]),
// Info box
E('div', { 'class': 'info-box' }, [
E('h4', {}, _('What will be configured:')),
E('ul', {}, [
E('li', {}, _('Enroll in CrowdSec Console for community blocklists')),
E('li', {}, _('Update CrowdSec hub with latest collections')),
E('li', {}, _('Install essential security scenarios')),
E('li', {}, _('Register and configure firewall bouncer')),
E('li', {}, _('Enable automatic IP blocking via nftables')),
E('li', {}, _('Start all services'))
E('li', {}, _('Enable automatic IP blocking via nftables'))
])
]),
@ -257,12 +283,107 @@ return view.extend({
ev.stopPropagation();
this.goToStep(2);
}, this)
}, _('Next'))
}, _('Next'))
])
]);
},
renderStep2: function(data) {
renderStep2Console: function(data) {
var consoleStatus = data && data.consoleStatus ? data.consoleStatus : {};
var enrolled = consoleStatus.enrolled || this.wizardData.consoleEnrolled;
return E('div', { 'class': 'wizard-step' }, [
E('h2', {}, _('CrowdSec Console Enrollment')),
E('p', {}, _('Connect to CrowdSec Console to receive community blocklists and monitor your security.')),
// Benefits
E('div', { 'class': 'info-box', 'style': 'margin-bottom: 24px;' }, [
E('h4', {}, _('Benefits of enrolling:')),
E('ul', {}, [
E('li', {}, _('Access to community-curated blocklists')),
E('li', {}, _('Real-time threat intelligence sharing')),
E('li', {}, _('Centralized monitoring dashboard')),
E('li', {}, _('Free tier available'))
])
]),
// Enrollment status
enrolled ?
E('div', { 'class': 'success-message', 'style': 'margin: 24px 0; padding: 16px; background: rgba(34, 197, 94, 0.15); border-radius: 8px;' }, [
E('span', { 'class': 'check-icon success', 'style': 'font-size: 24px; margin-right: 12px;' }, '✓'),
E('div', { 'style': 'display: inline-block;' }, [
E('strong', { 'style': 'display: block; color: #16a34a;' }, _('Already enrolled!')),
E('span', { 'style': 'color: #15803d; font-size: 0.9em;' },
_('Your instance is connected to CrowdSec Console'))
])
]) :
E('div', { 'class': 'enrollment-form', 'style': 'margin: 24px 0;' }, [
// Enrollment key input
E('div', { 'class': 'form-group', 'style': 'margin-bottom: 16px;' }, [
E('label', { 'style': 'display: block; margin-bottom: 8px; font-weight: 500;' },
_('Enrollment Key')),
E('input', {
'type': 'text',
'id': 'console-enrollment-key',
'class': 'cbi-input-text',
'placeholder': _('Paste your enrollment key from console.crowdsec.net'),
'style': 'width: 100%; padding: 12px; font-family: monospace;'
}),
E('p', { 'style': 'margin-top: 8px; color: var(--cyber-text-secondary, #666); font-size: 0.9em;' }, [
_('Get your key from '),
E('a', {
'href': 'https://app.crowdsec.net/security-engines',
'target': '_blank',
'style': 'color: var(--cyber-accent-primary, #667eea);'
}, 'app.crowdsec.net')
])
]),
// Machine name (optional)
E('div', { 'class': 'form-group', 'style': 'margin-bottom: 16px;' }, [
E('label', { 'style': 'display: block; margin-bottom: 8px; font-weight: 500;' },
_('Machine Name (optional)')),
E('input', {
'type': 'text',
'id': 'console-machine-name',
'class': 'cbi-input-text',
'placeholder': _('e.g., secubox-router'),
'style': 'width: 100%; padding: 12px;'
})
]),
// Enrolling status
this.wizardData.enrolling ?
E('div', { 'style': 'text-align: center; padding: 16px;' }, [
E('div', { 'class': 'spinning' }),
E('p', {}, _('Enrolling...'))
]) : E([]),
// Enroll button
E('button', {
'class': 'cbi-button cbi-button-action',
'style': 'width: 100%; padding: 12px; font-size: 1em;',
'disabled': this.wizardData.enrolling ? true : null,
'click': L.bind(this.handleConsoleEnroll, this)
}, _('Enroll in CrowdSec Console'))
]),
// Navigation
E('div', { 'class': 'wizard-nav' }, [
E('button', {
'class': 'cbi-button',
'click': L.bind(this.goToStep, this, 1)
}, _('Back')),
E('button', {
'class': 'cbi-button',
'click': L.bind(this.goToStep, this, 3),
'disabled': this.wizardData.enrolling ? true : null
}, enrolled ? _('Next') : _('Skip'))
])
]);
},
renderStep3Hub: function(data) {
return E('div', { 'class': 'wizard-step' }, [
E('h2', {}, _('Update CrowdSec Hub')),
E('p', {}, _('Fetching the latest security collections from CrowdSec hub...')),
@ -282,13 +403,13 @@ return view.extend({
E('div', { 'class': 'wizard-nav' }, [
E('button', {
'class': 'cbi-button',
'click': L.bind(this.goToStep, this, 1)
}, _('Back')),
'click': L.bind(this.goToStep, this, 2)
}, _('Back')),
this.wizardData.hubUpdated ?
E('button', {
'class': 'cbi-button cbi-button-positive',
'click': L.bind(this.goToStep, this, 3)
}, _('Next')) :
'click': L.bind(this.goToStep, this, 4)
}, _('Next')) :
E('button', {
'class': 'cbi-button cbi-button-action',
'click': L.bind(this.handleUpdateHub, this)
@ -297,7 +418,7 @@ return view.extend({
]);
},
renderStep3: function(data) {
renderStep4Collections: function(data) {
var recommendedCollections = [
{ name: 'crowdsecurity/linux', description: 'Base Linux scenarios', preselected: true },
{ name: 'crowdsecurity/ssh-bf', description: 'SSH brute force protection', preselected: true },
@ -356,24 +477,24 @@ return view.extend({
E('div', { 'class': 'wizard-nav' }, [
E('button', {
'class': 'cbi-button',
'click': L.bind(this.goToStep, this, 2),
'click': L.bind(this.goToStep, this, 3),
'disabled': this.wizardData.installing ? true : null
}, _('Back')),
}, _('Back')),
E('button', {
'class': 'cbi-button',
'click': L.bind(this.goToStep, this, 4),
'click': L.bind(this.goToStep, this, 5),
'disabled': this.wizardData.installing ? true : null
}, _('Skip')),
E('button', {
'class': 'cbi-button cbi-button-positive',
'click': L.bind(this.handleInstallCollections, this),
'disabled': (this.wizardData.installing || this.wizardData.installed) ? true : null
}, this.wizardData.installed ? _('Installed') : _('Install Selected'))
}, this.wizardData.installed ? _('Installed') : _('Install Selected'))
])
]);
},
renderStep4: function(data) {
renderStep5Bouncer: function(data) {
return E('div', { 'class': 'wizard-step' }, [
E('h2', {}, _('Configure Firewall Bouncer')),
E('p', {}, _('The firewall bouncer will automatically block malicious IPs using nftables.')),
@ -465,24 +586,24 @@ return view.extend({
E('div', { 'class': 'wizard-nav' }, [
E('button', {
'class': 'cbi-button',
'click': L.bind(this.goToStep, this, 3),
'click': L.bind(this.goToStep, this, 4),
'disabled': this.wizardData.configuring ? true : null
}, _('Back')),
}, _('Back')),
this.wizardData.bouncerConfigured ?
E('button', {
'class': 'cbi-button cbi-button-positive',
'click': L.bind(this.goToStep, this, 5)
}, _('Next')) :
'click': L.bind(this.goToStep, this, 6)
}, _('Next')) :
E('button', {
'class': 'cbi-button cbi-button-action',
'click': L.bind(function(ev) { console.log('[Wizard] Configure Bouncer button clicked!'); ev.preventDefault(); ev.stopPropagation(); this.handleConfigureBouncer(); }, this),
'click': L.bind(this.handleConfigureBouncer, this),
'disabled': this.wizardData.configuring ? true : null
}, _('Configure Bouncer'))
])
]);
},
renderStep5: function(data) {
renderStep6Services: function(data) {
return E('div', { 'class': 'wizard-step' }, [
E('h2', {}, _('Enable & Start Services')),
E('p', {}, _('Starting the firewall bouncer service and verifying operation...')),
@ -492,22 +613,22 @@ return view.extend({
E('div', { 'class': 'status-item' }, [
E('span', { 'class': 'status-label' }, _('Enable at boot:')),
E('span', { 'class': 'status-value' + (this.wizardData.enabled ? ' success' : '') },
this.wizardData.enabled ? _('Enabled') : this.wizardData.enabling ? _('Enabling...') : _('Not enabled'))
this.wizardData.enabled ? _('Enabled') : this.wizardData.enabling ? _('Enabling...') : _('Not enabled'))
]),
E('div', { 'class': 'status-item' }, [
E('span', { 'class': 'status-label' }, _('Service status:')),
E('span', { 'class': 'status-value' + (this.wizardData.running ? ' success' : '') },
this.wizardData.running ? _('Running') : this.wizardData.starting ? _('Starting...') : _('Stopped'))
this.wizardData.running ? _('Running') : this.wizardData.starting ? _('Starting...') : _('Stopped'))
]),
E('div', { 'class': 'status-item' }, [
E('span', { 'class': 'status-label' }, _('nftables rules:')),
E('span', { 'class': 'status-value' + (this.wizardData.nftablesActive ? ' success' : '') },
this.wizardData.nftablesActive ? _('Loaded') : _('Not loaded'))
this.wizardData.nftablesActive ? _('Loaded') : _('Not loaded'))
]),
E('div', { 'class': 'status-item' }, [
E('span', { 'class': 'status-label' }, _('LAPI connection:')),
E('span', { 'class': 'status-value' + (this.wizardData.lapiConnected ? ' success' : '') },
this.wizardData.lapiConnected ? _('Connected') : _('Not connected'))
this.wizardData.lapiConnected ? _('Connected') : _('Not connected'))
])
]),
@ -515,24 +636,24 @@ return view.extend({
E('div', { 'class': 'wizard-nav' }, [
E('button', {
'class': 'cbi-button',
'click': L.bind(this.goToStep, this, 4),
'click': L.bind(this.goToStep, this, 5),
'disabled': this.wizardData.starting ? true : null
}, _('Back')),
}, _('Back')),
(this.wizardData.enabled && this.wizardData.running && this.wizardData.nftablesActive && this.wizardData.lapiConnected) ?
E('button', {
'class': 'cbi-button cbi-button-positive',
'click': L.bind(this.goToStep, this, 6)
}, _('Next')) :
'click': L.bind(this.goToStep, this, 7)
}, _('Next')) :
E('button', {
'class': 'cbi-button cbi-button-action',
'click': L.bind(function(ev) { console.log('[Wizard] Start Services button clicked!'); ev.preventDefault(); ev.stopPropagation(); this.handleStartServices(); }, this),
'click': L.bind(this.handleStartServices, this),
'disabled': this.wizardData.starting ? true : null
}, _('Start Services'))
])
]);
},
renderStep6: function(data) {
renderStep7Complete: function(data) {
return E('div', { 'class': 'wizard-step wizard-complete' }, [
E('div', { 'class': 'success-hero' }, [
E('div', { 'class': 'success-icon' }, '🎉'),
@ -550,6 +671,14 @@ return view.extend({
E('div', { 'class': 'summary-desc' }, _('Running and monitoring'))
])
]),
this.wizardData.consoleEnrolled ?
E('div', { 'class': 'summary-item' }, [
E('span', { 'class': 'check-icon success' }, '✓'),
E('div', {}, [
E('strong', {}, _('Console Enrolled')),
E('div', { 'class': 'summary-desc' }, _('Receiving community blocklists'))
])
]) : E([]),
E('div', { 'class': 'summary-item' }, [
E('span', { 'class': 'check-icon success' }, '✓'),
E('div', {}, [
@ -607,7 +736,12 @@ return view.extend({
E('li', {}, _('View real-time decisions in the Decisions tab')),
E('li', {}, _('Monitor alerts in the Alerts tab')),
E('li', {}, _('Check blocked IPs in the Bouncers tab')),
E('li', {}, _('Review metrics in the Metrics tab'))
this.wizardData.consoleEnrolled ?
E('li', {}, [
_('Monitor from '),
E('a', { 'href': 'https://app.crowdsec.net', 'target': '_blank' }, 'CrowdSec Console')
]) :
E('li', {}, _('Consider enrolling in CrowdSec Console for blocklists'))
])
]),
@ -619,7 +753,7 @@ return view.extend({
'click': function() {
window.location.href = L.url('admin', 'secubox', 'security', 'crowdsec', 'overview');
}
}, _('Go to Dashboard'))
}, _('Go to Dashboard'))
])
]);
},
@ -644,6 +778,42 @@ return view.extend({
}
},
handleConsoleEnroll: function() {
var keyInput = document.getElementById('console-enrollment-key');
var nameInput = document.getElementById('console-machine-name');
var key = keyInput ? keyInput.value.trim() : '';
var name = nameInput ? nameInput.value.trim() : '';
if (!key) {
ui.addNotification(null, E('p', _('Please enter an enrollment key')), 'warning');
return;
}
console.log('[Wizard] Enrolling with key:', key.substring(0, 10) + '...');
this.wizardData.enrolling = true;
this.refreshView();
return API.consoleEnroll(key, name).then(L.bind(function(result) {
console.log('[Wizard] Enrollment result:', result);
this.wizardData.enrolling = false;
if (result && result.success) {
this.wizardData.consoleEnrolled = true;
ui.addNotification(null, E('p', _('Successfully enrolled in CrowdSec Console!')), 'success');
// Auto-advance after 2 seconds
setTimeout(L.bind(function() { this.goToStep(3); }, this), 2000);
} else {
ui.addNotification(null, E('p', _('Enrollment failed: ') + (result.error || result.output || 'Unknown error')), 'error');
}
this.refreshView();
}, this)).catch(L.bind(function(err) {
console.error('[Wizard] Enrollment error:', err);
this.wizardData.enrolling = false;
ui.addNotification(null, E('p', _('Enrollment failed: ') + err.message), 'error');
this.refreshView();
}, this));
},
handleUpdateHub: function() {
console.log('[Wizard] handleUpdateHub called');
this.wizardData.hubUpdating = true;
@ -686,7 +856,7 @@ return view.extend({
console.log('[Wizard] Selected collections:', selected);
if (selected.length === 0) {
this.goToStep(4);
this.goToStep(5);
return;
}
@ -711,7 +881,7 @@ return view.extend({
this.refreshView();
// Auto-advance after 2 seconds
setTimeout(L.bind(function() { this.goToStep(4); }, this), 2000);
setTimeout(L.bind(function() { this.goToStep(5); }, this), 2000);
}, this)).catch(L.bind(function(err) {
this.wizardData.installing = false;
ui.addNotification(null, E('p', _('Installation failed: %s').format(err.message)), 'error');
@ -760,8 +930,8 @@ return view.extend({
this.refreshView();
// Auto-advance after 2 seconds
console.log('[Wizard] Auto-advancing to Step 5 in 2 seconds...');
setTimeout(L.bind(function() { this.goToStep(5); }, this), 2000);
console.log('[Wizard] Auto-advancing to Step 6 in 2 seconds...');
setTimeout(L.bind(function() { this.goToStep(6); }, this), 2000);
}, this)).catch(L.bind(function(err) {
console.error('[Wizard] Configuration error:', err);
this.wizardData.configuring = false;
@ -809,7 +979,8 @@ return view.extend({
return API.getBouncers();
}, this)).then(L.bind(function(bouncers) {
console.log('[Wizard] Bouncers list:', bouncers);
var bouncer = (bouncers || []).find(function(b) {
var bouncerList = bouncers && bouncers.bouncers ? bouncers.bouncers : bouncers;
var bouncer = (bouncerList || []).find(function(b) {
return b.name === 'crowdsec-firewall-bouncer';
});
@ -823,13 +994,13 @@ return view.extend({
this.refreshView();
// Success if enabled, running, and nftables active
// LAPI connection may take a few seconds to establish, so it's optional
if (this.wizardData.enabled && this.wizardData.running &&
// LAPI connection may take a few seconds to establish, so it's optional
if (this.wizardData.enabled && this.wizardData.running &&
this.wizardData.nftablesActive) {
console.log('[Wizard] All critical services started! Auto-advancing to Step 6...');
console.log('[Wizard] All critical services started! Auto-advancing to Step 7...');
ui.addNotification(null, E('p', _('Services started successfully!')), 'info');
// Auto-advance after 2 seconds
setTimeout(L.bind(function() { this.goToStep(6); }, this), 2000);
setTimeout(L.bind(function() { this.goToStep(7); }, this), 2000);
} else {
console.log('[Wizard] Service startup incomplete');
ui.addNotification(null, E('p', _('Service startup incomplete. Check status and retry.')), 'warning');

View File

@ -931,10 +931,128 @@ repair_lapi() {
json_dump
}
# Console enrollment status
get_console_status() {
json_init
check_cscli
local enrolled=0
local name=""
local company=""
# Check if console is enrolled by checking if there's a console config
if [ -f "/etc/crowdsec/console.yaml" ]; then
# Check if the console is actually configured (has credentials)
if grep -q "share_manual_decisions\|share_tainted\|share_context" /etc/crowdsec/console.yaml 2>/dev/null; then
enrolled=1
fi
fi
# Try to get console status from cscli
local console_status
console_status=$($CSCLI console status 2>/dev/null)
if [ -n "$console_status" ]; then
# Check if enrolled by looking for "Enrolled" or similar in output
if echo "$console_status" | grep -qi "enrolled\|connected\|active"; then
enrolled=1
fi
# Extract name if present
name=$(echo "$console_status" | grep -i "name\|machine" | head -1 | awk -F: '{print $2}' | xargs 2>/dev/null)
fi
json_add_boolean "enrolled" "$enrolled"
json_add_string "name" "$name"
# Get share settings if enrolled
if [ "$enrolled" = "1" ] && [ -f "/etc/crowdsec/console.yaml" ]; then
local share_manual=$(grep "share_manual_decisions:" /etc/crowdsec/console.yaml 2>/dev/null | awk '{print $2}')
local share_tainted=$(grep "share_tainted:" /etc/crowdsec/console.yaml 2>/dev/null | awk '{print $2}')
local share_context=$(grep "share_context:" /etc/crowdsec/console.yaml 2>/dev/null | awk '{print $2}')
json_add_boolean "share_manual_decisions" "$([ \"$share_manual\" = \"true\" ] && echo 1 || echo 0)"
json_add_boolean "share_tainted" "$([ \"$share_tainted\" = \"true\" ] && echo 1 || echo 0)"
json_add_boolean "share_context" "$([ \"$share_context\" = \"true\" ] && echo 1 || echo 0)"
fi
json_dump
}
# Console enroll
console_enroll() {
local key="$1"
local name="$2"
json_init
check_cscli
if [ -z "$key" ]; then
json_add_boolean "success" 0
json_add_string "error" "Enrollment key is required"
json_dump
return
fi
secubox_log "Enrolling CrowdSec Console with key..."
# Build enroll command
local enroll_cmd="$CSCLI console enroll $key"
if [ -n "$name" ]; then
enroll_cmd="$enroll_cmd --name \"$name\""
fi
# Run enrollment
local output
output=$(eval "$enroll_cmd" 2>&1)
local result=$?
if [ "$result" -eq 0 ]; then
json_add_boolean "success" 1
json_add_string "message" "Successfully enrolled in CrowdSec Console"
json_add_string "output" "$output"
secubox_log "Console enrollment successful"
# Enable sharing options by default
$CSCLI console enable share_manual_decisions >/dev/null 2>&1
$CSCLI console enable share_tainted >/dev/null 2>&1
$CSCLI console enable share_context >/dev/null 2>&1
else
json_add_boolean "success" 0
json_add_string "error" "Enrollment failed"
json_add_string "output" "$output"
secubox_log "Console enrollment failed: $output"
fi
json_dump
}
# Console disable (unenroll)
console_disable() {
json_init
check_cscli
secubox_log "Disabling CrowdSec Console enrollment..."
# Disable all sharing
$CSCLI console disable share_manual_decisions >/dev/null 2>&1
$CSCLI console disable share_tainted >/dev/null 2>&1
$CSCLI console disable share_context >/dev/null 2>&1
# Remove console config
if [ -f "/etc/crowdsec/console.yaml" ]; then
rm -f /etc/crowdsec/console.yaml
fi
json_add_boolean "success" 1
json_add_string "message" "Console enrollment disabled"
secubox_log "Console enrollment disabled"
json_dump
}
# 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":{}}'
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":{},"console_status":{},"console_enroll":{"key":"string","name":"string"},"console_disable":{}}'
;;
call)
case "$2" in
@ -1048,6 +1166,18 @@ case "$1" in
repair_lapi)
repair_lapi
;;
console_status)
get_console_status
;;
console_enroll)
read -r input
key=$(echo "$input" | jsonfilter -e '@.key' 2>/dev/null)
name=$(echo "$input" | jsonfilter -e '@.name' 2>/dev/null)
console_enroll "$key" "$name"
;;
console_disable)
console_disable
;;
*)
echo '{"error": "Unknown method"}'
;;

View File

@ -20,7 +20,8 @@
"firewall_bouncer_config",
"nftables_stats",
"check_wizard_needed",
"wizard_state"
"wizard_state",
"console_status"
],
"file": [ "read", "stat" ]
},
@ -40,7 +41,9 @@
"delete_bouncer",
"control_firewall_bouncer",
"update_firewall_bouncer_config",
"repair_lapi"
"repair_lapi",
"console_enroll",
"console_disable"
]
},
"uci": [ "crowdsec-dashboard" ]