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:
parent
4078b4d7a4
commit
e5b6d1dd87
@ -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,
|
||||
|
||||
@ -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');
|
||||
|
||||
@ -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"}'
|
||||
;;
|
||||
|
||||
@ -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" ]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user