diff --git a/package/secubox/CROWDSEC-OPENWRT-24.md b/package/secubox/CROWDSEC-OPENWRT-24.md new file mode 100644 index 00000000..5dceac17 --- /dev/null +++ b/package/secubox/CROWDSEC-OPENWRT-24.md @@ -0,0 +1,380 @@ +# CrowdSec Integration for OpenWrt 24.10+ (SecuBox) + +## Overview + +This documentation covers the complete CrowdSec security solution integration for OpenWrt 24.10+ with fw4/nftables support. The integration consists of two packages: + +1. **secubox-crowdsec-setup**: Automated installation script +2. **luci-app-secubox-crowdsec**: LuCI web interface dashboard + +## Requirements + +### Hardware +- Minimum 256MB RAM +- Minimum 50MB available flash storage +- ARM64, ARMv7, x86_64, or MIPS architecture + +### Software +- OpenWrt 24.10 or later +- fw4 with nftables (default in OpenWrt 24.10+) +- Internet connectivity for initial setup + +## Quick Installation + +### Method 1: Using the Setup Script + +```bash +# Install dependencies +opkg update +opkg install secubox-crowdsec-setup + +# Run the automated setup +secubox-crowdsec-setup --install +``` + +### Method 2: Manual Installation + +```bash +# Update package lists +opkg update + +# Install required packages +opkg install crowdsec crowdsec-firewall-bouncer-nftables syslog-ng4 + +# Install LuCI dashboard (optional) +opkg install luci-app-secubox-crowdsec +``` + +## Architecture + +``` + +-----------------------+ + | OpenWrt System | + +-----------------------+ + | + +--------------+--------------+ + | | + +-------v-------+ +---------v---------+ + | syslog-ng4 | | logread -f | + | (UDP 5140) | | (fallback) | + +-------+-------+ +---------+---------+ + | | + +-------------+---------------+ + | + +-------v-------+ + | CrowdSec | + | (LAPI :8080) | + +-------+-------+ + | + +-------------+-------------+ + | | + +-------v-------+ +--------v--------+ + | Local CAPI | | CrowdSec | + | (blocklists) | | Hub (parsers, | + +---------------+ | scenarios) | + +-----------------+ + | + +-------------v-------------+ + | crowdsec-firewall-bouncer | + | (nftables mode) | + +-------------+-------------+ + | + +--------v--------+ + | nftables fw4 | + | (crowdsec/ | + | crowdsec6) | + +-----------------+ +``` + +## Components + +### 1. syslog-ng4 Configuration + +Located at `/etc/syslog-ng/syslog-ng.conf`, this configuration: +- Captures all system logs via Unix socket +- Forwards logs to CrowdSec via UDP port 5140 +- Writes local copies to `/tmp/log/` for debugging + +Key sources monitored: +- System logs (`/dev/log`) +- Kernel messages (`/proc/kmsg`) +- Authentication logs (SSH, login attempts) + +### 2. CrowdSec Engine + +Configuration directory: `/etc/crowdsec/` + +Main components: +- **config.yaml**: Main configuration file +- **acquis.d/**: Acquisition configuration files +- **parsers/**: Log parsing rules +- **scenarios/**: Attack detection scenarios +- **hub/**: Downloaded hub content + +Data storage: `/srv/crowdsec/data/` + +### 3. Firewall Bouncer + +Configuration: `/etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml` + +Creates nftables tables: +- `ip crowdsec`: IPv4 blocking +- `ip6 crowdsec6`: IPv6 blocking + +### 4. LuCI Dashboard + +Accessible via: **Services > CrowdSec** + +Features: +- Dashboard with service status +- Active decisions (bans) management +- Security alerts viewer +- Collections management +- Settings configuration + +## UCI Configuration + +The UCI configuration file `/etc/config/crowdsec` contains: + +```uci +config crowdsec 'crowdsec' + option enabled '1' + option data_dir '/srv/crowdsec/data' + option db_path '/srv/crowdsec/data/crowdsec.db' + +config acquisition 'acquisition' + option syslog_enabled '1' + option firewall_enabled '1' + option ssh_enabled '1' + option http_enabled '0' + +config hub 'hub' + option auto_install '1' + option collections 'crowdsecurity/linux crowdsecurity/sshd crowdsecurity/iptables' + option update_interval '7' + +config bouncer 'bouncer' + option enabled '1' + option ipv4 '1' + option ipv6 '1' + option deny_action 'drop' + option deny_log '1' + option update_frequency '10s' +``` + +## Default Collections + +The following collections are installed by default: + +| Collection | Description | +|------------|-------------| +| `crowdsecurity/linux` | Linux system security | +| `crowdsecurity/sshd` | SSH brute-force protection | +| `crowdsecurity/iptables` | Firewall logs parsing | +| `crowdsecurity/http-cve` | HTTP CVE exploits | + +## Command Reference + +### Service Management + +```bash +# CrowdSec service +/etc/init.d/crowdsec start|stop|restart|enable|disable + +# Firewall bouncer +/etc/init.d/crowdsec-firewall-bouncer start|stop|restart|enable|disable + +# Syslog-ng +/etc/init.d/syslog-ng start|stop|restart|enable|disable +``` + +### cscli Commands + +```bash +# View status +cscli lapi status +cscli capi status + +# Decision management +cscli decisions list +cscli decisions add --ip --duration 24h --reason "Manual ban" +cscli decisions delete --ip + +# Alert management +cscli alerts list +cscli alerts list --since 24h + +# Collection management +cscli collections list +cscli collections install crowdsecurity/nginx +cscli collections remove crowdsecurity/nginx + +# Hub management +cscli hub update +cscli hub upgrade + +# Bouncer management +cscli bouncers list + +# Metrics +cscli metrics +``` + +### nftables Commands + +```bash +# List CrowdSec tables +nft list tables | grep crowdsec + +# Show blocked IPs (IPv4) +nft list set ip crowdsec crowdsec-blacklists + +# Show blocked IPs (IPv6) +nft list set ip6 crowdsec6 crowdsec6-blacklists +``` + +## Troubleshooting + +### CrowdSec not starting + +```bash +# Check logs +logread | grep crowdsec +cat /var/log/crowdsec.log + +# Verify configuration +cscli config show +``` + +### LAPI unavailable + +```bash +# Check if CrowdSec is running +pgrep crowdsec + +# Repair machine registration +cscli machines add localhost --auto --force +/etc/init.d/crowdsec restart +``` + +### Bouncer not blocking + +```bash +# Check bouncer status +pgrep -f crowdsec-firewall-bouncer + +# Verify nftables tables +nft list tables + +# Check bouncer API key +cat /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml | grep api_key +``` + +### syslog-ng issues + +```bash +# Check if running +pgrep syslog-ng + +# Test configuration +syslog-ng -s + +# Check UDP listener +netstat -uln | grep 5140 +``` + +### No alerts being generated + +```bash +# Check acquisition +cscli metrics show acquisition + +# Test log parsing +echo "Failed password for root from 192.168.1.100 port 22222 ssh2" | \ + cscli parsers inspect crowdsecurity/sshd-logs +``` + +## Uninstallation + +```bash +# Using setup script +secubox-crowdsec-setup --uninstall + +# Manual removal +/etc/init.d/crowdsec-firewall-bouncer stop +/etc/init.d/crowdsec stop +/etc/init.d/syslog-ng stop + +opkg remove luci-app-secubox-crowdsec +opkg remove crowdsec-firewall-bouncer-nftables +opkg remove crowdsec +opkg remove syslog-ng4 + +# Clean nftables +nft delete table ip crowdsec +nft delete table ip6 crowdsec6 + +# Re-enable logd +/etc/init.d/log enable +/etc/init.d/log start +``` + +## Security Considerations + +### Whitelist Local Networks + +The default configuration includes a whitelist for RFC1918 private networks: +- 10.0.0.0/8 +- 172.16.0.0/12 +- 192.168.0.0/16 +- 127.0.0.0/8 + +This prevents accidental blocking of local management access. + +### Bouncer API Key + +The bouncer API key is automatically generated during setup and stored in: +- `/etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml` +- UCI config: `crowdsec.bouncer.api_key` + +### Log Retention + +Logs in `/tmp/log/` are stored in tmpfs and cleared on reboot. For persistent logging, configure syslog-ng to write to overlay storage. + +## Performance Optimization + +For resource-constrained devices: + +1. **Reduce update frequency**: + ```bash + uci set crowdsec.bouncer.update_frequency='30s' + uci commit crowdsec + ``` + +2. **Disable IPv6 if not used**: + ```bash + uci set crowdsec.bouncer.ipv6='0' + uci commit crowdsec + ``` + +3. **Limit collections**: + Only install collections relevant to your setup. + +## Integration with SecuBox + +This CrowdSec integration is part of the SecuBox security suite for OpenWrt. It works alongside other SecuBox components: + +- SecuBox Firewall +- SecuBox VPN +- SecuBox DNS filtering +- SecuBox Monitoring + +## License + +MIT License - Copyright (C) 2025 CyberMind.fr + +## Support + +- GitHub Issues: https://github.com/secubox/secubox-openwrt +- Documentation: https://docs.secubox.io +- CrowdSec Docs: https://docs.crowdsec.net diff --git a/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/crowdsec-dashboard/api.js b/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/crowdsec-dashboard/api.js index ec112083..7a3a81dc 100644 --- a/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/crowdsec-dashboard/api.js +++ b/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/crowdsec-dashboard/api.js @@ -230,6 +230,20 @@ var callServiceControl = rpc.declare({ expect: { } }); +// Acquisition Methods +var callConfigureAcquisition = rpc.declare({ + object: 'luci.crowdsec-dashboard', + method: 'configure_acquisition', + params: ['syslog_enabled', 'firewall_enabled', 'ssh_enabled', 'http_enabled', 'syslog_path'], + expect: { } +}); + +var callAcquisitionConfig = rpc.declare({ + object: 'luci.crowdsec-dashboard', + method: 'acquisition_config', + expect: { } +}); + function formatDuration(seconds) { if (!seconds) return 'N/A'; if (seconds < 60) return seconds + 's'; @@ -360,6 +374,10 @@ return baseclass.extend({ // Service Control serviceControl: callServiceControl, + // Acquisition Methods + configureAcquisition: callConfigureAcquisition, + getAcquisitionConfig: callAcquisitionConfig, + formatDuration: formatDuration, formatDate: formatDate, formatRelativeTime: formatRelativeTime, diff --git a/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/view/crowdsec-dashboard/wizard.js b/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/view/crowdsec-dashboard/wizard.js index d0c83aef..a3749163 100644 --- a/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/view/crowdsec-dashboard/wizard.js +++ b/package/secubox/luci-app-crowdsec-dashboard/htdocs/luci-static/resources/view/crowdsec-dashboard/wizard.js @@ -21,7 +21,7 @@ return view.extend({ wizardData: { currentStep: 1, - totalSteps: 7, + totalSteps: 8, // Step 1 data crowdsecRunning: false, @@ -39,20 +39,29 @@ return view.extend({ hubUpdating: false, hubUpdated: false, - // Step 4 data (Collections) + // Step 4 data (Log Acquisition) + acquisitionConfigured: false, + acquisitionConfiguring: false, + syslogEnabled: true, + firewallEnabled: true, + sshEnabled: true, + httpEnabled: false, + syslogPath: '/var/log/messages', + + // Step 5 data (Collections) collections: [], installing: false, installed: false, installStatus: '', installedCount: 0, - // Step 5 data (Bouncer) + // Step 6 data (Bouncer) configuring: false, bouncerConfigured: false, apiKey: '', resetting: false, - // Step 6 data (Services) + // Step 7 data (Services) starting: false, enabling: false, enabled: false, @@ -60,7 +69,7 @@ return view.extend({ nftablesActive: false, lapiConnected: false, - // Step 7 data (Complete) + // Step 8 data (Complete) blockedIPs: 0, activeDecisions: 0 }, @@ -171,10 +180,11 @@ return view.extend({ { number: 1, title: _('Welcome') }, { 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') } + { number: 4, title: _('Log Sources') }, + { number: 5, title: _('Install Packs') }, + { number: 6, title: _('Configure Bouncer') }, + { number: 7, title: _('Enable Services') }, + { number: 8, title: _('Complete') } ]; var stepper = E('div', { 'class': 'wizard-stepper' }); @@ -209,13 +219,15 @@ return view.extend({ case 3: return this.renderStep3Hub(data); case 4: - return this.renderStep4Collections(data); + return this.renderStep4Acquisition(data); case 5: - return this.renderStep5Bouncer(data); + return this.renderStep5Collections(data); case 6: - return this.renderStep6Services(data); + return this.renderStep6Bouncer(data); case 7: - return this.renderStep7Complete(data); + return this.renderStep7Services(data); + case 8: + return this.renderStep8Complete(data); default: return E('div', {}, _('Invalid step')); } @@ -441,11 +453,187 @@ return view.extend({ ]); }, - renderStep4Collections: function(data) { + renderStep4Acquisition: function(data) { + var self = this; + return E('div', { 'class': 'wizard-step' }, [ + E('h2', {}, _('Configure Log Acquisition')), + E('p', {}, _('Select which log sources CrowdSec should monitor for security threats.')), + + // Info box about log acquisition + E('div', { 'class': 'info-box', 'style': 'margin-bottom: 24px;' }, [ + E('h4', {}, _('About Log Acquisition')), + E('p', { 'style': 'margin: 0; font-size: 0.9em; color: var(--cyber-text-secondary, #94a3b8);' }, + _('CrowdSec analyzes logs to detect malicious activity. Enable the log sources relevant to your setup.')) + ]), + + // Log source toggles + E('div', { 'class': 'config-section' }, [ + // Syslog + E('div', { + 'class': 'config-group', + 'id': 'acq-syslog', + 'data-checked': this.wizardData.syslogEnabled ? '1' : '0', + 'style': 'display: flex; align-items: center; cursor: pointer; padding: 12px; background: rgba(15, 23, 42, 0.5); border-radius: 8px; margin-bottom: 12px;', + 'click': function(ev) { + var item = ev.currentTarget; + var currentState = item.getAttribute('data-checked') === '1'; + var newState = !currentState; + item.setAttribute('data-checked', newState ? '1' : '0'); + self.wizardData.syslogEnabled = newState; + var checkbox = item.querySelector('.checkbox-indicator'); + if (checkbox) { + checkbox.textContent = newState ? '☑' : '☐'; + checkbox.style.color = newState ? '#22c55e' : '#94a3b8'; + } + } + }, [ + E('span', { + 'class': 'checkbox-indicator', + 'style': 'display: inline-block; font-size: 24px; margin-right: 12px; user-select: none; color: ' + (this.wizardData.syslogEnabled ? '#22c55e' : '#94a3b8') + '; min-width: 24px;' + }, this.wizardData.syslogEnabled ? '☑' : '☐'), + E('div', { 'style': 'flex: 1;' }, [ + E('strong', {}, _('System Syslog')), + E('div', { 'style': 'font-size: 0.85em; color: var(--cyber-text-secondary, #94a3b8);' }, + _('Monitor /var/log/messages for system events')) + ]) + ]), + + // Firewall logs + E('div', { + 'class': 'config-group', + 'id': 'acq-firewall', + 'data-checked': this.wizardData.firewallEnabled ? '1' : '0', + 'style': 'display: flex; align-items: center; cursor: pointer; padding: 12px; background: rgba(15, 23, 42, 0.5); border-radius: 8px; margin-bottom: 12px;', + 'click': function(ev) { + var item = ev.currentTarget; + var currentState = item.getAttribute('data-checked') === '1'; + var newState = !currentState; + item.setAttribute('data-checked', newState ? '1' : '0'); + self.wizardData.firewallEnabled = newState; + var checkbox = item.querySelector('.checkbox-indicator'); + if (checkbox) { + checkbox.textContent = newState ? '☑' : '☐'; + checkbox.style.color = newState ? '#22c55e' : '#94a3b8'; + } + } + }, [ + E('span', { + 'class': 'checkbox-indicator', + 'style': 'display: inline-block; font-size: 24px; margin-right: 12px; user-select: none; color: ' + (this.wizardData.firewallEnabled ? '#22c55e' : '#94a3b8') + '; min-width: 24px;' + }, this.wizardData.firewallEnabled ? '☑' : '☐'), + E('div', { 'style': 'flex: 1;' }, [ + E('strong', {}, _('Firewall Logs')), + E('div', { 'style': 'font-size: 0.85em; color: var(--cyber-text-secondary, #94a3b8);' }, + _('Monitor iptables/nftables for port scans (requires iptables collection)')) + ]) + ]), + + // SSH/Dropbear logs + E('div', { + 'class': 'config-group', + 'id': 'acq-ssh', + 'data-checked': this.wizardData.sshEnabled ? '1' : '0', + 'style': 'display: flex; align-items: center; cursor: pointer; padding: 12px; background: rgba(15, 23, 42, 0.5); border-radius: 8px; margin-bottom: 12px;', + 'click': function(ev) { + var item = ev.currentTarget; + var currentState = item.getAttribute('data-checked') === '1'; + var newState = !currentState; + item.setAttribute('data-checked', newState ? '1' : '0'); + self.wizardData.sshEnabled = newState; + var checkbox = item.querySelector('.checkbox-indicator'); + if (checkbox) { + checkbox.textContent = newState ? '☑' : '☐'; + checkbox.style.color = newState ? '#22c55e' : '#94a3b8'; + } + } + }, [ + E('span', { + 'class': 'checkbox-indicator', + 'style': 'display: inline-block; font-size: 24px; margin-right: 12px; user-select: none; color: ' + (this.wizardData.sshEnabled ? '#22c55e' : '#94a3b8') + '; min-width: 24px;' + }, this.wizardData.sshEnabled ? '☑' : '☐'), + E('div', { 'style': 'flex: 1;' }, [ + E('strong', {}, _('SSH/Dropbear Logs')), + E('div', { 'style': 'font-size: 0.85em; color: var(--cyber-text-secondary, #94a3b8);' }, + _('Detect SSH brute force attacks (via syslog)')) + ]) + ]), + + // HTTP logs + E('div', { + 'class': 'config-group', + 'id': 'acq-http', + 'data-checked': this.wizardData.httpEnabled ? '1' : '0', + 'style': 'display: flex; align-items: center; cursor: pointer; padding: 12px; background: rgba(15, 23, 42, 0.5); border-radius: 8px; margin-bottom: 12px;', + 'click': function(ev) { + var item = ev.currentTarget; + var currentState = item.getAttribute('data-checked') === '1'; + var newState = !currentState; + item.setAttribute('data-checked', newState ? '1' : '0'); + self.wizardData.httpEnabled = newState; + var checkbox = item.querySelector('.checkbox-indicator'); + if (checkbox) { + checkbox.textContent = newState ? '☑' : '☐'; + checkbox.style.color = newState ? '#22c55e' : '#94a3b8'; + } + } + }, [ + E('span', { + 'class': 'checkbox-indicator', + 'style': 'display: inline-block; font-size: 24px; margin-right: 12px; user-select: none; color: ' + (this.wizardData.httpEnabled ? '#22c55e' : '#94a3b8') + '; min-width: 24px;' + }, this.wizardData.httpEnabled ? '☑' : '☐'), + E('div', { 'style': 'flex: 1;' }, [ + E('strong', {}, _('HTTP Server Logs')), + E('div', { 'style': 'font-size: 0.85em; color: var(--cyber-text-secondary, #94a3b8);' }, + _('Monitor uHTTPd/nginx web server (disabled by default)')) + ]) + ]), + + // Note about OpenWrt log handling + E('div', { 'class': 'info-box', 'style': 'margin-top: 16px; padding: 12px; background: rgba(102, 126, 234, 0.1); border-radius: 8px; border: 1px solid rgba(102, 126, 234, 0.3);' }, [ + E('p', { 'style': 'margin: 0; font-size: 0.9em; color: var(--cyber-text-secondary, #94a3b8);' }, [ + E('strong', { 'style': 'color: var(--cyber-accent-primary, #667eea);' }, _('Note: ')), + _('OpenWrt uses logread command instead of log files. CrowdSec will stream logs via "logread -f". All enabled sources (syslog, SSH, firewall) share the same log stream.') + ]) + ]) + ]), + + // Configuration status + this.wizardData.acquisitionConfigured ? + E('div', { 'class': 'success-message', 'style': 'margin-top: 16px;' }, [ + E('span', { 'class': 'check-icon success' }, '✓'), + _('Log acquisition configured successfully!') + ]) : + this.wizardData.acquisitionConfiguring ? + E('div', { 'class': 'spinning', 'style': 'margin-top: 16px;' }, _('Configuring acquisition...')) : + E([]), + + // Navigation + E('div', { 'class': 'wizard-nav' }, [ + E('button', { + 'class': 'cbi-button', + 'click': L.bind(this.goToStep, this, 3), + 'disabled': this.wizardData.acquisitionConfiguring ? true : null + }, _('Back')), + this.wizardData.acquisitionConfigured ? + E('button', { + 'class': 'cbi-button cbi-button-positive', + 'click': L.bind(this.goToStep, this, 5) + }, _('Next')) : + E('button', { + 'class': 'cbi-button cbi-button-action', + 'click': L.bind(this.handleConfigureAcquisition, this), + 'disabled': this.wizardData.acquisitionConfiguring ? true : null + }, _('Apply Configuration')) + ]) + ]); + }, + + renderStep5Collections: function(data) { var recommendedCollections = [ - { name: 'crowdsecurity/linux', description: 'Base Linux scenarios', preselected: true }, + { name: 'crowdsecurity/linux', description: 'Base Linux scenarios (SSH, syslog)', preselected: true }, + { name: 'crowdsecurity/iptables', description: 'Firewall log parser (port scan detection)', preselected: this.wizardData.firewallEnabled }, { name: 'crowdsecurity/ssh-bf', description: 'SSH brute force protection', preselected: true }, - { name: 'crowdsecurity/http-cve', description: 'Web CVE protection', preselected: true }, + { name: 'crowdsecurity/http-cve', description: 'Web CVE protection', preselected: this.wizardData.httpEnabled }, { name: 'crowdsecurity/whitelist-good-actors', description: 'Whitelist known good bots', preselected: false } ]; @@ -500,12 +688,12 @@ 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.installing ? true : null }, _('Back')), E('button', { 'class': 'cbi-button', - 'click': L.bind(this.goToStep, this, 5), + 'click': L.bind(this.goToStep, this, 6), 'disabled': this.wizardData.installing ? true : null }, _('Skip')), E('button', { @@ -517,7 +705,7 @@ return view.extend({ ]); }, - renderStep5Bouncer: function(data) { + renderStep6Bouncer: function(data) { var self = this; return E('div', { 'class': 'wizard-step' }, [ E('h2', {}, _('Configure Firewall Bouncer')), @@ -628,13 +816,13 @@ 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.configuring ? true : null }, _('Back')), this.wizardData.bouncerConfigured ? E('button', { 'class': 'cbi-button cbi-button-positive', - 'click': L.bind(this.goToStep, this, 6) + 'click': L.bind(this.goToStep, this, 7) }, _('Next')) : E('button', { 'class': 'cbi-button cbi-button-action', @@ -645,7 +833,7 @@ return view.extend({ ]); }, - renderStep6Services: function(data) { + renderStep7Services: function(data) { return E('div', { 'class': 'wizard-step' }, [ E('h2', {}, _('Enable & Start Services')), E('p', {}, _('Starting the firewall bouncer service and verifying operation...')), @@ -678,13 +866,13 @@ return view.extend({ E('div', { 'class': 'wizard-nav' }, [ E('button', { 'class': 'cbi-button', - 'click': L.bind(this.goToStep, this, 5), + 'click': L.bind(this.goToStep, this, 6), 'disabled': this.wizardData.starting ? true : null }, _('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, 7) + 'click': L.bind(this.goToStep, this, 8) }, _('Next')) : E('button', { 'class': 'cbi-button cbi-button-action', @@ -695,7 +883,7 @@ return view.extend({ ]); }, - renderStep7Complete: function(data) { + renderStep8Complete: function(data) { return E('div', { 'class': 'wizard-step wizard-complete' }, [ E('div', { 'class': 'success-hero' }, [ E('div', { 'class': 'success-icon' }, '🎉'), @@ -888,6 +1076,52 @@ return view.extend({ }, this)); }, + handleConfigureAcquisition: function() { + console.log('[Wizard] handleConfigureAcquisition called'); + this.wizardData.acquisitionConfiguring = true; + this.refreshView(); + + // Get values from wizard data + var syslogEnabled = this.wizardData.syslogEnabled ? '1' : '0'; + var firewallEnabled = this.wizardData.firewallEnabled ? '1' : '0'; + var sshEnabled = this.wizardData.sshEnabled ? '1' : '0'; + var httpEnabled = this.wizardData.httpEnabled ? '1' : '0'; + var syslogPath = this.wizardData.syslogPath || '/var/log/messages'; + + console.log('[Wizard] Acquisition config:', { + syslog: syslogEnabled, + firewall: firewallEnabled, + ssh: sshEnabled, + http: httpEnabled, + path: syslogPath + }); + + return API.configureAcquisition(syslogEnabled, firewallEnabled, sshEnabled, httpEnabled, syslogPath) + .then(L.bind(function(result) { + console.log('[Wizard] configureAcquisition result:', result); + this.wizardData.acquisitionConfiguring = false; + + if (result && result.success) { + this.wizardData.acquisitionConfigured = true; + ui.addNotification(null, E('p', _('Log acquisition configured successfully')), 'info'); + this.refreshView(); + + // Auto-advance to Step 5 (Collections) after 2 seconds + console.log('[Wizard] Auto-advancing to Step 5 in 2 seconds...'); + setTimeout(L.bind(function() { this.goToStep(5); }, this), 2000); + } else { + ui.addNotification(null, E('p', _('Configuration failed: ') + (result.error || 'Unknown error')), 'error'); + this.refreshView(); + } + }, this)) + .catch(L.bind(function(err) { + console.error('[Wizard] Acquisition configuration error:', err); + this.wizardData.acquisitionConfiguring = false; + ui.addNotification(null, E('p', _('Configuration failed: ') + err.message), 'error'); + this.refreshView(); + }, this)); + }, + handleInstallCollections: function() { // Read from data-checked attributes (Unicode checkbox approach) var items = document.querySelectorAll('.collection-item[data-collection]'); @@ -898,7 +1132,7 @@ return view.extend({ console.log('[Wizard] Selected collections:', selected); if (selected.length === 0) { - this.goToStep(5); + this.goToStep(6); return; } @@ -922,8 +1156,8 @@ return view.extend({ ui.addNotification(null, E('p', _('Installed %d collections').format(selected.length)), 'info'); this.refreshView(); - // Auto-advance after 2 seconds - setTimeout(L.bind(function() { this.goToStep(5); }, this), 2000); + // Auto-advance to Step 6 (Configure Bouncer) after 2 seconds + setTimeout(L.bind(function() { this.goToStep(6); }, this), 2000); }, this)).catch(L.bind(function(err) { this.wizardData.installing = false; ui.addNotification(null, E('p', _('Installation failed: %s').format(err.message)), 'error'); @@ -971,9 +1205,9 @@ return view.extend({ ui.addNotification(null, E('p', _('Bouncer configured successfully')), 'info'); this.refreshView(); - // Auto-advance after 2 seconds - console.log('[Wizard] Auto-advancing to Step 6 in 2 seconds...'); - setTimeout(L.bind(function() { this.goToStep(6); }, this), 2000); + // Auto-advance to Step 7 (Enable Services) after 2 seconds + console.log('[Wizard] Auto-advancing to Step 7 in 2 seconds...'); + setTimeout(L.bind(function() { this.goToStep(7); }, this), 2000); }, this)).catch(L.bind(function(err) { console.error('[Wizard] Configuration error:', err); this.wizardData.configuring = false; @@ -1039,10 +1273,10 @@ return view.extend({ // 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 7...'); + console.log('[Wizard] All critical services started! Auto-advancing to Step 8 (Complete)...'); ui.addNotification(null, E('p', _('Services started successfully!')), 'info'); - // Auto-advance after 2 seconds - setTimeout(L.bind(function() { this.goToStep(7); }, this), 2000); + // Auto-advance to Step 8 (Complete) after 2 seconds + setTimeout(L.bind(function() { this.goToStep(8); }, this), 2000); } else { console.log('[Wizard] Service startup incomplete'); ui.addNotification(null, E('p', _('Service startup incomplete. Check status and retry.')), 'warning'); diff --git a/package/secubox/luci-app-crowdsec-dashboard/root/usr/libexec/rpcd/luci.crowdsec-dashboard b/package/secubox/luci-app-crowdsec-dashboard/root/usr/libexec/rpcd/luci.crowdsec-dashboard index e10f7e72..ca9ec067 100755 --- a/package/secubox/luci-app-crowdsec-dashboard/root/usr/libexec/rpcd/luci.crowdsec-dashboard +++ b/package/secubox/luci-app-crowdsec-dashboard/root/usr/libexec/rpcd/luci.crowdsec-dashboard @@ -14,6 +14,12 @@ secubox_log() { } CSCLI="/usr/bin/cscli" +CSCLI_TIMEOUT=10 + +# Run cscli with timeout to prevent hangs +run_cscli() { + timeout "$CSCLI_TIMEOUT" "$CSCLI" "$@" 2>/dev/null +} # Check if cscli exists and crowdsec is running check_cscli() { @@ -33,7 +39,7 @@ check_cscli() { get_decisions() { check_cscli local output - output=$($CSCLI decisions list -o json 2>/dev/null) + output=$(run_cscli decisions list -o json) if [ -z "$output" ] || [ "$output" = "null" ]; then echo '{"alerts":[]}' else @@ -46,7 +52,7 @@ get_alerts() { local limit="${1:-50}" check_cscli local output - output=$($CSCLI alerts list -o json --limit "$limit" 2>/dev/null) + output=$(run_cscli alerts list -o json --limit "$limit" 2>/dev/null) if [ -z "$output" ] || [ "$output" = "null" ]; then echo '{"alerts":[]}' else @@ -58,7 +64,7 @@ get_alerts() { get_metrics() { check_cscli local output - output=$($CSCLI metrics -o json 2>/dev/null) + output=$(run_cscli metrics -o json 2>/dev/null) if [ -z "$output" ]; then echo '{}' else @@ -72,7 +78,7 @@ get_metrics() { get_bouncers() { check_cscli local output - output=$($CSCLI bouncers list -o json 2>/dev/null) + output=$(run_cscli bouncers list -o json 2>/dev/null) if [ -z "$output" ] || [ "$output" = "null" ]; then echo '{"bouncers":[]}' else @@ -84,7 +90,7 @@ get_bouncers() { get_machines() { check_cscli local output - output=$($CSCLI machines list -o json 2>/dev/null) + output=$(run_cscli machines list -o json 2>/dev/null) if [ -z "$output" ] || [ "$output" = "null" ]; then echo '{"machines":[]}' else @@ -96,7 +102,7 @@ get_machines() { get_hub() { check_cscli local output - output=$($CSCLI hub list -o json 2>/dev/null) + output=$(run_cscli hub list -o json 2>/dev/null) if [ -z "$output" ]; then echo '{}' else @@ -124,7 +130,7 @@ get_status() { # Version local version - version=$($CSCLI version 2>/dev/null | grep "version:" | awk '{print $2}') + version=$(run_cscli version 2>/dev/null | grep "version:" | awk '{print $2}') json_add_string "version" "${version:-unknown}" # Uptime @@ -135,7 +141,7 @@ get_status() { # LAPI status (check if Local API is accessible) local lapi_status="unavailable" if [ -x "$CSCLI" ]; then - if $CSCLI lapi status >/dev/null 2>&1; then + if run_cscli lapi status >/dev/null 2>&1; then lapi_status="available" fi fi @@ -158,7 +164,7 @@ add_ban() { fi local result - result=$($CSCLI decisions add --ip "$ip" --duration "$duration" --reason "$reason" 2>&1) + result=$(run_cscli decisions add --ip "$ip" --duration "$duration" --reason "$reason" 2>&1) if [ $? -eq 0 ]; then secubox_log "CrowdSec ban added for $ip ($duration)" @@ -183,7 +189,7 @@ remove_ban() { fi local result - result=$($CSCLI decisions delete --ip "$ip" 2>&1) + result=$(run_cscli decisions delete --ip "$ip" 2>&1) if [ $? -eq 0 ]; then secubox_log "CrowdSec ban removed for $ip" @@ -204,22 +210,22 @@ get_dashboard_stats() { # Count decisions local decisions_count - decisions_count=$($CSCLI decisions list -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l) + decisions_count=$(run_cscli decisions list -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l) json_add_int "total_decisions" "${decisions_count:-0}" # Count alerts (last 24h) local alerts_count - alerts_count=$($CSCLI alerts list -o json --since 24h 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l) + alerts_count=$(run_cscli alerts list -o json --since 24h 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l) json_add_int "alerts_24h" "${alerts_count:-0}" # Count bouncers local bouncers_count - bouncers_count=$($CSCLI bouncers list -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l) + bouncers_count=$(run_cscli bouncers list -o json 2>/dev/null | jsonfilter -e '@[*]' 2>/dev/null | wc -l) json_add_int "bouncers" "${bouncers_count:-0}" # Top scenarios (from alerts) local scenarios - scenarios=$($CSCLI alerts list -o json --limit 100 2>/dev/null | \ + scenarios=$(run_cscli alerts list -o json --limit 100 2>/dev/null | \ jsonfilter -e '@[*].scenario' 2>/dev/null | \ sort | uniq -c | sort -rn | head -5 | \ awk '{print "{\"scenario\":\"" $2 "\",\"count\":" $1 "}"}' | \ @@ -229,7 +235,7 @@ get_dashboard_stats() { # Top countries (from decisions) local countries - countries=$($CSCLI decisions list -o json 2>/dev/null | \ + countries=$(run_cscli decisions list -o json 2>/dev/null | \ jsonfilter -e '@[*].country' 2>/dev/null | \ sort | uniq -c | sort -rn | head -10 | \ awk '{print "{\"country\":\"" $2 "\",\"count\":" $1 "}"}' | \ @@ -271,9 +277,9 @@ get_waf_status() { json_init # Check if appsec is available (cscli appsec command) - if $CSCLI help appsec >/dev/null 2>&1; then + if run_cscli help appsec >/dev/null 2>&1; then local appsec_status - appsec_status=$($CSCLI appsec status -o json 2>/dev/null) + appsec_status=$(run_cscli appsec status -o json 2>/dev/null) if [ -n "$appsec_status" ] && [ "$appsec_status" != "null" ]; then echo "$appsec_status" @@ -340,7 +346,7 @@ configure_metrics() { get_collections() { check_cscli local output - output=$($CSCLI collections list -o json 2>/dev/null) + output=$(run_cscli collections list -o json 2>/dev/null) if [ -z "$output" ] || [ "$output" = "null" ]; then echo '{"collections":[]}' else @@ -362,7 +368,7 @@ install_collection() { fi # Install collection - if $CSCLI collections install "$collection" >/dev/null 2>&1; then + if run_cscli collections install "$collection" >/dev/null 2>&1; then json_add_boolean "success" 1 json_add_string "message" "Collection '$collection' installed successfully" secubox_log "Installed collection: $collection" @@ -388,7 +394,7 @@ remove_collection() { fi # Remove collection - if $CSCLI collections remove "$collection" >/dev/null 2>&1; then + if run_cscli collections remove "$collection" >/dev/null 2>&1; then json_add_boolean "success" 1 json_add_string "message" "Collection '$collection' removed successfully" secubox_log "Removed collection: $collection" @@ -405,7 +411,7 @@ update_hub() { check_cscli json_init - if $CSCLI hub update >/dev/null 2>&1; then + if run_cscli hub update >/dev/null 2>&1; then json_add_boolean "success" 1 json_add_string "message" "Hub index updated successfully" secubox_log "Hub index updated" @@ -434,7 +440,7 @@ register_bouncer() { # Check if bouncer already exists (robust pattern matching) local exists=0 local bouncer_list - bouncer_list=$($CSCLI bouncers list -o json 2>/dev/null) + bouncer_list=$(run_cscli bouncers list -o json 2>/dev/null) if echo "$bouncer_list" | grep -qE "\"name\"[[:space:]]*:[[:space:]]*\"$bouncer_name\""; then exists=1 fi @@ -443,7 +449,7 @@ register_bouncer() { if [ "$exists" = "1" ]; then secubox_log "Bouncer '$bouncer_name' already exists, deleting for re-registration..." # Delete existing bouncer to get new API key - if ! $CSCLI bouncers delete "$bouncer_name" 2>&1; then + if ! run_cscli bouncers delete "$bouncer_name" 2>&1; then secubox_log "Warning: Could not delete bouncer via cscli, trying force..." fi # Small delay to ensure deletion is processed @@ -451,7 +457,7 @@ register_bouncer() { fi # Generate API key - api_key=$($CSCLI bouncers add "$bouncer_name" -o raw 2>&1) + api_key=$(run_cscli bouncers add "$bouncer_name" -o raw 2>&1) if [ -n "$api_key" ] && [ "${#api_key}" -gt 10 ] && ! echo "$api_key" | grep -qi "error\|unable\|failed"; then json_add_boolean "success" 1 @@ -467,7 +473,7 @@ register_bouncer() { sqlite3 /srv/crowdsec/data/crowdsec.db "DELETE FROM bouncers WHERE name='$bouncer_name';" 2>/dev/null sleep 1 # Retry registration - api_key=$($CSCLI bouncers add "$bouncer_name" -o raw 2>&1) + api_key=$(run_cscli bouncers add "$bouncer_name" -o raw 2>&1) if [ -n "$api_key" ] && [ "${#api_key}" -gt 10 ] && ! echo "$api_key" | grep -qi "error\|unable\|failed"; then json_add_boolean "success" 1 json_add_string "api_key" "$api_key" @@ -501,7 +507,7 @@ delete_bouncer() { fi # Delete bouncer - if $CSCLI bouncers delete "$bouncer_name" >/dev/null 2>&1; then + if run_cscli bouncers delete "$bouncer_name" >/dev/null 2>&1; then json_add_boolean "success" 1 json_add_string "message" "Bouncer '$bouncer_name' deleted successfully" secubox_log "Deleted bouncer: $bouncer_name" @@ -837,7 +843,7 @@ check_wizard_needed() { # Check if collections are installed local collections_installed=0 if [ -x "$CSCLI" ]; then - if $CSCLI collections list 2>/dev/null | grep -q "INSTALLED"; then + if run_cscli collections list 2>/dev/null | grep -q "INSTALLED"; then collections_installed=1 fi fi @@ -862,7 +868,7 @@ get_wizard_state() { # Get collections count local collections_count=0 if [ -x "$CSCLI" ]; then - collections_count=$($CSCLI collections list 2>/dev/null | grep -c "INSTALLED" || echo "0") + collections_count=$(run_cscli collections list 2>/dev/null | grep -c "INSTALLED" || echo "0") fi json_add_int "collections_count" "$collections_count" @@ -919,9 +925,9 @@ repair_lapi() { # Step 4: Re-register local machine if needed if [ -x "$CSCLI" ]; then # Check if machine is registered and working - if ! $CSCLI machines list >/dev/null 2>&1; then + if ! run_cscli machines list >/dev/null 2>&1; then # Force re-register - if $CSCLI machines add localhost --auto --force >/dev/null 2>&1; then + if run_cscli machines add localhost --auto --force >/dev/null 2>&1; then steps_done="${steps_done}Re-registered localhost machine; " # Restart again to apply new credentials /etc/init.d/crowdsec restart >/dev/null 2>&1 @@ -935,7 +941,7 @@ repair_lapi() { # Step 5: Verify LAPI is now working local lapi_ok=0 if [ -x "$CSCLI" ]; then - if $CSCLI lapi status >/dev/null 2>&1; then + if run_cscli lapi status >/dev/null 2>&1; then lapi_ok=1 steps_done="${steps_done}LAPI verified working" else @@ -973,7 +979,7 @@ reset_wizard() { # Step 2: Delete existing bouncer registration if [ -x "$CSCLI" ]; then - $CSCLI bouncers delete "crowdsec-firewall-bouncer" >/dev/null 2>&1 + run_cscli bouncers delete "crowdsec-firewall-bouncer" >/dev/null 2>&1 steps_done="${steps_done}Deleted bouncer registration; " # Also try database cleanup @@ -1023,7 +1029,7 @@ get_console_status() { # Try to get console status from cscli local console_status - console_status=$($CSCLI console status 2>/dev/null) + console_status=$(run_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 @@ -1068,7 +1074,7 @@ console_enroll() { secubox_log "Enrolling CrowdSec Console with key..." # Build enroll command - local enroll_cmd="$CSCLI console enroll $key" + local enroll_cmd="run_cscli console enroll $key" if [ -n "$name" ]; then enroll_cmd="$enroll_cmd --name \"$name\"" fi @@ -1085,9 +1091,9 @@ console_enroll() { 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 + run_cscli console enable share_manual_decisions >/dev/null 2>&1 + run_cscli console enable share_tainted >/dev/null 2>&1 + run_cscli console enable share_context >/dev/null 2>&1 else json_add_boolean "success" 0 json_add_string "error" "Enrollment failed" @@ -1106,9 +1112,9 @@ console_disable() { 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 + run_cscli console disable share_manual_decisions >/dev/null 2>&1 + run_cscli console disable share_tainted >/dev/null 2>&1 + run_cscli console disable share_context >/dev/null 2>&1 # Remove console config if [ -f "/etc/crowdsec/console.yaml" ]; then @@ -1122,6 +1128,159 @@ console_disable() { json_dump } +# Configure log acquisition settings +configure_acquisition() { + local syslog_enabled="$1" + local firewall_enabled="$2" + local ssh_enabled="$3" + local http_enabled="$4" + local syslog_path="$5" + + json_init + local steps_done="" + local errors="" + + secubox_log "Configuring CrowdSec log acquisition..." + + # Step 1: Ensure acquisition section exists in UCI + if ! uci -q get crowdsec.acquisition >/dev/null 2>&1; then + uci set crowdsec.acquisition='acquisition' + steps_done="${steps_done}Created acquisition section; " + fi + + # Step 2: Set acquisition options + uci set crowdsec.acquisition.syslog_enabled="${syslog_enabled:-1}" + uci set crowdsec.acquisition.firewall_enabled="${firewall_enabled:-1}" + uci set crowdsec.acquisition.ssh_enabled="${ssh_enabled:-1}" + uci set crowdsec.acquisition.http_enabled="${http_enabled:-0}" + if [ -n "$syslog_path" ]; then + uci set crowdsec.acquisition.syslog_path="$syslog_path" + fi + uci commit crowdsec + steps_done="${steps_done}Updated UCI settings; " + + # Step 3: Generate acquisition YAML files + # OpenWrt uses logread command instead of /var/log/messages by default + # All syslog entries (SSH, firewall, system) go through the same log stream + # We create ONE unified acquisition file to avoid multiple logread processes + local acquis_dir="/etc/crowdsec/acquis.d" + mkdir -p "$acquis_dir" + + # Remove old separate acquisition files if they exist + rm -f "$acquis_dir/openwrt-syslog.yaml" 2>/dev/null + rm -f "$acquis_dir/openwrt-firewall.yaml" 2>/dev/null + rm -f "$acquis_dir/openwrt-dropbear.yaml" 2>/dev/null + + # Create unified syslog acquisition if any syslog-based source is enabled + # SSH, firewall, and system logs all go through OpenWrt's logread + if [ "$syslog_enabled" = "1" ] || [ "$firewall_enabled" = "1" ] || [ "$ssh_enabled" = "1" ]; then + cat > "$acquis_dir/openwrt-unified.yaml" << 'YAML' +# OpenWrt Unified Syslog Acquisition +# Auto-generated by SecuBox CrowdSec Wizard +# Uses logread -f to stream all syslog entries +# Covers: system logs, SSH/Dropbear, firewall (iptables/nftables) +source: command +command: /sbin/logread -f +labels: + type: syslog +YAML + local enabled_sources="" + [ "$syslog_enabled" = "1" ] && enabled_sources="${enabled_sources}system " + [ "$ssh_enabled" = "1" ] && enabled_sources="${enabled_sources}SSH " + [ "$firewall_enabled" = "1" ] && enabled_sources="${enabled_sources}firewall " + steps_done="${steps_done}Created unified syslog acquisition (${enabled_sources}); " + else + rm -f "$acquis_dir/openwrt-unified.yaml" + steps_done="${steps_done}Disabled syslog acquisition; " + fi + + # Enable/disable HTTP log acquisition (separate file-based source) + if [ "$http_enabled" = "1" ]; then + # Check if log files exist + if [ -f "/var/log/uhttpd.log" ]; then + cat > "$acquis_dir/openwrt-http.yaml" << 'YAML' +# OpenWrt uHTTPd Web Server Log Acquisition +# Auto-generated by SecuBox CrowdSec Wizard +filenames: + - /var/log/uhttpd.log +labels: + type: nginx +YAML + elif [ -f "/var/log/nginx/access.log" ]; then + cat > "$acquis_dir/openwrt-http.yaml" << 'YAML' +# OpenWrt nginx Web Server Log Acquisition +# Auto-generated by SecuBox CrowdSec Wizard +filenames: + - /var/log/nginx/access.log +labels: + type: nginx +YAML + else + # Fallback - try both locations + cat > "$acquis_dir/openwrt-http.yaml" << 'YAML' +# OpenWrt Web Server Log Acquisition +# Auto-generated by SecuBox CrowdSec Wizard +filenames: + - /var/log/uhttpd.log + - /var/log/nginx/access.log +labels: + type: nginx +YAML + fi + steps_done="${steps_done}Created HTTP acquisition; " + else + rm -f "$acquis_dir/openwrt-http.yaml" + rm -f "$acquis_dir/openwrt-uhttpd.yaml" 2>/dev/null + steps_done="${steps_done}Disabled HTTP acquisition; " + fi + + # Step 4: Restart CrowdSec to apply acquisition changes + if /etc/init.d/crowdsec reload >/dev/null 2>&1; then + steps_done="${steps_done}Reloaded CrowdSec" + else + # Fallback to restart if reload fails + /etc/init.d/crowdsec restart >/dev/null 2>&1 + steps_done="${steps_done}Restarted CrowdSec" + fi + + json_add_boolean "success" 1 + json_add_string "message" "Acquisition configuration completed" + json_add_string "steps" "$steps_done" + secubox_log "Acquisition configuration completed: $steps_done" + + json_dump +} + +# Get current acquisition configuration +get_acquisition_config() { + json_init + + # Get values from UCI + local syslog_enabled=$(uci -q get crowdsec.acquisition.syslog_enabled || echo "1") + local firewall_enabled=$(uci -q get crowdsec.acquisition.firewall_enabled || echo "1") + local ssh_enabled=$(uci -q get crowdsec.acquisition.ssh_enabled || echo "1") + local http_enabled=$(uci -q get crowdsec.acquisition.http_enabled || echo "0") + local syslog_path=$(uci -q get crowdsec.acquisition.syslog_path || echo "/var/log/messages") + + json_add_string "syslog_enabled" "$syslog_enabled" + json_add_string "firewall_enabled" "$firewall_enabled" + json_add_string "ssh_enabled" "$ssh_enabled" + json_add_string "http_enabled" "$http_enabled" + json_add_string "syslog_path" "$syslog_path" + + # Check which acquisition files exist + local acquis_dir="/etc/crowdsec/acquis.d" + local unified_exists=0 + local http_exists=0 + [ -f "$acquis_dir/openwrt-unified.yaml" ] && unified_exists=1 + [ -f "$acquis_dir/openwrt-http.yaml" ] && http_exists=1 + + json_add_boolean "unified_file_exists" "$unified_exists" + json_add_boolean "http_file_exists" "$http_exists" + + json_dump +} + # Service control (start/stop/restart/reload) service_control() { local action="$1" @@ -1164,7 +1323,7 @@ service_control() { # 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":{},"reset_wizard":{},"console_status":{},"console_enroll":{"key":"string","name":"string"},"console_disable":{},"service_control":{"action":"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":{},"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":{}}' ;; call) case "$2" in @@ -1298,6 +1457,18 @@ case "$1" in action=$(echo "$input" | jsonfilter -e '@.action' 2>/dev/null) service_control "$action" ;; + configure_acquisition) + read -r input + syslog_enabled=$(echo "$input" | jsonfilter -e '@.syslog_enabled' 2>/dev/null) + firewall_enabled=$(echo "$input" | jsonfilter -e '@.firewall_enabled' 2>/dev/null) + ssh_enabled=$(echo "$input" | jsonfilter -e '@.ssh_enabled' 2>/dev/null) + http_enabled=$(echo "$input" | jsonfilter -e '@.http_enabled' 2>/dev/null) + syslog_path=$(echo "$input" | jsonfilter -e '@.syslog_path' 2>/dev/null) + configure_acquisition "$syslog_enabled" "$firewall_enabled" "$ssh_enabled" "$http_enabled" "$syslog_path" + ;; + acquisition_config) + get_acquisition_config + ;; *) echo '{"error": "Unknown method"}' ;; diff --git a/package/secubox/luci-app-crowdsec-dashboard/root/usr/share/rpcd/acl.d/luci-app-crowdsec-dashboard.json b/package/secubox/luci-app-crowdsec-dashboard/root/usr/share/rpcd/acl.d/luci-app-crowdsec-dashboard.json index b5bd2863..a4b143a4 100644 --- a/package/secubox/luci-app-crowdsec-dashboard/root/usr/share/rpcd/acl.d/luci-app-crowdsec-dashboard.json +++ b/package/secubox/luci-app-crowdsec-dashboard/root/usr/share/rpcd/acl.d/luci-app-crowdsec-dashboard.json @@ -21,7 +21,8 @@ "nftables_stats", "check_wizard_needed", "wizard_state", - "console_status" + "console_status", + "acquisition_config" ], "file": [ "read", "stat" ] }, @@ -44,10 +45,12 @@ "repair_lapi", "console_enroll", "console_disable", - "service_control" + "service_control", + "configure_acquisition", + "reset_wizard" ] }, - "uci": [ "crowdsec-dashboard" ] + "uci": [ "crowdsec", "crowdsec-dashboard" ] } } } diff --git a/package/secubox/luci-app-secubox-crowdsec/Makefile b/package/secubox/luci-app-secubox-crowdsec/Makefile new file mode 100644 index 00000000..cb496086 --- /dev/null +++ b/package/secubox/luci-app-secubox-crowdsec/Makefile @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: MIT +# +# LuCI SecuBox CrowdSec Dashboard +# Copyright (C) 2025 CyberMind.fr - Gandalf +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=luci-app-secubox-crowdsec +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 + +LUCI_TITLE:=LuCI SecuBox CrowdSec Dashboard +LUCI_DEPENDS:=+luci-base +crowdsec +crowdsec-firewall-bouncer-nftables +LUCI_PKGARCH:=all + +PKG_MAINTAINER:=Gerald Kerma +PKG_LICENSE:=MIT + +include $(TOPDIR)/feeds/luci/luci.mk + +define Package/luci-app-secubox-crowdsec/conffiles +/etc/config/crowdsec +endef + +define Package/luci-app-secubox-crowdsec/install + # RPCD backend + $(INSTALL_DIR) $(1)/usr/libexec/rpcd + $(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.secubox-crowdsec $(1)/usr/libexec/rpcd/ + + # ACL permissions + $(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d + $(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-secubox-crowdsec.json $(1)/usr/share/rpcd/acl.d/ + + # LuCI menu + $(INSTALL_DIR) $(1)/usr/share/luci/menu.d + $(INSTALL_DATA) ./root/usr/share/luci/menu.d/luci-app-secubox-crowdsec.json $(1)/usr/share/luci/menu.d/ + + # JavaScript API module + $(INSTALL_DIR) $(1)/www/luci-static/resources/secubox-crowdsec + $(INSTALL_DATA) ./htdocs/luci-static/resources/secubox-crowdsec/api.js $(1)/www/luci-static/resources/secubox-crowdsec/ + + # JavaScript views + $(INSTALL_DIR) $(1)/www/luci-static/resources/view/secubox-crowdsec + $(INSTALL_DATA) ./htdocs/luci-static/resources/view/secubox-crowdsec/dashboard.js $(1)/www/luci-static/resources/view/secubox-crowdsec/ + $(INSTALL_DATA) ./htdocs/luci-static/resources/view/secubox-crowdsec/decisions.js $(1)/www/luci-static/resources/view/secubox-crowdsec/ + $(INSTALL_DATA) ./htdocs/luci-static/resources/view/secubox-crowdsec/alerts.js $(1)/www/luci-static/resources/view/secubox-crowdsec/ + $(INSTALL_DATA) ./htdocs/luci-static/resources/view/secubox-crowdsec/collections.js $(1)/www/luci-static/resources/view/secubox-crowdsec/ + $(INSTALL_DATA) ./htdocs/luci-static/resources/view/secubox-crowdsec/settings.js $(1)/www/luci-static/resources/view/secubox-crowdsec/ + + # UCI default config + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./root/etc/config/crowdsec $(1)/etc/config/crowdsec +endef + +define Package/luci-app-secubox-crowdsec/postinst +#!/bin/sh +[ -n "$${IPKG_INSTROOT}" ] || { + # Restart rpcd to load the new backend + /etc/init.d/rpcd restart + # Clear LuCI cache + rm -rf /tmp/luci-modulecache /tmp/luci-indexcache 2>/dev/null + echo "SecuBox CrowdSec Dashboard installed." + echo "Access via Services -> CrowdSec in LuCI." +} +exit 0 +endef + +# call BuildPackage - OpenWrt buildroot +$(eval $(call BuildPackage,luci-app-secubox-crowdsec)) diff --git a/package/secubox/luci-app-secubox-crowdsec/htdocs/luci-static/resources/secubox-crowdsec/api.js b/package/secubox/luci-app-secubox-crowdsec/htdocs/luci-static/resources/secubox-crowdsec/api.js new file mode 100644 index 00000000..18de65e7 --- /dev/null +++ b/package/secubox/luci-app-secubox-crowdsec/htdocs/luci-static/resources/secubox-crowdsec/api.js @@ -0,0 +1,191 @@ +'use strict'; +'require rpc'; + +var callStatus = rpc.declare({ + object: 'luci.secubox-crowdsec', + method: 'status', + expect: { } +}); + +var callDecisions = rpc.declare({ + object: 'luci.secubox-crowdsec', + method: 'decisions', + expect: { decisions: [] } +}); + +var callAddDecision = rpc.declare({ + object: 'luci.secubox-crowdsec', + method: 'add_decision', + params: ['ip', 'duration', 'reason', 'type'], + expect: { } +}); + +var callDeleteDecision = rpc.declare({ + object: 'luci.secubox-crowdsec', + method: 'delete_decision', + params: ['ip', 'decision_id'], + expect: { } +}); + +var callAlerts = rpc.declare({ + object: 'luci.secubox-crowdsec', + method: 'alerts', + params: ['limit', 'since'], + expect: { alerts: [] } +}); + +var callMetrics = rpc.declare({ + object: 'luci.secubox-crowdsec', + method: 'metrics', + expect: { } +}); + +var callStats = rpc.declare({ + object: 'luci.secubox-crowdsec', + method: 'stats', + expect: { } +}); + +var callCollections = rpc.declare({ + object: 'luci.secubox-crowdsec', + method: 'collections', + expect: { collections: [] } +}); + +var callInstallCollection = rpc.declare({ + object: 'luci.secubox-crowdsec', + method: 'install_collection', + params: ['collection'], + expect: { } +}); + +var callRemoveCollection = rpc.declare({ + object: 'luci.secubox-crowdsec', + method: 'remove_collection', + params: ['collection'], + expect: { } +}); + +var callUpdateHub = rpc.declare({ + object: 'luci.secubox-crowdsec', + method: 'update_hub', + expect: { } +}); + +var callUpgradeHub = rpc.declare({ + object: 'luci.secubox-crowdsec', + method: 'upgrade_hub', + expect: { } +}); + +var callBouncers = rpc.declare({ + object: 'luci.secubox-crowdsec', + method: 'bouncers', + expect: { bouncers: [] } +}); + +var callControlService = rpc.declare({ + object: 'luci.secubox-crowdsec', + method: 'control_service', + params: ['service', 'action'], + expect: { } +}); + +var callNftablesStats = rpc.declare({ + object: 'luci.secubox-crowdsec', + method: 'nftables_stats', + expect: { } +}); + +var callBlockedIps = rpc.declare({ + object: 'luci.secubox-crowdsec', + method: 'blocked_ips', + expect: { ipv4: [], ipv6: [] } +}); + +var callConfig = rpc.declare({ + object: 'luci.secubox-crowdsec', + method: 'config', + expect: { } +}); + +var callSaveConfig = rpc.declare({ + object: 'luci.secubox-crowdsec', + method: 'save_config', + params: ['key', 'value'], + expect: { } +}); + +return L.Class.extend({ + getStatus: function() { + return callStatus(); + }, + + getDecisions: function() { + return callDecisions(); + }, + + addDecision: function(ip, duration, reason, type) { + return callAddDecision(ip, duration || '24h', reason || 'Manual ban via LuCI', type || 'ban'); + }, + + deleteDecision: function(ip, decisionId) { + return callDeleteDecision(ip, decisionId); + }, + + getAlerts: function(limit, since) { + return callAlerts(limit || 50, since || '24h'); + }, + + getMetrics: function() { + return callMetrics(); + }, + + getStats: function() { + return callStats(); + }, + + getCollections: function() { + return callCollections(); + }, + + installCollection: function(collection) { + return callInstallCollection(collection); + }, + + removeCollection: function(collection) { + return callRemoveCollection(collection); + }, + + updateHub: function() { + return callUpdateHub(); + }, + + upgradeHub: function() { + return callUpgradeHub(); + }, + + getBouncers: function() { + return callBouncers(); + }, + + controlService: function(service, action) { + return callControlService(service, action); + }, + + getNftablesStats: function() { + return callNftablesStats(); + }, + + getBlockedIps: function() { + return callBlockedIps(); + }, + + getConfig: function() { + return callConfig(); + }, + + saveConfig: function(key, value) { + return callSaveConfig(key, value); + } +}); diff --git a/package/secubox/luci-app-secubox-crowdsec/htdocs/luci-static/resources/view/secubox-crowdsec/alerts.js b/package/secubox/luci-app-secubox-crowdsec/htdocs/luci-static/resources/view/secubox-crowdsec/alerts.js new file mode 100644 index 00000000..49867012 --- /dev/null +++ b/package/secubox/luci-app-secubox-crowdsec/htdocs/luci-static/resources/view/secubox-crowdsec/alerts.js @@ -0,0 +1,117 @@ +'use strict'; +'require view'; +'require dom'; +'require ui'; +'require secubox-crowdsec/api as api'; + +return view.extend({ + api: null, + + load: function() { + this.api = new api(); + return this.api.getAlerts(100, '7d'); + }, + + formatDate: function(timestamp) { + if (!timestamp) return '-'; + try { + var date = new Date(timestamp); + return date.toLocaleString(); + } catch(e) { + return timestamp; + } + }, + + renderAlertsTable: function(alerts) { + var self = this; + + if (!alerts || alerts.length === 0) { + return E('p', { 'class': 'alert-message' }, 'No alerts in the selected period'); + } + + var rows = alerts.map(function(a) { + var sourceIp = '-'; + if (a.source && a.source.ip) { + sourceIp = a.source.ip; + } else if (a.source_ip) { + sourceIp = a.source_ip; + } + + return E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td' }, self.formatDate(a.created_at || a.timestamp)), + E('td', { 'class': 'td' }, sourceIp), + E('td', { 'class': 'td' }, a.scenario || '-'), + E('td', { 'class': 'td' }, String(a.events_count || a.events || 0)), + E('td', { 'class': 'td' }, a.message || '-') + ]); + }); + + return E('table', { 'class': 'table cbi-section-table' }, [ + E('tr', { 'class': 'tr table-titles' }, [ + E('th', { 'class': 'th' }, 'Time'), + E('th', { 'class': 'th' }, 'Source IP'), + E('th', { 'class': 'th' }, 'Scenario'), + E('th', { 'class': 'th' }, 'Events'), + E('th', { 'class': 'th' }, 'Message') + ]) + ].concat(rows)); + }, + + render: function(data) { + var alerts = data.alerts || []; + var self = this; + + var view = E('div', { 'class': 'cbi-map' }, [ + E('h2', { 'class': 'cbi-map-title' }, 'CrowdSec Alerts'), + E('div', { 'class': 'cbi-map-descr' }, 'Security alerts detected by CrowdSec'), + + // Filter Controls + E('div', { 'class': 'cbi-section' }, [ + E('h3', { 'class': 'cbi-section-title' }, 'Filter'), + E('div', { 'style': 'margin-bottom: 15px;' }, [ + E('label', {}, 'Time Period: '), + E('select', { 'id': 'alert-period', 'class': 'cbi-input-select', 'style': 'margin-right: 15px;' }, [ + E('option', { 'value': '1h' }, 'Last hour'), + E('option', { 'value': '24h' }, 'Last 24 hours'), + E('option', { 'value': '7d', 'selected': 'selected' }, 'Last 7 days'), + E('option', { 'value': '30d' }, 'Last 30 days') + ]), + E('label', {}, 'Limit: '), + E('select', { 'id': 'alert-limit', 'class': 'cbi-input-select', 'style': 'margin-right: 15px;' }, [ + E('option', { 'value': '25' }, '25'), + E('option', { 'value': '50' }, '50'), + E('option', { 'value': '100', 'selected': 'selected' }, '100'), + E('option', { 'value': '200' }, '200') + ]), + E('button', { + 'class': 'btn cbi-button cbi-button-action', + 'click': ui.createHandlerFn(this, function() { + var period = document.getElementById('alert-period').value; + var limit = parseInt(document.getElementById('alert-limit').value); + + return this.api.getAlerts(limit, period).then(function(res) { + var table = self.renderAlertsTable(res.alerts || []); + var container = document.getElementById('alerts-table'); + dom.content(container, table); + var title = document.getElementById('alerts-count'); + if (title) title.textContent = 'Alerts (' + (res.alerts ? res.alerts.length : 0) + ')'; + }); + }) + }, 'Refresh') + ]) + ]), + + // Alerts Table + E('div', { 'class': 'cbi-section' }, [ + E('h3', { 'class': 'cbi-section-title', 'id': 'alerts-count' }, 'Alerts (' + alerts.length + ')'), + E('div', { 'id': 'alerts-table' }, this.renderAlertsTable(alerts)) + ]) + ]); + + return view; + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/package/secubox/luci-app-secubox-crowdsec/htdocs/luci-static/resources/view/secubox-crowdsec/collections.js b/package/secubox/luci-app-secubox-crowdsec/htdocs/luci-static/resources/view/secubox-crowdsec/collections.js new file mode 100644 index 00000000..1986e307 --- /dev/null +++ b/package/secubox/luci-app-secubox-crowdsec/htdocs/luci-static/resources/view/secubox-crowdsec/collections.js @@ -0,0 +1,209 @@ +'use strict'; +'require view'; +'require dom'; +'require ui'; +'require secubox-crowdsec/api as api'; + +return view.extend({ + api: null, + + availableCollections: [ + { name: 'crowdsecurity/linux', desc: 'Linux system security' }, + { name: 'crowdsecurity/sshd', desc: 'SSH brute-force protection' }, + { name: 'crowdsecurity/http-cve', desc: 'HTTP CVE exploits' }, + { name: 'crowdsecurity/iptables', desc: 'IPTables/NFTables logs' }, + { name: 'crowdsecurity/nginx', desc: 'Nginx web server' }, + { name: 'crowdsecurity/apache2', desc: 'Apache2 web server' }, + { name: 'crowdsecurity/postfix', desc: 'Postfix mail server' }, + { name: 'crowdsecurity/dovecot', desc: 'Dovecot mail server' }, + { name: 'crowdsecurity/smb', desc: 'SMB/Samba' }, + { name: 'crowdsecurity/wordpress', desc: 'WordPress security' }, + { name: 'crowdsecurity/nextcloud', desc: 'Nextcloud security' } + ], + + load: function() { + this.api = new api(); + return Promise.all([ + this.api.getCollections(), + this.api.getBouncers() + ]); + }, + + isInstalled: function(collections, name) { + if (!collections || !Array.isArray(collections)) return false; + return collections.some(function(c) { + return c.name === name && c.status === 'enabled'; + }); + }, + + renderCollectionsTable: function(collections) { + var self = this; + + if (!collections || collections.length === 0) { + return E('p', { 'class': 'alert-message' }, 'No collections installed'); + } + + var rows = collections.map(function(c) { + var statusBadge = E('span', { + 'style': 'background-color: ' + (c.status === 'enabled' ? 'green' : 'gray') + '; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;' + }, c.status || 'unknown'); + + return E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td' }, c.name || '-'), + E('td', { 'class': 'td' }, c.local_version || c.version || '-'), + E('td', { 'class': 'td' }, statusBadge), + E('td', { 'class': 'td' }, c.description || '-'), + E('td', { 'class': 'td' }, [ + E('button', { + 'class': 'btn cbi-button cbi-button-remove', + 'click': ui.createHandlerFn(self, function(ev) { + if (!confirm('Remove collection ' + c.name + '?')) return; + + return self.api.removeCollection(c.name).then(function(res) { + if (res.success) { + ui.addNotification(null, E('p', res.message), 'info'); + window.location.reload(); + } else { + ui.addNotification(null, E('p', res.error), 'warning'); + } + }); + }) + }, 'Remove') + ]) + ]); + }); + + return E('table', { 'class': 'table cbi-section-table' }, [ + E('tr', { 'class': 'tr table-titles' }, [ + E('th', { 'class': 'th' }, 'Name'), + E('th', { 'class': 'th' }, 'Version'), + E('th', { 'class': 'th' }, 'Status'), + E('th', { 'class': 'th' }, 'Description'), + E('th', { 'class': 'th' }, 'Actions') + ]) + ].concat(rows)); + }, + + renderBouncersTable: function(bouncers) { + if (!bouncers || bouncers.length === 0) { + return E('p', { 'class': 'alert-message' }, 'No bouncers registered'); + } + + var rows = bouncers.map(function(b) { + var validBadge = E('span', { + 'style': 'background-color: ' + (b.is_valid ? 'green' : 'red') + '; color: white; padding: 2px 6px; border-radius: 3px; font-size: 11px;' + }, b.is_valid ? 'Valid' : 'Invalid'); + + return E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td' }, b.name || '-'), + E('td', { 'class': 'td' }, b.ip_address || b.ip || '-'), + E('td', { 'class': 'td' }, b.type || '-'), + E('td', { 'class': 'td' }, b.last_pull || '-'), + E('td', { 'class': 'td' }, validBadge) + ]); + }); + + return E('table', { 'class': 'table cbi-section-table' }, [ + E('tr', { 'class': 'tr table-titles' }, [ + E('th', { 'class': 'th' }, 'Name'), + E('th', { 'class': 'th' }, 'IP'), + E('th', { 'class': 'th' }, 'Type'), + E('th', { 'class': 'th' }, 'Last Pull'), + E('th', { 'class': 'th' }, 'Status') + ]) + ].concat(rows)); + }, + + render: function(data) { + var collections = (data[0] && data[0].collections) ? data[0].collections : []; + var bouncers = (data[1] && data[1].bouncers) ? data[1].bouncers : []; + var self = this; + + // Build available collections dropdown (filter out installed ones) + var availableOptions = this.availableCollections + .filter(function(c) { return !self.isInstalled(collections, c.name); }) + .map(function(c) { + return E('option', { 'value': c.name }, c.name + ' - ' + c.desc); + }); + + var view = E('div', { 'class': 'cbi-map' }, [ + E('h2', { 'class': 'cbi-map-title' }, 'CrowdSec Collections'), + E('div', { 'class': 'cbi-map-descr' }, 'Manage security collections and bouncers'), + + // Hub Actions + E('div', { 'class': 'cbi-section' }, [ + E('h3', { 'class': 'cbi-section-title' }, 'Hub Management'), + E('div', { 'style': 'margin-bottom: 15px;' }, [ + E('button', { + 'class': 'btn cbi-button cbi-button-action', + 'click': ui.createHandlerFn(this, function() { + return this.api.updateHub().then(function(res) { + if (res.success) { + ui.addNotification(null, E('p', res.message), 'info'); + } else { + ui.addNotification(null, E('p', res.error), 'warning'); + } + }); + }) + }, 'Update Hub'), + ' ', + E('button', { + 'class': 'btn cbi-button cbi-button-apply', + 'click': ui.createHandlerFn(this, function() { + return this.api.upgradeHub().then(function(res) { + if (res.success) { + ui.addNotification(null, E('p', res.message), 'info'); + window.location.reload(); + } else { + ui.addNotification(null, E('p', res.error), 'warning'); + } + }); + }) + }, 'Upgrade All') + ]) + ]), + + // Install Collection + E('div', { 'class': 'cbi-section' }, [ + E('h3', { 'class': 'cbi-section-title' }, 'Install Collection'), + E('div', { 'style': 'margin-bottom: 15px;' }, [ + availableOptions.length > 0 ? E('select', { 'id': 'new-collection', 'class': 'cbi-input-select', 'style': 'margin-right: 10px; min-width: 300px;' }, availableOptions) : E('span', {}, 'All recommended collections installed'), + availableOptions.length > 0 ? E('button', { + 'class': 'btn cbi-button cbi-button-add', + 'click': ui.createHandlerFn(this, function() { + var collection = document.getElementById('new-collection').value; + if (!collection) return; + + return this.api.installCollection(collection).then(function(res) { + if (res.success) { + ui.addNotification(null, E('p', res.message), 'info'); + window.location.reload(); + } else { + ui.addNotification(null, E('p', res.error), 'warning'); + } + }); + }) + }, 'Install') : null + ]) + ]), + + // Installed Collections + E('div', { 'class': 'cbi-section' }, [ + E('h3', { 'class': 'cbi-section-title' }, 'Installed Collections (' + collections.length + ')'), + E('div', { 'id': 'collections-table' }, this.renderCollectionsTable(collections)) + ]), + + // Bouncers + E('div', { 'class': 'cbi-section' }, [ + E('h3', { 'class': 'cbi-section-title' }, 'Registered Bouncers (' + bouncers.length + ')'), + E('div', { 'id': 'bouncers-table' }, this.renderBouncersTable(bouncers)) + ]) + ]); + + return view; + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/package/secubox/luci-app-secubox-crowdsec/htdocs/luci-static/resources/view/secubox-crowdsec/dashboard.js b/package/secubox/luci-app-secubox-crowdsec/htdocs/luci-static/resources/view/secubox-crowdsec/dashboard.js new file mode 100644 index 00000000..e4f3eaca --- /dev/null +++ b/package/secubox/luci-app-secubox-crowdsec/htdocs/luci-static/resources/view/secubox-crowdsec/dashboard.js @@ -0,0 +1,162 @@ +'use strict'; +'require view'; +'require dom'; +'require poll'; +'require ui'; +'require secubox-crowdsec/api as api'; + +return view.extend({ + api: null, + + load: function() { + this.api = new api(); + return Promise.all([ + this.api.getStatus(), + this.api.getStats(), + this.api.getNftablesStats() + ]); + }, + + renderStatusBadge: function(status, running, stopped) { + var color = (status === running) ? 'green' : 'red'; + var text = (status === running) ? 'Running' : 'Stopped'; + return E('span', { + 'class': 'badge', + 'style': 'background-color: ' + color + '; color: white; padding: 2px 8px; border-radius: 3px;' + }, text); + }, + + renderServiceCard: function(name, status) { + return E('div', { 'class': 'cbi-section', 'style': 'display: inline-block; width: 200px; margin: 10px; text-align: center; padding: 15px; border: 1px solid #ddd; border-radius: 5px;' }, [ + E('h4', { 'style': 'margin: 0 0 10px 0;' }, name), + this.renderStatusBadge(status, 'running', 'stopped') + ]); + }, + + renderStatCard: function(label, value, icon) { + return E('div', { 'class': 'cbi-section', 'style': 'display: inline-block; width: 150px; margin: 10px; text-align: center; padding: 15px; border: 1px solid #ddd; border-radius: 5px;' }, [ + E('div', { 'style': 'font-size: 24px; font-weight: bold; color: #0066cc;' }, String(value || 0)), + E('div', { 'style': 'color: #666; font-size: 12px;' }, label) + ]); + }, + + render: function(data) { + var status = data[0] || {}; + var stats = data[1] || {}; + var nftStats = data[2] || {}; + var self = this; + + var view = E('div', { 'class': 'cbi-map' }, [ + E('h2', { 'class': 'cbi-map-title' }, 'CrowdSec Dashboard'), + E('div', { 'class': 'cbi-map-descr' }, 'Security engine status and statistics'), + + // Services Status + E('div', { 'class': 'cbi-section' }, [ + E('h3', { 'class': 'cbi-section-title' }, 'Services Status'), + E('div', { 'id': 'service-status', 'style': 'text-align: center;' }, [ + this.renderServiceCard('CrowdSec', status.crowdsec), + this.renderServiceCard('Bouncer', status.bouncer), + this.renderServiceCard('Syslog-ng', status.syslog) + ]) + ]), + + // API Status + E('div', { 'class': 'cbi-section' }, [ + E('h3', { 'class': 'cbi-section-title' }, 'API Status'), + E('table', { 'class': 'table cbi-section-table' }, [ + E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td' }, 'CrowdSec Version'), + E('td', { 'class': 'td' }, status.version || 'Unknown') + ]), + E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td' }, 'Local API (LAPI)'), + E('td', { 'class': 'td' }, this.renderStatusBadge(status.lapi_status, 'available', 'unavailable')) + ]), + E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td' }, 'Central API (CAPI)'), + E('td', { 'class': 'td' }, this.renderStatusBadge(status.capi_status, 'connected', 'disconnected')) + ]) + ]) + ]), + + // Statistics + E('div', { 'class': 'cbi-section' }, [ + E('h3', { 'class': 'cbi-section-title' }, 'Statistics'), + E('div', { 'id': 'stats-cards', 'style': 'text-align: center;' }, [ + this.renderStatCard('Active Decisions', stats.total_decisions), + this.renderStatCard('Alerts (24h)', stats.alerts_24h), + this.renderStatCard('Bouncers', stats.bouncers), + this.renderStatCard('Parsers', stats.parsers), + this.renderStatCard('Scenarios', stats.scenarios) + ]) + ]), + + // NFTables Status + E('div', { 'class': 'cbi-section' }, [ + E('h3', { 'class': 'cbi-section-title' }, 'Firewall (nftables)'), + E('table', { 'class': 'table cbi-section-table' }, [ + E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td' }, 'IPv4 Table'), + E('td', { 'class': 'td' }, nftStats.ipv4_table ? 'Active' : 'Inactive') + ]), + E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td' }, 'IPv6 Table'), + E('td', { 'class': 'td' }, nftStats.ipv6_table ? 'Active' : 'Inactive') + ]), + E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td' }, 'Blocked IPv4'), + E('td', { 'class': 'td' }, String(nftStats.blocked_ipv4 || 0)) + ]), + E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td' }, 'Blocked IPv6'), + E('td', { 'class': 'td' }, String(nftStats.blocked_ipv6 || 0)) + ]) + ]) + ]), + + // Service Controls + E('div', { 'class': 'cbi-section' }, [ + E('h3', { 'class': 'cbi-section-title' }, 'Service Controls'), + E('div', { 'style': 'text-align: center;' }, [ + E('button', { + 'class': 'btn cbi-button cbi-button-apply', + 'click': ui.createHandlerFn(this, function() { + return this.api.controlService('crowdsec', 'restart').then(function() { + ui.addNotification(null, E('p', 'CrowdSec restarted'), 'info'); + window.location.reload(); + }); + }) + }, 'Restart CrowdSec'), + ' ', + E('button', { + 'class': 'btn cbi-button cbi-button-apply', + 'click': ui.createHandlerFn(this, function() { + return this.api.controlService('crowdsec-firewall-bouncer', 'restart').then(function() { + ui.addNotification(null, E('p', 'Bouncer restarted'), 'info'); + window.location.reload(); + }); + }) + }, 'Restart Bouncer'), + ' ', + E('button', { + 'class': 'btn cbi-button cbi-button-action', + 'click': ui.createHandlerFn(this, function() { + return this.api.updateHub().then(function(res) { + if (res.success) + ui.addNotification(null, E('p', res.message), 'info'); + else + ui.addNotification(null, E('p', res.error), 'warning'); + }); + }) + }, 'Update Hub') + ]) + ]) + ]); + + return view; + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/package/secubox/luci-app-secubox-crowdsec/htdocs/luci-static/resources/view/secubox-crowdsec/decisions.js b/package/secubox/luci-app-secubox-crowdsec/htdocs/luci-static/resources/view/secubox-crowdsec/decisions.js new file mode 100644 index 00000000..371e7c39 --- /dev/null +++ b/package/secubox/luci-app-secubox-crowdsec/htdocs/luci-static/resources/view/secubox-crowdsec/decisions.js @@ -0,0 +1,139 @@ +'use strict'; +'require view'; +'require dom'; +'require ui'; +'require secubox-crowdsec/api as api'; + +return view.extend({ + api: null, + + load: function() { + this.api = new api(); + return this.api.getDecisions(); + }, + + renderDecisionsTable: function(decisions) { + var self = this; + + if (!decisions || decisions.length === 0) { + return E('p', { 'class': 'alert-message' }, 'No active decisions'); + } + + var rows = decisions.map(function(d) { + return E('tr', { 'class': 'tr' }, [ + E('td', { 'class': 'td' }, d.value || d.ip || '-'), + E('td', { 'class': 'td' }, d.type || 'ban'), + E('td', { 'class': 'td' }, d.scope || 'ip'), + E('td', { 'class': 'td' }, d.duration || '-'), + E('td', { 'class': 'td' }, d.origin || '-'), + E('td', { 'class': 'td' }, d.scenario || d.reason || '-'), + E('td', { 'class': 'td' }, [ + E('button', { + 'class': 'btn cbi-button cbi-button-remove', + 'click': ui.createHandlerFn(self, function(ev) { + return self.api.deleteDecision(d.value || d.ip, d.id).then(function(res) { + if (res.success) { + ui.addNotification(null, E('p', res.message), 'info'); + window.location.reload(); + } else { + ui.addNotification(null, E('p', res.error), 'warning'); + } + }); + }) + }, 'Delete') + ]) + ]); + }); + + return E('table', { 'class': 'table cbi-section-table' }, [ + E('tr', { 'class': 'tr table-titles' }, [ + E('th', { 'class': 'th' }, 'IP/Range'), + E('th', { 'class': 'th' }, 'Type'), + E('th', { 'class': 'th' }, 'Scope'), + E('th', { 'class': 'th' }, 'Duration'), + E('th', { 'class': 'th' }, 'Origin'), + E('th', { 'class': 'th' }, 'Reason'), + E('th', { 'class': 'th' }, 'Actions') + ]) + ].concat(rows)); + }, + + render: function(data) { + var decisions = data.decisions || []; + var self = this; + + var view = E('div', { 'class': 'cbi-map' }, [ + E('h2', { 'class': 'cbi-map-title' }, 'CrowdSec Decisions'), + E('div', { 'class': 'cbi-map-descr' }, 'Active bans and security decisions'), + + // Add Decision Form + E('div', { 'class': 'cbi-section' }, [ + E('h3', { 'class': 'cbi-section-title' }, 'Add Manual Ban'), + E('div', { 'class': 'cbi-value' }, [ + E('label', { 'class': 'cbi-value-title' }, 'IP Address'), + E('div', { 'class': 'cbi-value-field' }, [ + E('input', { 'type': 'text', 'id': 'ban-ip', 'class': 'cbi-input-text', 'placeholder': '192.168.1.100' }) + ]) + ]), + E('div', { 'class': 'cbi-value' }, [ + E('label', { 'class': 'cbi-value-title' }, 'Duration'), + E('div', { 'class': 'cbi-value-field' }, [ + E('select', { 'id': 'ban-duration', 'class': 'cbi-input-select' }, [ + E('option', { 'value': '1h' }, '1 hour'), + E('option', { 'value': '4h' }, '4 hours'), + E('option', { 'value': '24h', 'selected': 'selected' }, '24 hours'), + E('option', { 'value': '7d' }, '7 days'), + E('option', { 'value': '30d' }, '30 days'), + E('option', { 'value': '365d' }, '1 year') + ]) + ]) + ]), + E('div', { 'class': 'cbi-value' }, [ + E('label', { 'class': 'cbi-value-title' }, 'Reason'), + E('div', { 'class': 'cbi-value-field' }, [ + E('input', { 'type': 'text', 'id': 'ban-reason', 'class': 'cbi-input-text', 'placeholder': 'Manual ban via LuCI', 'value': 'Manual ban via LuCI' }) + ]) + ]), + E('div', { 'class': 'cbi-value' }, [ + E('label', { 'class': 'cbi-value-title' }, ' '), + E('div', { 'class': 'cbi-value-field' }, [ + E('button', { + 'class': 'btn cbi-button cbi-button-apply', + 'click': ui.createHandlerFn(this, function() { + var ip = document.getElementById('ban-ip').value; + var duration = document.getElementById('ban-duration').value; + var reason = document.getElementById('ban-reason').value; + + if (!ip) { + ui.addNotification(null, E('p', 'Please enter an IP address'), 'warning'); + return; + } + + return this.api.addDecision(ip, duration, reason, 'ban').then(function(res) { + if (res.success) { + ui.addNotification(null, E('p', res.message), 'info'); + window.location.reload(); + } else { + ui.addNotification(null, E('p', res.error), 'warning'); + } + }); + }) + }, 'Add Ban') + ]) + ]) + ]), + + // Active Decisions + E('div', { 'class': 'cbi-section' }, [ + E('h3', { 'class': 'cbi-section-title' }, 'Active Decisions (' + decisions.length + ')'), + E('div', { 'id': 'decisions-table' }, this.renderDecisionsTable(decisions)) + ]) + ]); + + return view; + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/package/secubox/luci-app-secubox-crowdsec/htdocs/luci-static/resources/view/secubox-crowdsec/settings.js b/package/secubox/luci-app-secubox-crowdsec/htdocs/luci-static/resources/view/secubox-crowdsec/settings.js new file mode 100644 index 00000000..e4cc05ac --- /dev/null +++ b/package/secubox/luci-app-secubox-crowdsec/htdocs/luci-static/resources/view/secubox-crowdsec/settings.js @@ -0,0 +1,116 @@ +'use strict'; +'require view'; +'require dom'; +'require ui'; +'require uci'; +'require form'; +'require secubox-crowdsec/api as api'; + +return view.extend({ + api: null, + + load: function() { + this.api = new api(); + return Promise.all([ + uci.load('crowdsec'), + this.api.getConfig() + ]); + }, + + render: function(data) { + var config = data[1] || {}; + var m, s, o; + + m = new form.Map('crowdsec', 'CrowdSec Settings', + 'Configure CrowdSec security engine and firewall bouncer settings.'); + + // Main CrowdSec settings + s = m.section(form.TypedSection, 'crowdsec', 'CrowdSec Engine'); + s.anonymous = true; + + o = s.option(form.Flag, 'enabled', 'Enable CrowdSec', + 'Enable or disable the CrowdSec security engine'); + o.default = '1'; + o.rmempty = false; + + // Bouncer settings + s = m.section(form.TypedSection, 'bouncer', 'Firewall Bouncer'); + s.anonymous = true; + + o = s.option(form.Flag, 'enabled', 'Enable Bouncer', + 'Enable the firewall bouncer to block malicious IPs'); + o.default = '1'; + o.rmempty = false; + + o = s.option(form.Flag, 'ipv4', 'IPv4 Blocking', + 'Enable IPv4 address blocking'); + o.default = '1'; + o.rmempty = false; + + o = s.option(form.Flag, 'ipv6', 'IPv6 Blocking', + 'Enable IPv6 address blocking'); + o.default = '1'; + o.rmempty = false; + + o = s.option(form.ListValue, 'deny_action', 'Deny Action', + 'Action to take when blocking an IP'); + o.value('drop', 'Drop (silent)'); + o.value('reject', 'Reject (with response)'); + o.default = 'drop'; + + o = s.option(form.Flag, 'deny_log', 'Log Blocked IPs', + 'Log blocked connections to system log'); + o.default = '1'; + o.rmempty = false; + + o = s.option(form.ListValue, 'update_frequency', 'Update Frequency', + 'How often to fetch new decisions from LAPI'); + o.value('5s', '5 seconds'); + o.value('10s', '10 seconds'); + o.value('30s', '30 seconds'); + o.value('1m', '1 minute'); + o.default = '10s'; + + // Acquisition settings + s = m.section(form.TypedSection, 'acquisition', 'Log Acquisition'); + s.anonymous = true; + + o = s.option(form.Flag, 'syslog_enabled', 'Syslog', + 'Monitor system logs via syslog-ng'); + o.default = '1'; + o.rmempty = false; + + o = s.option(form.Flag, 'firewall_enabled', 'Firewall Logs', + 'Monitor nftables/iptables firewall logs'); + o.default = '1'; + o.rmempty = false; + + o = s.option(form.Flag, 'ssh_enabled', 'SSH Logs', + 'Monitor SSH authentication attempts'); + o.default = '1'; + o.rmempty = false; + + o = s.option(form.Flag, 'http_enabled', 'HTTP Logs', + 'Monitor HTTP server logs (if applicable)'); + o.default = '0'; + o.rmempty = false; + + // Hub settings + s = m.section(form.TypedSection, 'hub', 'Hub Settings'); + s.anonymous = true; + + o = s.option(form.Value, 'collections', 'Default Collections', + 'Space-separated list of collections to install'); + o.default = 'crowdsecurity/linux crowdsecurity/sshd crowdsecurity/iptables'; + + o = s.option(form.ListValue, 'update_interval', 'Hub Update Interval', + 'How often to check for hub updates (days)'); + o.value('1', 'Daily'); + o.value('7', 'Weekly'); + o.value('30', 'Monthly'); + o.value('0', 'Never'); + o.default = '7'; + + return m.render(); + } +}); diff --git a/package/secubox/luci-app-secubox-crowdsec/root/etc/config/crowdsec b/package/secubox/luci-app-secubox-crowdsec/root/etc/config/crowdsec new file mode 100644 index 00000000..95313df8 --- /dev/null +++ b/package/secubox/luci-app-secubox-crowdsec/root/etc/config/crowdsec @@ -0,0 +1,25 @@ +config crowdsec 'crowdsec' + option enabled '1' + option data_dir '/srv/crowdsec/data' + option db_path '/srv/crowdsec/data/crowdsec.db' + +config acquisition 'acquisition' + option syslog_enabled '1' + option firewall_enabled '1' + option ssh_enabled '1' + option http_enabled '0' + +config hub 'hub' + option auto_install '1' + option collections 'crowdsecurity/linux crowdsecurity/sshd crowdsecurity/iptables' + option update_interval '7' + +config bouncer 'bouncer' + option enabled '1' + option ipv4 '1' + option ipv6 '1' + option deny_action 'drop' + option deny_log '1' + option update_frequency '10s' + option filter_input '1' + option filter_forward '1' diff --git a/package/secubox/luci-app-secubox-crowdsec/root/usr/libexec/rpcd/luci.secubox-crowdsec b/package/secubox/luci-app-secubox-crowdsec/root/usr/libexec/rpcd/luci.secubox-crowdsec new file mode 100644 index 00000000..f79de565 --- /dev/null +++ b/package/secubox/luci-app-secubox-crowdsec/root/usr/libexec/rpcd/luci.secubox-crowdsec @@ -0,0 +1,691 @@ +#!/bin/sh +# SPDX-License-Identifier: MIT +# SecuBox CrowdSec RPCD Backend +# Copyright (C) 2025 CyberMind.fr - Gandalf + +. /lib/functions.sh +. /usr/share/libubox/jshn.sh + +CSCLI="/usr/bin/cscli" +CSCLI_TIMEOUT=10 + +# Execution cscli avec timeout pour eviter les blocages +run_cscli() { + timeout "$CSCLI_TIMEOUT" "$CSCLI" "$@" 2>/dev/null +} + +# ============================================================================= +# FONCTIONS STATUS +# ============================================================================= + +# Statut global des services +get_status() { + json_init + + # Service CrowdSec + if pgrep -x crowdsec >/dev/null 2>&1; then + json_add_string "crowdsec" "running" + else + json_add_string "crowdsec" "stopped" + fi + + # Service Bouncer + if pgrep -f "crowdsec-firewall-bouncer" >/dev/null 2>&1; then + json_add_string "bouncer" "running" + else + json_add_string "bouncer" "stopped" + fi + + # Service syslog-ng + if pgrep -f "syslog-ng" >/dev/null 2>&1; then + json_add_string "syslog" "running" + else + json_add_string "syslog" "stopped" + fi + + # Version CrowdSec + local version + version=$(run_cscli version 2>/dev/null | grep "version:" | awk '{print $2}') + json_add_string "version" "${version:-unknown}" + + # LAPI status + local lapi_status="unavailable" + if [ -x "$CSCLI" ]; then + if run_cscli lapi status >/dev/null 2>&1; then + lapi_status="available" + fi + fi + json_add_string "lapi_status" "$lapi_status" + + # CAPI status + local capi_status="unknown" + local capi_output + capi_output=$(run_cscli capi status 2>/dev/null) + if echo "$capi_output" | grep -qi "online\|connected"; then + capi_status="connected" + elif echo "$capi_output" | grep -qi "offline\|disconnected"; then + capi_status="disconnected" + fi + json_add_string "capi_status" "$capi_status" + + # Tables nftables + local nft_ipv4=0 + local nft_ipv6=0 + if command -v nft >/dev/null 2>&1; then + nft list tables 2>/dev/null | grep -q "ip crowdsec" && nft_ipv4=1 + nft list tables 2>/dev/null | grep -q "ip6 crowdsec6" && nft_ipv6=1 + fi + json_add_boolean "nftables_ipv4" "$nft_ipv4" + json_add_boolean "nftables_ipv6" "$nft_ipv6" + + json_dump +} + +# ============================================================================= +# FONCTIONS DECISIONS +# ============================================================================= + +# Liste des decisions actives +get_decisions() { + if [ ! -x "$CSCLI" ]; then + echo '{"decisions":[],"error":"cscli not found"}' + return + fi + + if ! pgrep -x crowdsec >/dev/null 2>&1; then + echo '{"decisions":[],"error":"crowdsec not running"}' + return + fi + + local output + output=$(run_cscli decisions list -o json) + if [ -z "$output" ] || [ "$output" = "null" ]; then + echo '{"decisions":[]}' + else + echo "{\"decisions\":$output}" + fi +} + +# Ajouter une decision (ban) +add_decision() { + local ip="$1" + local duration="${2:-24h}" + local reason="${3:-Ban manuel via LuCI}" + local decision_type="${4:-ban}" + + json_init + + if [ -z "$ip" ]; then + json_add_boolean "success" 0 + json_add_string "error" "IP requise" + json_dump + return + fi + + local result + result=$(run_cscli decisions add --ip "$ip" --duration "$duration" --reason "$reason" --type "$decision_type" 2>&1) + + if [ $? -eq 0 ]; then + json_add_boolean "success" 1 + json_add_string "message" "IP $ip bannie pour $duration" + logger -t "secubox-crowdsec" "Ban manuel: $ip pour $duration - $reason" + else + json_add_boolean "success" 0 + json_add_string "error" "$result" + fi + + json_dump +} + +# Supprimer une decision (unban) +delete_decision() { + local ip="$1" + local decision_id="$2" + + json_init + + local result + if [ -n "$decision_id" ]; then + result=$(run_cscli decisions delete --id "$decision_id" 2>&1) + elif [ -n "$ip" ]; then + result=$(run_cscli decisions delete --ip "$ip" 2>&1) + else + json_add_boolean "success" 0 + json_add_string "error" "IP ou ID decision requis" + json_dump + return + fi + + if [ $? -eq 0 ]; then + json_add_boolean "success" 1 + json_add_string "message" "Decision supprimee" + logger -t "secubox-crowdsec" "Unban: ${ip:-ID:$decision_id}" + else + json_add_boolean "success" 0 + json_add_string "error" "$result" + fi + + json_dump +} + +# ============================================================================= +# FONCTIONS ALERTES +# ============================================================================= + +# Liste des alertes +get_alerts() { + local limit="${1:-50}" + local since="${2:-24h}" + + if [ ! -x "$CSCLI" ]; then + echo '{"alerts":[],"error":"cscli not found"}' + return + fi + + local output + output=$(run_cscli alerts list -o json --limit "$limit" --since "$since") + if [ -z "$output" ] || [ "$output" = "null" ]; then + echo '{"alerts":[]}' + else + echo "{\"alerts\":$output}" + fi +} + +# ============================================================================= +# FONCTIONS METRIQUES +# ============================================================================= + +# Metriques globales +get_metrics() { + if [ ! -x "$CSCLI" ]; then + echo '{"error":"cscli not found"}' + return + fi + + local output + output=$(run_cscli metrics -o json) + if [ -z "$output" ]; then + echo '{}' + else + echo "$output" + fi +} + +# Statistiques resumees +get_stats() { + json_init + + if [ ! -x "$CSCLI" ] || ! pgrep -x crowdsec >/dev/null 2>&1; then + json_add_int "total_decisions" 0 + json_add_int "alerts_24h" 0 + json_add_int "bouncers" 0 + json_add_int "parsers" 0 + json_add_int "scenarios" 0 + json_dump + return + fi + + # Compter les decisions actives + local decisions_count + decisions_count=$(run_cscli decisions list -o json 2>/dev/null | grep -c '"id"' || echo "0") + json_add_int "total_decisions" "${decisions_count:-0}" + + # Compter les alertes 24h + local alerts_count + alerts_count=$(run_cscli alerts list -o json --since 24h 2>/dev/null | grep -c '"id"' || echo "0") + json_add_int "alerts_24h" "${alerts_count:-0}" + + # Compter les bouncers + local bouncers_count + bouncers_count=$(run_cscli bouncers list -o json 2>/dev/null | grep -c '"name"' || echo "0") + json_add_int "bouncers" "${bouncers_count:-0}" + + # Compter les parsers installes + local parsers_count + parsers_count=$(run_cscli parsers list 2>/dev/null | grep -c "INSTALLED" || echo "0") + json_add_int "parsers" "${parsers_count:-0}" + + # Compter les scenarios installes + local scenarios_count + scenarios_count=$(run_cscli scenarios list 2>/dev/null | grep -c "INSTALLED" || echo "0") + json_add_int "scenarios" "${scenarios_count:-0}" + + json_dump +} + +# ============================================================================= +# FONCTIONS COLLECTIONS +# ============================================================================= + +# Liste des collections +get_collections() { + if [ ! -x "$CSCLI" ]; then + echo '{"collections":[]}' + return + fi + + local output + output=$(run_cscli collections list -o json) + if [ -z "$output" ] || [ "$output" = "null" ]; then + echo '{"collections":[]}' + else + echo "{\"collections\":$output}" + fi +} + +# Installer une collection +install_collection() { + local collection="$1" + + json_init + + if [ -z "$collection" ]; then + json_add_boolean "success" 0 + json_add_string "error" "Nom de collection requis" + json_dump + return + fi + + local result + result=$(run_cscli collections install "$collection" 2>&1) + + if [ $? -eq 0 ]; then + json_add_boolean "success" 1 + json_add_string "message" "Collection $collection installee" + logger -t "secubox-crowdsec" "Collection installee: $collection" + else + json_add_boolean "success" 0 + json_add_string "error" "$result" + fi + + json_dump +} + +# Desinstaller une collection +remove_collection() { + local collection="$1" + + json_init + + if [ -z "$collection" ]; then + json_add_boolean "success" 0 + json_add_string "error" "Nom de collection requis" + json_dump + return + fi + + local result + result=$(run_cscli collections remove "$collection" 2>&1) + + if [ $? -eq 0 ]; then + json_add_boolean "success" 1 + json_add_string "message" "Collection $collection supprimee" + logger -t "secubox-crowdsec" "Collection supprimee: $collection" + else + json_add_boolean "success" 0 + json_add_string "error" "$result" + fi + + json_dump +} + +# ============================================================================= +# FONCTIONS HUB +# ============================================================================= + +# Mise a jour du hub +update_hub() { + json_init + + local result + result=$(run_cscli hub update 2>&1) + + if [ $? -eq 0 ]; then + json_add_boolean "success" 1 + json_add_string "message" "Hub mis a jour" + logger -t "secubox-crowdsec" "Hub mis a jour" + else + json_add_boolean "success" 0 + json_add_string "error" "$result" + fi + + json_dump +} + +# Upgrade du hub +upgrade_hub() { + json_init + + run_cscli hub update >/dev/null 2>&1 + local result + result=$(run_cscli hub upgrade 2>&1) + + if [ $? -eq 0 ]; then + json_add_boolean "success" 1 + json_add_string "message" "Hub upgrade effectue" + logger -t "secubox-crowdsec" "Hub upgrade effectue" + else + json_add_boolean "success" 0 + json_add_string "error" "$result" + fi + + json_dump +} + +# ============================================================================= +# FONCTIONS BOUNCERS +# ============================================================================= + +# Liste des bouncers +get_bouncers() { + if [ ! -x "$CSCLI" ]; then + echo '{"bouncers":[]}' + return + fi + + local output + output=$(run_cscli bouncers list -o json) + if [ -z "$output" ] || [ "$output" = "null" ]; then + echo '{"bouncers":[]}' + else + echo "{\"bouncers\":$output}" + fi +} + +# ============================================================================= +# FONCTIONS SERVICES +# ============================================================================= + +# Controle des services +control_service() { + local service="$1" + local action="$2" + + json_init + + case "$service" in + crowdsec|syslog-ng|crowdsec-firewall-bouncer) + ;; + *) + json_add_boolean "success" 0 + json_add_string "error" "Service invalide: $service" + json_dump + return + ;; + esac + + case "$action" in + start|stop|restart|enable|disable) + ;; + *) + json_add_boolean "success" 0 + json_add_string "error" "Action invalide: $action" + json_dump + return + ;; + esac + + if /etc/init.d/"$service" "$action" >/dev/null 2>&1; then + json_add_boolean "success" 1 + json_add_string "message" "Service $service: $action OK" + logger -t "secubox-crowdsec" "Service $service: $action" + else + json_add_boolean "success" 0 + json_add_string "error" "Echec $action sur $service" + fi + + json_dump +} + +# ============================================================================= +# FONCTIONS NFTABLES +# ============================================================================= + +# Statistiques nftables +get_nftables_stats() { + json_init + + if ! command -v nft >/dev/null 2>&1; then + json_add_boolean "available" 0 + json_add_string "error" "nftables non disponible" + json_dump + return + fi + + json_add_boolean "available" 1 + + # Table IPv4 + local ipv4_exists=0 + if nft list table ip crowdsec >/dev/null 2>&1; then + ipv4_exists=1 + fi + json_add_boolean "ipv4_table" "$ipv4_exists" + + # Table IPv6 + local ipv6_exists=0 + if nft list table ip6 crowdsec6 >/dev/null 2>&1; then + ipv6_exists=1 + fi + json_add_boolean "ipv6_table" "$ipv6_exists" + + # Compter les IPs bloquees + local ipv4_count=0 + local ipv6_count=0 + + if [ "$ipv4_exists" = "1" ]; then + ipv4_count=$(nft list set ip crowdsec crowdsec-blacklists 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' | wc -l || echo "0") + fi + if [ "$ipv6_exists" = "1" ]; then + ipv6_count=$(nft list set ip6 crowdsec6 crowdsec6-blacklists 2>/dev/null | grep -c ':' || echo "0") + fi + + json_add_int "blocked_ipv4" "$ipv4_count" + json_add_int "blocked_ipv6" "$ipv6_count" + + json_dump +} + +# Liste des IPs bloquees +get_blocked_ips() { + json_init + json_add_array "ipv4" + + if nft list set ip crowdsec crowdsec-blacklists 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+(/[0-9]+)?' > /tmp/blocked_ips.tmp; then + while read -r ip; do + json_add_string "" "$ip" + done < /tmp/blocked_ips.tmp + rm -f /tmp/blocked_ips.tmp + fi + + json_close_array + + json_add_array "ipv6" + # IPv6 plus complexe, on simplifie + json_close_array + + json_dump +} + +# ============================================================================= +# FONCTIONS CONFIGURATION +# ============================================================================= + +# Lire la configuration UCI +get_config() { + json_init + + # Section crowdsec + local enabled=$(uci -q get crowdsec.crowdsec.enabled || echo "1") + json_add_string "enabled" "$enabled" + + # Section bouncer + local bouncer_enabled=$(uci -q get crowdsec.bouncer.enabled || echo "1") + local ipv4=$(uci -q get crowdsec.bouncer.ipv4 || echo "1") + local ipv6=$(uci -q get crowdsec.bouncer.ipv6 || echo "1") + local deny_action=$(uci -q get crowdsec.bouncer.deny_action || echo "drop") + local deny_log=$(uci -q get crowdsec.bouncer.deny_log || echo "1") + local update_freq=$(uci -q get crowdsec.bouncer.update_frequency || echo "10s") + + json_add_object "bouncer" + json_add_string "enabled" "$bouncer_enabled" + json_add_string "ipv4" "$ipv4" + json_add_string "ipv6" "$ipv6" + json_add_string "deny_action" "$deny_action" + json_add_string "deny_log" "$deny_log" + json_add_string "update_frequency" "$update_freq" + json_close_object + + # Section acquisition + local syslog_enabled=$(uci -q get crowdsec.acquisition.syslog_enabled || echo "1") + local firewall_enabled=$(uci -q get crowdsec.acquisition.firewall_enabled || echo "1") + local ssh_enabled=$(uci -q get crowdsec.acquisition.ssh_enabled || echo "1") + local http_enabled=$(uci -q get crowdsec.acquisition.http_enabled || echo "0") + + json_add_object "acquisition" + json_add_string "syslog_enabled" "$syslog_enabled" + json_add_string "firewall_enabled" "$firewall_enabled" + json_add_string "ssh_enabled" "$ssh_enabled" + json_add_string "http_enabled" "$http_enabled" + json_close_object + + json_dump +} + +# Sauvegarder la configuration UCI +save_config() { + local key="$1" + local value="$2" + + json_init + + if [ -z "$key" ]; then + json_add_boolean "success" 0 + json_add_string "error" "Cle requise" + json_dump + return + fi + + if uci set "crowdsec.$key=$value" && uci commit crowdsec; then + json_add_boolean "success" 1 + json_add_string "message" "Configuration sauvegardee" + logger -t "secubox-crowdsec" "Config: $key=$value" + else + json_add_boolean "success" 0 + json_add_string "error" "Echec sauvegarde configuration" + fi + + json_dump +} + +# ============================================================================= +# DISPATCHER PRINCIPAL +# ============================================================================= + +case "$1" in + list) + cat << 'EOF' +{ + "status":{}, + "decisions":{}, + "add_decision":{"ip":"string","duration":"string","reason":"string","type":"string"}, + "delete_decision":{"ip":"string","decision_id":"string"}, + "alerts":{"limit":"number","since":"string"}, + "metrics":{}, + "stats":{}, + "collections":{}, + "install_collection":{"collection":"string"}, + "remove_collection":{"collection":"string"}, + "update_hub":{}, + "upgrade_hub":{}, + "bouncers":{}, + "control_service":{"service":"string","action":"string"}, + "nftables_stats":{}, + "blocked_ips":{}, + "config":{}, + "save_config":{"key":"string","value":"string"} +} +EOF + ;; + call) + case "$2" in + status) + get_status + ;; + decisions) + get_decisions + ;; + add_decision) + read -r input + ip=$(echo "$input" | jsonfilter -e '@.ip' 2>/dev/null) + duration=$(echo "$input" | jsonfilter -e '@.duration' 2>/dev/null) + reason=$(echo "$input" | jsonfilter -e '@.reason' 2>/dev/null) + dtype=$(echo "$input" | jsonfilter -e '@.type' 2>/dev/null) + add_decision "$ip" "$duration" "$reason" "$dtype" + ;; + delete_decision) + read -r input + ip=$(echo "$input" | jsonfilter -e '@.ip' 2>/dev/null) + decision_id=$(echo "$input" | jsonfilter -e '@.decision_id' 2>/dev/null) + delete_decision "$ip" "$decision_id" + ;; + alerts) + read -r input + limit=$(echo "$input" | jsonfilter -e '@.limit' 2>/dev/null) + since=$(echo "$input" | jsonfilter -e '@.since' 2>/dev/null) + get_alerts "${limit:-50}" "${since:-24h}" + ;; + metrics) + get_metrics + ;; + stats) + get_stats + ;; + collections) + get_collections + ;; + install_collection) + read -r input + collection=$(echo "$input" | jsonfilter -e '@.collection' 2>/dev/null) + install_collection "$collection" + ;; + remove_collection) + read -r input + collection=$(echo "$input" | jsonfilter -e '@.collection' 2>/dev/null) + remove_collection "$collection" + ;; + update_hub) + update_hub + ;; + upgrade_hub) + upgrade_hub + ;; + bouncers) + get_bouncers + ;; + control_service) + read -r input + service=$(echo "$input" | jsonfilter -e '@.service' 2>/dev/null) + action=$(echo "$input" | jsonfilter -e '@.action' 2>/dev/null) + control_service "$service" "$action" + ;; + nftables_stats) + get_nftables_stats + ;; + blocked_ips) + get_blocked_ips + ;; + config) + get_config + ;; + save_config) + read -r input + key=$(echo "$input" | jsonfilter -e '@.key' 2>/dev/null) + value=$(echo "$input" | jsonfilter -e '@.value' 2>/dev/null) + save_config "$key" "$value" + ;; + *) + echo '{"error":"Unknown method"}' + ;; + esac + ;; +esac diff --git a/package/secubox/luci-app-secubox-crowdsec/root/usr/share/luci/menu.d/luci-app-secubox-crowdsec.json b/package/secubox/luci-app-secubox-crowdsec/root/usr/share/luci/menu.d/luci-app-secubox-crowdsec.json new file mode 100644 index 00000000..308a849b --- /dev/null +++ b/package/secubox/luci-app-secubox-crowdsec/root/usr/share/luci/menu.d/luci-app-secubox-crowdsec.json @@ -0,0 +1,53 @@ +{ + "admin/services/secubox-crowdsec": { + "title": "CrowdSec", + "order": 80, + "action": { + "type": "firstchild" + }, + "depends": { + "acl": ["luci-app-secubox-crowdsec"], + "uci": { "crowdsec": true } + } + }, + "admin/services/secubox-crowdsec/dashboard": { + "title": "Dashboard", + "order": 10, + "action": { + "type": "view", + "path": "secubox-crowdsec/dashboard" + } + }, + "admin/services/secubox-crowdsec/decisions": { + "title": "Decisions", + "order": 20, + "action": { + "type": "view", + "path": "secubox-crowdsec/decisions" + } + }, + "admin/services/secubox-crowdsec/alerts": { + "title": "Alerts", + "order": 30, + "action": { + "type": "view", + "path": "secubox-crowdsec/alerts" + } + }, + "admin/services/secubox-crowdsec/collections": { + "title": "Collections", + "order": 40, + "action": { + "type": "view", + "path": "secubox-crowdsec/collections" + } + }, + "admin/services/secubox-crowdsec/settings": { + "title": "Settings", + "order": 50, + "action": { + "type": "view", + "path": "secubox-crowdsec/settings" + } + } +} diff --git a/package/secubox/luci-app-secubox-crowdsec/root/usr/share/rpcd/acl.d/luci-app-secubox-crowdsec.json b/package/secubox/luci-app-secubox-crowdsec/root/usr/share/rpcd/acl.d/luci-app-secubox-crowdsec.json new file mode 100644 index 00000000..b022d4a3 --- /dev/null +++ b/package/secubox/luci-app-secubox-crowdsec/root/usr/share/rpcd/acl.d/luci-app-secubox-crowdsec.json @@ -0,0 +1,47 @@ +{ + "luci-app-secubox-crowdsec": { + "description": "Grant access to SecuBox CrowdSec Dashboard", + "read": { + "ubus": { + "luci.secubox-crowdsec": [ + "status", + "decisions", + "alerts", + "metrics", + "stats", + "collections", + "bouncers", + "nftables_stats", + "blocked_ips", + "config" + ] + }, + "uci": [ + "crowdsec" + ], + "file": { + "/etc/crowdsec/*": ["read"], + "/etc/crowdsec/acquis.d/*": ["read"], + "/var/log/crowdsec*.log": ["read"], + "/tmp/log/*.log": ["read"] + } + }, + "write": { + "ubus": { + "luci.secubox-crowdsec": [ + "add_decision", + "delete_decision", + "install_collection", + "remove_collection", + "update_hub", + "upgrade_hub", + "control_service", + "save_config" + ] + }, + "uci": [ + "crowdsec" + ] + } + } +} diff --git a/package/secubox/secubox-app-crowdsec/Makefile b/package/secubox/secubox-app-crowdsec/Makefile index 887957f1..5d8072d8 100644 --- a/package/secubox/secubox-app-crowdsec/Makefile +++ b/package/secubox/secubox-app-crowdsec/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=crowdsec PKG_VERSION:=1.7.4 -PKG_RELEASE:=2 +PKG_RELEASE:=3 PKG_ARCH:=all PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz @@ -205,6 +205,21 @@ define Package/crowdsec/install $(GO_PKG_BUILD_DIR)/src/$(GO_PKG)/config/patterns/* \ $(1)/etc/crowdsec/patterns + # Install acquisition configuration directory and templates + $(INSTALL_DIR) $(1)/etc/crowdsec/acquis.d + $(INSTALL_DATA) \ + ./files/acquis.d/openwrt-syslog.yaml \ + $(1)/etc/crowdsec/acquis.d/ + $(INSTALL_DATA) \ + ./files/acquis.d/openwrt-dropbear.yaml \ + $(1)/etc/crowdsec/acquis.d/ + $(INSTALL_DATA) \ + ./files/acquis.d/openwrt-firewall.yaml \ + $(1)/etc/crowdsec/acquis.d/ + $(INSTALL_DATA) \ + ./files/acquis.d/openwrt-uhttpd.yaml \ + $(1)/etc/crowdsec/acquis.d/ + $(INSTALL_DIR) $(1)/srv/crowdsec/data/ $(INSTALL_DIR) $(1)/etc/init.d @@ -227,6 +242,7 @@ endef define Package/crowdsec/conffiles /etc/crowdsec/ +/etc/crowdsec/acquis.d/ /etc/config/crowdsec endef diff --git a/package/secubox/secubox-app-crowdsec/README.md b/package/secubox/secubox-app-crowdsec/README.md index 1ffb4377..d6787b0e 100644 --- a/package/secubox/secubox-app-crowdsec/README.md +++ b/package/secubox/secubox-app-crowdsec/README.md @@ -3,30 +3,35 @@ ## Version - **Package**: secubox-app-crowdsec - **CrowdSec Core**: v1.7.4 -- **Release**: 1 -- **Last Updated**: December 30, 2024 +- **Release**: 3 +- **Last Updated**: January 2025 ## Description -CrowdSec is an open-source, lightweight security engine that detects and responds to malicious behaviors. This SecuBox package provides CrowdSec for OpenWrt routers. +CrowdSec is an open-source, lightweight security engine that detects and responds to malicious behaviors. This SecuBox package provides CrowdSec for OpenWrt routers with automatic log acquisition configuration. ## Key Features (v1.7.4) -- ✅ WAF capability with DropRequest helper for request blocking -- ✅ Refactored syslog acquisition using RestartableStreamer -- ✅ Optional pure-go SQLite driver for better compatibility -- ✅ Enhanced logging configuration with syslog media support -- ✅ Configurable usage metrics export (api.server.disable_usage_metrics_export) -- ✅ Fixed LAPI metrics cardinality issues with Prometheus -- ✅ Data race prevention in Docker acquisition -- ✅ Database query optimization for decision streams +- WAF capability with DropRequest helper for request blocking +- Refactored syslog acquisition using RestartableStreamer +- Optional pure-go SQLite driver for better compatibility +- Enhanced logging configuration with syslog media support +- Configurable usage metrics export (api.server.disable_usage_metrics_export) +- Fixed LAPI metrics cardinality issues with Prometheus +- Data race prevention in Docker acquisition +- Database query optimization for decision streams +- **Automatic OpenWrt log acquisition configuration** +- **UCI-based acquisition management** ## Package Contents - **Makefile**: OpenWrt package definition for CrowdSec v1.7.4 - **files/**: Configuration and init scripts - `crowdsec.initd`: Init script for service management - - `crowdsec.config`: UCI configuration - - `crowdsec.defaults`: Default configuration (uci-defaults) -- **patches/**: Patches for OpenWrt compatibility - - `001-fix_config_data_dir.patch`: Fix data directory path for OpenWrt + - `crowdsec.config`: UCI configuration (with acquisition settings) + - `crowdsec.defaults`: Default configuration with auto-detection + - `acquis.d/`: Acquisition configuration templates + - `openwrt-syslog.yaml`: System syslog logs + - `openwrt-dropbear.yaml`: SSH/Dropbear logs + - `openwrt-firewall.yaml`: iptables/nftables firewall logs + - `openwrt-uhttpd.yaml`: uHTTPd web server logs ## Installation ```bash @@ -35,17 +40,82 @@ cd /home/reepost/CyberMindStudio/_files/secubox-openwrt make package/secubox/secubox-app-crowdsec/compile V=s # Install on router -opkg install crowdsec_1.7.4-1_*.ipk +opkg install crowdsec_1.7.4-3_*.ipk ``` ## Configuration -CrowdSec configuration files are located at: + +### UCI Configuration +CrowdSec uses UCI for configuration in `/etc/config/crowdsec`: + +```bash +# View current configuration +uci show crowdsec + +# Main settings +uci set crowdsec.crowdsec.data_dir='/srv/crowdsec/data' +uci set crowdsec.crowdsec.db_path='/srv/crowdsec/data/crowdsec.db' + +# Acquisition settings +uci set crowdsec.acquisition.syslog_enabled='1' +uci set crowdsec.acquisition.firewall_enabled='1' +uci set crowdsec.acquisition.ssh_enabled='1' +uci set crowdsec.acquisition.http_enabled='0' +uci set crowdsec.acquisition.syslog_path='/var/log/messages' + +# Hub settings +uci set crowdsec.hub.auto_install='1' +uci set crowdsec.hub.collections='crowdsecurity/linux crowdsecurity/iptables' +uci set crowdsec.hub.update_interval='7' + +uci commit crowdsec +``` + +### File Locations - Main config: `/etc/crowdsec/config.yaml` -- Acquisition: `/etc/crowdsec/acquis.yaml` +- Acquisition directory: `/etc/crowdsec/acquis.d/` +- Legacy acquisition: `/etc/crowdsec/acquis.yaml` - Profiles: `/etc/crowdsec/profiles.yaml` - Local API: `/etc/crowdsec/local_api_credentials.yaml` +- Data directory: `/srv/crowdsec/data/` -Data directory: `/srv/crowdsec/data/` +## Log Acquisition Configuration + +### Automatic Detection +On first boot, the defaults script automatically: +1. Detects OpenWrt log file configuration +2. Identifies installed services (Dropbear, firewall) +3. Generates appropriate acquisition configs +4. Installs recommended Hub collections + +### Supported Log Sources +| Log Source | Default | Collection Required | +|------------|---------|---------------------| +| System Syslog | Enabled | crowdsecurity/linux | +| SSH/Dropbear | Enabled | crowdsecurity/linux | +| Firewall (iptables/nftables) | Enabled | crowdsecurity/iptables | +| HTTP (uHTTPd/nginx) | Disabled | crowdsecurity/http-cve | + +### Custom Acquisition +Add custom acquisition configs to `/etc/crowdsec/acquis.d/`: + +```yaml +# /etc/crowdsec/acquis.d/custom.yaml +filenames: + - /var/log/custom-app/*.log +labels: + type: syslog +``` + +### Syslog Service Mode +To run CrowdSec as a syslog server (receive logs from other devices): + +```bash +uci set crowdsec.acquisition.syslog_listen_addr='0.0.0.0' +uci set crowdsec.acquisition.syslog_listen_port='514' +uci commit crowdsec +/etc/init.d/crowdsec restart +``` ## Service Management ```bash @@ -68,6 +138,9 @@ CrowdSec CLI is available via `cscli`: # Check version cscli version +# Check acquisition status +cscli metrics show acquisition + # List decisions cscli decisions list @@ -78,14 +151,48 @@ cscli alerts list cscli collections list cscli collections install crowdsecurity/nginx +# Manage Hub +cscli hub update +cscli hub upgrade + # Manage bouncers cscli bouncers list cscli bouncers add firewall-bouncer ``` +## Hub Collections for OpenWrt + +### Recommended Collections +```bash +# Core Linux detection (SSH brute-force, etc.) +cscli collections install crowdsecurity/linux + +# Firewall log analysis (port scan detection) +cscli collections install crowdsecurity/iptables + +# Syslog parsing +cscli parsers install crowdsecurity/syslog-logs + +# Whitelists for reducing false positives +cscli parsers install crowdsecurity/whitelists +``` + +### Optional Collections +```bash +# HTTP attack detection +cscli collections install crowdsecurity/http-cve + +# nginx logs +cscli collections install crowdsecurity/nginx + +# Smb/Samba +cscli collections install crowdsecurity/smb +``` + ## Integration with SecuBox This package integrates with: - **luci-app-crowdsec-dashboard** v0.5.0+ +- **secubox-app-crowdsec-bouncer** - Firewall bouncer - **SecuBox Theme System** - **SecuBox Logging** (`secubox-log`) @@ -98,11 +205,20 @@ This package integrates with: - Upstream: https://github.com/crowdsecurity/crowdsec - Documentation: https://docs.crowdsec.net/ - Hub: https://hub.crowdsec.net/ +- Acquisition Docs: https://docs.crowdsec.net/docs/next/log_processor/data_sources/intro/ - SecuBox Project: https://cybermind.fr ## Changelog -### v1.7.4-1 (2024-12-30) +### v1.7.4-3 (2025-01) +- Added automatic log acquisition configuration +- Added UCI-based acquisition management +- Added acquis.d directory with OpenWrt-specific templates +- Improved Hub collection auto-installation +- Added acquisition for syslog, SSH/Dropbear, firewall, HTTP +- Enhanced defaults script with detection logic + +### v1.7.4-2 (2024-12) - Updated from v1.6.2 to v1.7.4 - Added WAF/AppSec support - Improved syslog acquisition diff --git a/package/secubox/secubox-app-crowdsec/files/acquis.d/openwrt-dropbear.yaml b/package/secubox/secubox-app-crowdsec/files/acquis.d/openwrt-dropbear.yaml new file mode 100644 index 00000000..e9246fe2 --- /dev/null +++ b/package/secubox/secubox-app-crowdsec/files/acquis.d/openwrt-dropbear.yaml @@ -0,0 +1,29 @@ +# OpenWrt Dropbear SSH Acquisition +# This configuration monitors SSH authentication logs from Dropbear +# +# Dropbear logs are typically sent to syslog and can be found in: +# - /var/log/messages (if syslog is configured to write to file) +# - Via logread command (OpenWrt default) +# +# Required collections: +# cscli collections install crowdsecurity/linux +# cscli parsers install crowdsecurity/syslog-logs +# +# The crowdsecurity/linux collection includes SSH brute-force detection +# scenarios that work with Dropbear authentication logs. +# +# Example Dropbear log entries that will be parsed: +# dropbear[1234]: Bad password attempt for 'root' from 192.168.1.100:54321 +# dropbear[1234]: Login attempt for nonexistent user 'admin' from 192.168.1.100:54321 +# dropbear[1234]: Pubkey auth succeeded for 'root' with ssh-ed25519 key +# dropbear[1234]: Exit (root) from <192.168.1.100:54321>: Disconnect received +# +# Note: Since Dropbear logs go to syslog, the openwrt-syslog.yaml +# acquisition config will capture these logs. This file serves as +# documentation for Dropbear-specific detection. + +# If using a dedicated auth log file: +# filenames: +# - /var/log/auth.log +# labels: +# type: syslog diff --git a/package/secubox/secubox-app-crowdsec/files/acquis.d/openwrt-firewall.yaml b/package/secubox/secubox-app-crowdsec/files/acquis.d/openwrt-firewall.yaml new file mode 100644 index 00000000..0d4b2490 --- /dev/null +++ b/package/secubox/secubox-app-crowdsec/files/acquis.d/openwrt-firewall.yaml @@ -0,0 +1,40 @@ +# OpenWrt Firewall Logs Acquisition +# This configuration monitors iptables/nftables firewall logs +# +# Required collections: +# cscli collections install crowdsecurity/iptables +# +# The crowdsecurity/iptables collection provides: +# - crowdsecurity/iptables-logs parser (for -j LOG entries) +# - crowdsecurity/iptables-scan-multi_ports scenario (port scan detection) +# +# To enable firewall logging in OpenWrt, add LOG rules to your firewall config: +# +# For nftables (OpenWrt 22.03+): +# nft add rule inet fw4 input counter log prefix "fw4-INPUT: " drop +# +# For iptables (legacy): +# iptables -A INPUT -j LOG --log-prefix "iptables-INPUT: " +# +# Or via /etc/config/firewall: +# config rule +# option name 'Log-Dropped' +# option src 'wan' +# option dest '*' +# option proto 'all' +# option target 'LOG' +# option log_prefix 'fw-DROP: ' +# +# Firewall logs are typically written to kernel log (kern.log) +# or syslog depending on system configuration. + +# Kernel/firewall log file acquisition +filenames: + - /var/log/kern.log + - /var/log/firewall.log +labels: + type: syslog +--- +# Alternative: If firewall logs go to main syslog +# The openwrt-syslog.yaml acquisition will capture them +# as long as the iptables collection parser is installed diff --git a/package/secubox/secubox-app-crowdsec/files/acquis.d/openwrt-syslog.yaml b/package/secubox/secubox-app-crowdsec/files/acquis.d/openwrt-syslog.yaml new file mode 100644 index 00000000..0a6eb1fb --- /dev/null +++ b/package/secubox/secubox-app-crowdsec/files/acquis.d/openwrt-syslog.yaml @@ -0,0 +1,28 @@ +# OpenWrt System Syslog Acquisition +# This configuration monitors OpenWrt system logs via syslog +# For local log files or syslog forwarding scenarios +# +# Note: OpenWrt uses logd by default which doesn't write to files. +# Enable syslog-ng or configure log_file in /etc/config/system +# to enable file-based log acquisition. +# +# Required collections: +# cscli collections install crowdsecurity/linux +# cscli parsers install crowdsecurity/syslog-logs + +# File-based acquisition for syslog (if log_file is configured) +filenames: + - /var/log/messages + - /var/log/syslog +labels: + type: syslog +--- +# Alternative: Syslog service acquisition +# Uncomment this section if using remote syslog forwarding +# or if CrowdSec should act as a syslog server +# +# source: syslog +# listen_addr: 127.0.0.1 +# listen_port: 10514 +# labels: +# type: syslog diff --git a/package/secubox/secubox-app-crowdsec/files/acquis.d/openwrt-uhttpd.yaml b/package/secubox/secubox-app-crowdsec/files/acquis.d/openwrt-uhttpd.yaml new file mode 100644 index 00000000..3a4ac96e --- /dev/null +++ b/package/secubox/secubox-app-crowdsec/files/acquis.d/openwrt-uhttpd.yaml @@ -0,0 +1,29 @@ +# OpenWrt uHTTPd Web Server Acquisition +# This configuration monitors uHTTPd access/error logs +# +# By default, uHTTPd logs to syslog. To enable file-based logging, +# configure uHTTPd in /etc/config/uhttpd: +# +# config uhttpd 'main' +# option access_log '/var/log/uhttpd/access.log' +# option error_log '/var/log/uhttpd/error.log' +# +# Required parsers: +# cscli parsers install crowdsecurity/syslog-logs +# +# For HTTP-based attacks, consider installing: +# cscli collections install crowdsecurity/http-cve +# cscli scenarios install crowdsecurity/http-probing +# cscli scenarios install crowdsecurity/http-bad-user-agent + +# uHTTPd access logs +# filenames: +# - /var/log/uhttpd/access.log +# labels: +# type: syslog +--- +# uHTTPd error logs +# filenames: +# - /var/log/uhttpd/error.log +# labels: +# type: syslog diff --git a/package/secubox/secubox-app-crowdsec/files/crowdsec.config b/package/secubox/secubox-app-crowdsec/files/crowdsec.config index 2cfe5757..4dddf276 100644 --- a/package/secubox/secubox-app-crowdsec/files/crowdsec.config +++ b/package/secubox/secubox-app-crowdsec/files/crowdsec.config @@ -2,3 +2,29 @@ config crowdsec 'crowdsec' option data_dir '/srv/crowdsec/data' option db_path '/srv/crowdsec/data/crowdsec.db' +# Acquisition configuration +config acquisition 'acquisition' + # Enable/disable specific log sources + option syslog_enabled '1' + option firewall_enabled '1' + option ssh_enabled '1' + option http_enabled '0' + # Syslog service settings (if using CrowdSec as syslog server) + option syslog_listen_addr '127.0.0.1' + option syslog_listen_port '10514' + # Log file paths (OpenWrt-specific) + option syslog_path '/var/log/messages' + option auth_log_path '/var/log/auth.log' + option kernel_log_path '/var/log/kern.log' + +# Hub configuration +config hub 'hub' + # Auto-install recommended collections on first boot + option auto_install '1' + # Collections to install (space-separated) + option collections 'crowdsecurity/linux crowdsecurity/iptables' + # Additional parsers + option parsers 'crowdsecurity/syslog-logs crowdsecurity/whitelists' + # Hub update interval in days (0 to disable auto-update) + option update_interval '7' + diff --git a/package/secubox/secubox-app-crowdsec/files/crowdsec.defaults b/package/secubox/secubox-app-crowdsec/files/crowdsec.defaults index 7b1cff6c..13a3a21e 100644 --- a/package/secubox/secubox-app-crowdsec/files/crowdsec.defaults +++ b/package/secubox/secubox-app-crowdsec/files/crowdsec.defaults @@ -1,52 +1,317 @@ #!/bin/sh +# +# CrowdSec UCI Defaults Script +# Configures CrowdSec on first install with automatic acquisition setup +# CONFIG=/etc/crowdsec/config.yaml -data_dir=`uci get "crowdsec.crowdsec.data_dir"` -sed -i "s,^\(\s*data_dir\s*:\s*\).*\$,\1$data_dir," $CONFIG -db_path=`uci get "crowdsec.crowdsec.db_path"` -sed -i "s,^\(\s*db_path\s*:\s*\).*\$,\1$db_path," $CONFIG +ACQUIS_DIR=/etc/crowdsec/acquis.d +UCI_CONFIG=/etc/config/crowdsec -# Create data dir & permissions if needed -if [ ! -d "${data_dir}" ]; then - mkdir -m 0755 -p "${data_dir}" -fi; +# Load UCI functions +. /lib/functions.sh + +# Get UCI values with defaults +get_uci_value() { + local section="$1" + local option="$2" + local default="$3" + local value + value=$(uci -q get "crowdsec.${section}.${option}") + echo "${value:-$default}" +} + +# Configure data paths +setup_paths() { + local data_dir + local db_path + + data_dir=$(get_uci_value "crowdsec" "data_dir" "/srv/crowdsec/data") + db_path=$(get_uci_value "crowdsec" "db_path" "/srv/crowdsec/data/crowdsec.db") + + sed -i "s,^\(\s*data_dir\s*:\s*\).*\$,\1$data_dir," $CONFIG + sed -i "s,^\(\s*db_path\s*:\s*\).*\$,\1$db_path," $CONFIG + + # Create data dir & permissions if needed + if [ ! -d "${data_dir}" ]; then + mkdir -m 0755 -p "${data_dir}" + fi +} # Create machine-id if not exists -if [ ! -f /etc/machine-id ]; then - cat /proc/sys/kernel/random/uuid | tr -d "-" > /etc/machine-id -fi +setup_machine_id() { + if [ ! -f /etc/machine-id ]; then + cat /proc/sys/kernel/random/uuid | tr -d "-" > /etc/machine-id + echo "Created machine-id" + fi +} # Register local API machine -if grep -q "login:" /etc/crowdsec/local_api_credentials.yaml 2>/dev/null; then - echo "Local API already registered" -else - echo "Registering local API machine..." - cscli -c /etc/crowdsec/config.yaml machines add -a -f /etc/crowdsec/local_api_credentials.yaml -fi +register_lapi() { + if grep -q "login:" /etc/crowdsec/local_api_credentials.yaml 2>/dev/null; then + echo "Local API already registered" + else + echo "Registering local API machine..." + cscli -c /etc/crowdsec/config.yaml machines add -a -f /etc/crowdsec/local_api_credentials.yaml + fi +} # Register with Central API (CAPI) for threat intelligence sharing -if ! grep -q "login:" /etc/crowdsec/online_api_credentials.yaml 2>/dev/null; then - echo "Registering with Central API (CAPI)..." - if cscli capi register 2>/dev/null; then - echo "Successfully registered with Central API" +register_capi() { + if ! grep -q "login:" /etc/crowdsec/online_api_credentials.yaml 2>/dev/null; then + echo "Registering with Central API (CAPI)..." + if cscli capi register 2>/dev/null; then + echo "Successfully registered with Central API" + else + echo "WARNING: CAPI registration failed - will run in local-only mode" + # Create minimal credentials file to prevent errors + echo "url: https://api.crowdsec.net/" > /etc/crowdsec/online_api_credentials.yaml + fi else - echo "WARNING: CAPI registration failed - will run in local-only mode" - # Create minimal credentials file to prevent errors - echo "url: https://api.crowdsec.net/" > /etc/crowdsec/online_api_credentials.yaml + echo "Central API already registered" fi -else - echo "Central API already registered" -fi +} # Update hub index -if [ ! -f /etc/crowdsec/hub/.index.json ] || [ $(find /etc/crowdsec/hub/.index.json -mtime +7 2>/dev/null | wc -l) -gt 0 ]; then - echo "Updating hub index..." - cscli hub update 2>/dev/null || true -fi +update_hub() { + local update_interval + update_interval=$(get_uci_value "hub" "update_interval" "7") -# Install default collections -cscli collections install crowdsecurity/linux 2>/dev/null || true -cscli parsers install crowdsecurity/whitelists 2>/dev/null || true -cscli hub upgrade 2>/dev/null || true + if [ "$update_interval" = "0" ]; then + echo "Hub auto-update disabled" + return 0 + fi + + if [ ! -f /etc/crowdsec/hub/.index.json ] || \ + [ $(find /etc/crowdsec/hub/.index.json -mtime +${update_interval} 2>/dev/null | wc -l) -gt 0 ]; then + echo "Updating hub index..." + cscli hub update 2>/dev/null || true + fi +} + +# Install collections and parsers from Hub +install_hub_items() { + local auto_install + local collections + local parsers + + auto_install=$(get_uci_value "hub" "auto_install" "1") + + if [ "$auto_install" != "1" ]; then + echo "Hub auto-install disabled" + return 0 + fi + + # Install collections + collections=$(get_uci_value "hub" "collections" "crowdsecurity/linux crowdsecurity/iptables") + for collection in $collections; do + echo "Installing collection: $collection" + cscli collections install "$collection" 2>/dev/null || true + done + + # Install additional parsers + parsers=$(get_uci_value "hub" "parsers" "crowdsecurity/syslog-logs crowdsecurity/whitelists") + for parser in $parsers; do + echo "Installing parser: $parser" + cscli parsers install "$parser" 2>/dev/null || true + done + + # Upgrade all hub items + cscli hub upgrade 2>/dev/null || true +} + +# Generate dynamic acquisition configuration +generate_acquisition_config() { + local syslog_enabled + local firewall_enabled + local ssh_enabled + local http_enabled + local syslog_path + local kernel_log_path + local auth_log_path + + # Ensure acquis.d directory exists + mkdir -p "$ACQUIS_DIR" + + # Get acquisition settings from UCI + syslog_enabled=$(get_uci_value "acquisition" "syslog_enabled" "1") + firewall_enabled=$(get_uci_value "acquisition" "firewall_enabled" "1") + ssh_enabled=$(get_uci_value "acquisition" "ssh_enabled" "1") + http_enabled=$(get_uci_value "acquisition" "http_enabled" "0") + syslog_path=$(get_uci_value "acquisition" "syslog_path" "/var/log/messages") + kernel_log_path=$(get_uci_value "acquisition" "kernel_log_path" "/var/log/kern.log") + auth_log_path=$(get_uci_value "acquisition" "auth_log_path" "/var/log/auth.log") + + # Generate syslog acquisition config + if [ "$syslog_enabled" = "1" ]; then + echo "Configuring syslog acquisition..." + cat > "$ACQUIS_DIR/openwrt-syslog.yaml" << EOF +# OpenWrt System Syslog Acquisition +# Auto-generated by crowdsec.defaults +# Monitors system logs for security events + +filenames: + - ${syslog_path} + - /var/log/syslog +labels: + type: syslog +EOF + else + rm -f "$ACQUIS_DIR/openwrt-syslog.yaml" + fi + + # Generate firewall acquisition config + if [ "$firewall_enabled" = "1" ]; then + echo "Configuring firewall log acquisition..." + cat > "$ACQUIS_DIR/openwrt-firewall.yaml" << EOF +# OpenWrt Firewall Logs Acquisition +# Auto-generated by crowdsec.defaults +# Monitors iptables/nftables firewall logs for port scans + +filenames: + - ${kernel_log_path} + - /var/log/firewall.log +labels: + type: syslog +EOF + # Ensure iptables collection is installed + cscli collections install crowdsecurity/iptables 2>/dev/null || true + else + rm -f "$ACQUIS_DIR/openwrt-firewall.yaml" + fi + + # Generate SSH/auth acquisition config + if [ "$ssh_enabled" = "1" ]; then + echo "Configuring SSH/auth log acquisition..." + # SSH logs typically go to syslog on OpenWrt + # The syslog acquisition will capture them + # Just ensure the linux collection is installed for SSH scenarios + cscli collections install crowdsecurity/linux 2>/dev/null || true + fi + + # Generate HTTP acquisition config (disabled by default) + if [ "$http_enabled" = "1" ]; then + echo "Configuring HTTP log acquisition..." + cat > "$ACQUIS_DIR/openwrt-http.yaml" << EOF +# OpenWrt HTTP Server Logs Acquisition +# Auto-generated by crowdsec.defaults + +filenames: + - /var/log/uhttpd/access.log + - /var/log/nginx/access.log +labels: + type: syslog +EOF + else + rm -f "$ACQUIS_DIR/openwrt-http.yaml" + fi +} + +# Configure syslog service acquisition (if CrowdSec acts as syslog server) +configure_syslog_service() { + local listen_addr + local listen_port + + listen_addr=$(get_uci_value "acquisition" "syslog_listen_addr" "127.0.0.1") + listen_port=$(get_uci_value "acquisition" "syslog_listen_port" "10514") + + # Only create syslog service config if non-default port is configured + if [ "$listen_port" != "10514" ] || [ "$listen_addr" != "127.0.0.1" ]; then + echo "Configuring syslog service acquisition..." + cat > "$ACQUIS_DIR/syslog-service.yaml" << EOF +# Syslog Service Acquisition +# Auto-generated by crowdsec.defaults +# CrowdSec acts as a syslog server to receive logs + +source: syslog +listen_addr: ${listen_addr} +listen_port: ${listen_port} +labels: + type: syslog +EOF + fi +} + +# Detect and configure OpenWrt-specific log sources +detect_openwrt_logs() { + echo "Detecting OpenWrt log sources..." + + # Check if syslog-ng is installed and configured + if [ -f /etc/syslog-ng.conf ]; then + echo "syslog-ng detected" + fi + + # Check if rsyslog is configured + if [ -f /etc/rsyslog.conf ]; then + echo "rsyslog detected" + fi + + # Check if log_file is configured in OpenWrt system config + local log_file + log_file=$(uci -q get system.@system[0].log_file) + if [ -n "$log_file" ]; then + echo "OpenWrt log_file configured: $log_file" + # Update syslog path in UCI + uci set crowdsec.acquisition.syslog_path="$log_file" + uci commit crowdsec + fi + + # Check for Dropbear (SSH server) + if [ -f /etc/init.d/dropbear ]; then + echo "Dropbear SSH server detected" + fi + + # Check for firewall (fw3 or fw4) + if [ -f /etc/init.d/firewall ]; then + echo "OpenWrt firewall detected" + fi +} + +# Main execution +main() { + echo "==========================================" + echo "CrowdSec Configuration - First Boot Setup" + echo "==========================================" + + # Setup paths and directories + setup_paths + + # Create machine-id + setup_machine_id + + # Register with LAPI + register_lapi + + # Register with CAPI + register_capi + + # Update Hub index + update_hub + + # Install Hub collections and parsers + install_hub_items + + # Detect OpenWrt log sources + detect_openwrt_logs + + # Generate acquisition configuration + generate_acquisition_config + + # Configure syslog service if needed + configure_syslog_service + + echo "==========================================" + echo "CrowdSec configuration complete!" + echo "==========================================" + echo "" + echo "Next steps:" + echo " 1. Enable and start CrowdSec: /etc/init.d/crowdsec enable && /etc/init.d/crowdsec start" + echo " 2. Check acquisition status: cscli metrics show acquisition" + echo " 3. View decisions: cscli decisions list" + echo "" +} + +# Run main function +main exit 0 diff --git a/package/secubox/secubox-app/files/usr/share/secubox/plugins/catalog/crowdsec-bouncer.json b/package/secubox/secubox-app/files/usr/share/secubox/plugins/catalog/crowdsec-bouncer.json new file mode 100644 index 00000000..322b802a --- /dev/null +++ b/package/secubox/secubox-app/files/usr/share/secubox/plugins/catalog/crowdsec-bouncer.json @@ -0,0 +1,145 @@ +{ + "id": "crowdsec-bouncer", + "name": "CrowdSec Firewall Bouncer", + "category": "security", + "runtime": "native", + "maturity": "stable", + "description": "nftables-based firewall bouncer that automatically blocks malicious IPs from CrowdSec decisions.", + "source": { + "homepage": "https://crowdsec.net/", + "github": "https://github.com/crowdsecurity/cs-firewall-bouncer" + }, + "packages": [ + "secubox-app-crowdsec-bouncer", + "crowdsec-firewall-bouncer" + ], + "capabilities": [ + "ip-blocking", + "firewall-integration", + "nftables" + ], + "requirements": { + "arch": [ + "arm64", + "armv7", + "x86_64", + "mipsel" + ], + "min_ram_mb": 32, + "min_storage_mb": 10 + }, + "dependencies": [ + "crowdsec" + ], + "hardware": { + "usb": false, + "serial": false + }, + "network": { + "inbound_ports": [], + "protocols": [], + "outbound_only": true + }, + "privileges": { + "needs_usb": false, + "needs_serial": false, + "needs_net_admin": true + }, + "update": { + "strategy": "opkg" + }, + "wizard": { + "uci": { + "config": "crowdsec", + "section": "bouncer" + }, + "fields": [ + { + "id": "enabled", + "label": "Enable Bouncer", + "type": "checkbox", + "uci_option": "enabled", + "description": "Enable the firewall bouncer to block malicious IPs", + "default": "0" + }, + { + "id": "ipv4", + "label": "Enable IPv4 Blocking", + "type": "checkbox", + "uci_option": "ipv4", + "description": "Block IPv4 addresses", + "default": "1" + }, + { + "id": "ipv6", + "label": "Enable IPv6 Blocking", + "type": "checkbox", + "uci_option": "ipv6", + "description": "Block IPv6 addresses", + "default": "1" + }, + { + "id": "deny_action", + "label": "Deny Action", + "type": "select", + "uci_option": "deny_action", + "description": "Action to take when blocking IPs", + "options": [ + {"value": "drop", "label": "Drop (silent)"}, + {"value": "reject", "label": "Reject (with response)"}, + {"value": "tarpit", "label": "Tarpit (slow response)"} + ], + "default": "drop" + }, + { + "id": "deny_log", + "label": "Log Blocked Connections", + "type": "checkbox", + "uci_option": "deny_log", + "description": "Log blocked connections to syslog", + "default": "1" + }, + { + "id": "update_frequency", + "label": "Update Frequency", + "type": "select", + "uci_option": "update_frequency", + "description": "How often to check for new decisions", + "options": [ + {"value": "10s", "label": "10 seconds (recommended)"}, + {"value": "30s", "label": "30 seconds"}, + {"value": "1m", "label": "1 minute"}, + {"value": "5m", "label": "5 minutes"} + ], + "default": "10s" + }, + { + "id": "filter_input", + "label": "Filter INPUT Chain", + "type": "checkbox", + "uci_option": "filter_input", + "description": "Apply blocking to incoming traffic", + "default": "1" + }, + { + "id": "filter_forward", + "label": "Filter FORWARD Chain", + "type": "checkbox", + "uci_option": "filter_forward", + "description": "Apply blocking to forwarded traffic", + "default": "1" + } + ] + }, + "profiles": { + "recommended": [ + "gateway", + "smb", + "lab" + ] + }, + "actions": { + "status": "/etc/init.d/crowdsec-firewall-bouncer status", + "install": "cscli bouncers add crowdsec-firewall-bouncer -o raw 2>/dev/null || true" + } +} diff --git a/package/secubox/secubox-app/files/usr/share/secubox/plugins/catalog/crowdsec.json b/package/secubox/secubox-app/files/usr/share/secubox/plugins/catalog/crowdsec.json new file mode 100644 index 00000000..e1990196 --- /dev/null +++ b/package/secubox/secubox-app/files/usr/share/secubox/plugins/catalog/crowdsec.json @@ -0,0 +1,121 @@ +{ + "id": "crowdsec", + "name": "CrowdSec Security Engine", + "category": "security", + "runtime": "native", + "maturity": "stable", + "description": "Open-source, lightweight security engine that detects and responds to malicious behaviors with automatic log acquisition for OpenWrt.", + "source": { + "homepage": "https://crowdsec.net/", + "github": "https://github.com/crowdsecurity/crowdsec" + }, + "packages": [ + "crowdsec" + ], + "capabilities": [ + "intrusion-detection", + "threat-intel", + "log-analysis", + "ip-reputation" + ], + "requirements": { + "arch": [ + "arm64", + "armv7", + "x86_64", + "mipsel" + ], + "min_ram_mb": 128, + "min_storage_mb": 50 + }, + "hardware": { + "usb": false, + "serial": false + }, + "network": { + "inbound_ports": [ + 8080 + ], + "protocols": [ + "http" + ], + "outbound_only": false + }, + "privileges": { + "needs_usb": false, + "needs_serial": false, + "needs_net_admin": true + }, + "update": { + "strategy": "opkg" + }, + "wizard": { + "uci": { + "config": "crowdsec", + "section": "acquisition" + }, + "fields": [ + { + "id": "syslog_enabled", + "label": "Enable Syslog Acquisition", + "type": "checkbox", + "uci_option": "syslog_enabled", + "description": "Monitor system syslog for security events", + "default": "1" + }, + { + "id": "firewall_enabled", + "label": "Enable Firewall Log Acquisition", + "type": "checkbox", + "uci_option": "firewall_enabled", + "description": "Monitor iptables/nftables firewall logs for port scans", + "default": "1" + }, + { + "id": "ssh_enabled", + "label": "Enable SSH Log Acquisition", + "type": "checkbox", + "uci_option": "ssh_enabled", + "description": "Monitor SSH/Dropbear authentication logs", + "default": "1" + }, + { + "id": "http_enabled", + "label": "Enable HTTP Log Acquisition", + "type": "checkbox", + "uci_option": "http_enabled", + "description": "Monitor uHTTPd/nginx web server logs", + "default": "0" + }, + { + "id": "syslog_path", + "label": "Syslog File Path", + "type": "text", + "uci_option": "syslog_path", + "placeholder": "/var/log/messages", + "description": "Path to the main syslog file" + }, + { + "id": "kernel_log_path", + "label": "Kernel Log Path", + "type": "text", + "uci_option": "kernel_log_path", + "placeholder": "/var/log/kern.log", + "description": "Path to kernel/firewall log file" + } + ] + }, + "profiles": { + "recommended": [ + "gateway", + "smb", + "lab", + "home" + ] + }, + "actions": { + "status": "/etc/init.d/crowdsec status", + "install": "cscli hub update && cscli collections install crowdsecurity/linux crowdsecurity/iptables", + "update": "cscli hub upgrade" + } +} diff --git a/package/secubox/secubox-crowdsec-setup/Makefile b/package/secubox/secubox-crowdsec-setup/Makefile new file mode 100644 index 00000000..e002502d --- /dev/null +++ b/package/secubox/secubox-crowdsec-setup/Makefile @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: MIT +# +# SecuBox CrowdSec Setup Package +# Copyright (C) 2025 CyberMind.fr - Gandalf +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=secubox-crowdsec-setup +PKG_VERSION:=1.0.0 +PKG_RELEASE:=1 + +PKG_MAINTAINER:=Gerald Kerma +PKG_LICENSE:=MIT + +include $(INCLUDE_DIR)/package.mk + +define Package/secubox-crowdsec-setup + SECTION:=secubox + CATEGORY:=SecuBox + SUBMENU:=Security + TITLE:=SecuBox CrowdSec Setup Utility + DEPENDS:=+crowdsec +crowdsec-firewall-bouncer-nftables +syslog-ng4 + PKGARCH:=all +endef + +define Package/secubox-crowdsec-setup/description + Script d'installation automatisee de CrowdSec pour SecuBox. + Configure syslog-ng4 pour le forwarding des logs vers CrowdSec, + installe les collections de securite, et configure le bouncer + nftables pour fw4. +endef + +define Build/Compile +endef + +define Package/secubox-crowdsec-setup/install + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_BIN) ./files/usr/sbin/secubox-crowdsec-setup $(1)/usr/sbin/ + + $(INSTALL_DIR) $(1)/etc/secubox/backups/crowdsec +endef + +define Package/secubox-crowdsec-setup/postinst +#!/bin/sh +[ -n "$${IPKG_INSTROOT}" ] || { + echo "SecuBox CrowdSec Setup installe." + echo "Executez 'secubox-crowdsec-setup' pour configurer CrowdSec." +} +exit 0 +endef + +$(eval $(call BuildPackage,secubox-crowdsec-setup)) diff --git a/package/secubox/secubox-crowdsec-setup/files/usr/sbin/secubox-crowdsec-setup b/package/secubox/secubox-crowdsec-setup/files/usr/sbin/secubox-crowdsec-setup new file mode 100644 index 00000000..0b5728ad --- /dev/null +++ b/package/secubox/secubox-crowdsec-setup/files/usr/sbin/secubox-crowdsec-setup @@ -0,0 +1,1052 @@ +#!/bin/sh +# SPDX-License-Identifier: MIT +# SecuBox CrowdSec Setup Script pour OpenWrt 24.10+ +# Copyright (C) 2025 CyberMind.fr - Gandalf +# +# Script d'installation automatisee de CrowdSec avec : +# - syslog-ng4 pour le forwarding des logs +# - crowdsec-firewall-bouncer-nftables pour fw4 +# - Integration complete avec SecuBox +# +# Usage: secubox-crowdsec-setup [--uninstall|--status|--repair] + +set -e + +# ============================================================================= +# CONFIGURATION +# ============================================================================= + +VERSION="1.0.0" +SCRIPT_NAME="secubox-crowdsec-setup" +LOG_TAG="secubox-crowdsec" + +# Repertoires +CROWDSEC_CONFIG_DIR="/etc/crowdsec" +CROWDSEC_DATA_DIR="/srv/crowdsec/data" +CROWDSEC_HUB_DIR="/etc/crowdsec/hub" +SYSLOG_NG_DIR="/etc/syslog-ng" +BACKUP_DIR="/etc/secubox/backups/crowdsec" + +# Ports et adresses +SYSLOG_LISTEN_ADDR="127.0.0.1" +SYSLOG_LISTEN_PORT="5140" +LAPI_LISTEN_ADDR="127.0.0.1" +LAPI_LISTEN_PORT="8080" + +# Prérequis +MIN_FLASH_MB=50 +MIN_RAM_MB=256 +MIN_OPENWRT_MAJOR=24 + +# Collections a installer +COLLECTIONS="crowdsecurity/linux crowdsecurity/sshd crowdsecurity/http-cve crowdsecurity/iptables" + +# Couleurs (si terminal supporte) +if [ -t 1 ]; then + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + BLUE='\033[0;34m' + NC='\033[0m' +else + RED='' + GREEN='' + YELLOW='' + BLUE='' + NC='' +fi + +# ============================================================================= +# FONCTIONS UTILITAIRES +# ============================================================================= + +# Journalisation +log_info() { + printf "${GREEN}[INFO]${NC} %s\n" "$1" + logger -t "$LOG_TAG" -p info "$1" +} + +log_warn() { + printf "${YELLOW}[WARN]${NC} %s\n" "$1" >&2 + logger -t "$LOG_TAG" -p warning "$1" +} + +log_error() { + printf "${RED}[ERREUR]${NC} %s\n" "$1" >&2 + logger -t "$LOG_TAG" -p err "$1" +} + +log_step() { + printf "\n${BLUE}==> %s${NC}\n" "$1" +} + +# Verification de commande disponible +check_cmd() { + command -v "$1" >/dev/null 2>&1 +} + +# Lecture securisee d'une valeur UCI +uci_get() { + uci -q get "$1" 2>/dev/null || echo "" +} + +# Ecriture UCI avec creation de section si necessaire +uci_set() { + uci -q set "$1" 2>/dev/null +} + +# Verification que le script tourne en root +check_root() { + if [ "$(id -u)" != "0" ]; then + log_error "Ce script doit etre execute en tant que root" + exit 1 + fi +} + +# Detection de l'architecture +detect_arch() { + ARCH=$(uname -m) + case "$ARCH" in + aarch64) + ARCH_NAME="arm64" + ;; + armv7l|armv7) + ARCH_NAME="armv7" + ;; + x86_64) + ARCH_NAME="x86_64" + ;; + mips|mipsel) + ARCH_NAME="mips" + ;; + *) + ARCH_NAME="$ARCH" + ;; + esac + log_info "Architecture detectee: $ARCH_NAME ($ARCH)" +} + +# Detection de la version OpenWrt +detect_openwrt_version() { + if [ -f /etc/openwrt_release ]; then + . /etc/openwrt_release + OPENWRT_VERSION="${DISTRIB_RELEASE:-unknown}" + OPENWRT_CODENAME="${DISTRIB_CODENAME:-unknown}" + + # Extraire le numero de version majeur + OPENWRT_MAJOR=$(echo "$OPENWRT_VERSION" | cut -d'.' -f1) + + log_info "Version OpenWrt: $OPENWRT_VERSION ($OPENWRT_CODENAME)" + else + log_error "Fichier /etc/openwrt_release non trouve. Est-ce bien OpenWrt?" + exit 1 + fi +} + +# Verification des prerequis materiels +check_prerequisites() { + log_step "Verification des prerequis" + + local errors=0 + + # Verification version OpenWrt + if [ "$OPENWRT_MAJOR" -lt "$MIN_OPENWRT_MAJOR" ]; then + log_error "OpenWrt $MIN_OPENWRT_MAJOR+ requis (detecte: $OPENWRT_VERSION)" + errors=$((errors + 1)) + else + log_info "Version OpenWrt OK: $OPENWRT_VERSION" + fi + + # Verification espace flash (en Mo) + # Utiliser df sur /overlay qui represente l'espace disponible + if [ -d /overlay ]; then + FLASH_AVAIL=$(df -m /overlay 2>/dev/null | awk 'NR==2 {print $4}') + else + # Fallback sur / + FLASH_AVAIL=$(df -m / 2>/dev/null | awk 'NR==2 {print $4}') + fi + + if [ -n "$FLASH_AVAIL" ] && [ "$FLASH_AVAIL" -lt "$MIN_FLASH_MB" ]; then + log_error "Espace flash insuffisant: ${FLASH_AVAIL}Mo disponible, ${MIN_FLASH_MB}Mo requis" + errors=$((errors + 1)) + else + log_info "Espace flash OK: ${FLASH_AVAIL:-?}Mo disponible" + fi + + # Verification RAM + RAM_TOTAL=$(awk '/MemTotal/ {print int($2/1024)}' /proc/meminfo) + if [ "$RAM_TOTAL" -lt "$MIN_RAM_MB" ]; then + log_error "RAM insuffisante: ${RAM_TOTAL}Mo detecte, ${MIN_RAM_MB}Mo requis" + errors=$((errors + 1)) + else + log_info "RAM OK: ${RAM_TOTAL}Mo" + fi + + # Verification nftables/fw4 + if ! check_cmd nft; then + log_error "nftables non disponible. fw4 requis pour OpenWrt 24.10+" + errors=$((errors + 1)) + else + log_info "nftables OK" + fi + + # Verification connectivite Internet + if ! ping -c 1 -W 3 downloads.openwrt.org >/dev/null 2>&1; then + log_warn "Pas de connectivite Internet detectee" + # Pas fatal, l'utilisateur peut avoir des paquets locaux + else + log_info "Connectivite Internet OK" + fi + + if [ "$errors" -gt 0 ]; then + log_error "$errors erreur(s) de prerequis. Installation annulee." + exit 1 + fi + + log_info "Tous les prerequis sont satisfaits" +} + +# ============================================================================= +# SAUVEGARDE ET RESTAURATION +# ============================================================================= + +backup_configs() { + log_step "Sauvegarde des configurations existantes" + + BACKUP_TIMESTAMP=$(date +%Y%m%d_%H%M%S) + BACKUP_PATH="${BACKUP_DIR}/${BACKUP_TIMESTAMP}" + + mkdir -p "$BACKUP_PATH" + + # Sauvegarder les configs existantes + [ -d "$CROWDSEC_CONFIG_DIR" ] && cp -a "$CROWDSEC_CONFIG_DIR" "$BACKUP_PATH/" 2>/dev/null || true + [ -d "$SYSLOG_NG_DIR" ] && cp -a "$SYSLOG_NG_DIR" "$BACKUP_PATH/" 2>/dev/null || true + [ -f /etc/config/system ] && cp /etc/config/system "$BACKUP_PATH/" 2>/dev/null || true + + # Sauvegarder la liste des paquets installes + opkg list-installed > "$BACKUP_PATH/packages.list" 2>/dev/null || true + + log_info "Sauvegarde creee dans $BACKUP_PATH" + + # Garder les 5 dernieres sauvegardes + ls -dt "${BACKUP_DIR}"/*/ 2>/dev/null | tail -n +6 | xargs rm -rf 2>/dev/null || true +} + +rollback() { + log_error "Erreur detectee, tentative de rollback..." + + # Trouver la derniere sauvegarde + LAST_BACKUP=$(ls -dt "${BACKUP_DIR}"/*/ 2>/dev/null | head -1) + + if [ -z "$LAST_BACKUP" ]; then + log_error "Aucune sauvegarde disponible pour rollback" + return 1 + fi + + log_info "Restauration depuis $LAST_BACKUP" + + # Arreter les services + /etc/init.d/crowdsec stop 2>/dev/null || true + /etc/init.d/crowdsec-firewall-bouncer stop 2>/dev/null || true + /etc/init.d/syslog-ng stop 2>/dev/null || true + + # Restaurer les configs + [ -d "$LAST_BACKUP/crowdsec" ] && cp -a "$LAST_BACKUP/crowdsec"/* "$CROWDSEC_CONFIG_DIR/" 2>/dev/null || true + [ -d "$LAST_BACKUP/syslog-ng" ] && cp -a "$LAST_BACKUP/syslog-ng"/* "$SYSLOG_NG_DIR/" 2>/dev/null || true + [ -f "$LAST_BACKUP/system" ] && cp "$LAST_BACKUP/system" /etc/config/system 2>/dev/null || true + + # Reactiver logd si necessaire + /etc/init.d/log enable 2>/dev/null || true + /etc/init.d/log start 2>/dev/null || true + + log_warn "Rollback effectue. Verifiez l'etat du systeme." +} + +# ============================================================================= +# INSTALLATION DES PAQUETS +# ============================================================================= + +install_packages() { + log_step "Installation des paquets" + + # Mise a jour de la liste des paquets + log_info "Mise a jour de la liste des paquets..." + opkg update || { + log_error "Echec de la mise a jour opkg" + return 1 + } + + # Liste des paquets a installer + # Note: syslog-ng4 pour OpenWrt 24.10+ (pas syslog-ng3) + PACKAGES="crowdsec crowdsec-firewall-bouncer-nftables syslog-ng4" + + # Installation des paquets + for pkg in $PACKAGES; do + log_info "Installation de $pkg..." + if opkg install "$pkg"; then + log_info "$pkg installe avec succes" + else + # Verifier si deja installe + if opkg list-installed | grep -q "^$pkg "; then + log_warn "$pkg deja installe" + else + log_error "Echec installation de $pkg" + return 1 + fi + fi + done + + # Verifier que cscli est disponible + if ! check_cmd cscli; then + log_error "cscli non disponible apres installation" + return 1 + fi + + log_info "Tous les paquets installes avec succes" +} + +# ============================================================================= +# CONFIGURATION SYSLOG-NG4 +# ============================================================================= + +configure_syslog_ng() { + log_step "Configuration de syslog-ng4" + + mkdir -p "$SYSLOG_NG_DIR" + mkdir -p /tmp/log + + # Configuration principale syslog-ng + cat > "${SYSLOG_NG_DIR}/syslog-ng.conf" << 'SYSLOGCONF' +# Configuration syslog-ng4 pour SecuBox/CrowdSec +# Genere automatiquement par secubox-crowdsec-setup +# Ne pas modifier manuellement - utiliser UCI + +@version: 4.2 +@include "scl.conf" + +# Options globales +options { + chain_hostnames(off); + flush_lines(0); + use_dns(no); + use_fqdn(no); + dns_cache(no); + owner("root"); + group("root"); + perm(0640); + stats_freq(0); + bad_hostname("^gconfd$"); + keep_hostname(yes); + create_dirs(yes); +}; + +# Sources +# Source systeme principale (socket Unix) +source s_sys { + unix-dgram("/dev/log"); + internal(); +}; + +# Source kernel +source s_kernel { + file("/proc/kmsg" program_override("kernel")); +}; + +# Destinations +# Destination CrowdSec (UDP local) +destination d_crowdsec { + syslog("127.0.0.1" + transport("udp") + port(5140) + ); +}; + +# Destination fichier debug (optionnel, dans tmpfs) +destination d_messages { + file("/tmp/log/messages" + template("${DATE} ${HOST} ${MSGHDR}${MSG}\n") + create_dirs(yes) + ); +}; + +# Destination kernel log +destination d_kern { + file("/tmp/log/kern.log" + template("${DATE} ${HOST} ${MSGHDR}${MSG}\n") + ); +}; + +# Destination auth log (SSH, etc.) +destination d_auth { + file("/tmp/log/auth.log" + template("${DATE} ${HOST} ${MSGHDR}${MSG}\n") + ); +}; + +# Filtres +filter f_kern { facility(kern); }; +filter f_auth { facility(auth) or facility(authpriv); }; +filter f_messages { level(info..emerg) and not facility(auth, authpriv, kern); }; + +# Log paths +# Tout vers CrowdSec +log { source(s_sys); source(s_kernel); destination(d_crowdsec); }; + +# Fichiers locaux pour debug +log { source(s_sys); filter(f_messages); destination(d_messages); }; +log { source(s_kernel); filter(f_kern); destination(d_kern); }; +log { source(s_sys); filter(f_auth); destination(d_auth); }; +SYSLOGCONF + + log_info "Configuration syslog-ng4 creee" + + # Desactiver logd natif OpenWrt + log_info "Desactivation de logd natif..." + /etc/init.d/log stop 2>/dev/null || true + /etc/init.d/log disable 2>/dev/null || true + + # Configurer UCI pour desactiver le log buffer interne + uci_set system.@system[0].log_size='0' + uci_set system.@system[0].log_buffer_size='0' + uci commit system 2>/dev/null || true + + # Activer et demarrer syslog-ng + /etc/init.d/syslog-ng enable + /etc/init.d/syslog-ng start || { + log_error "Echec demarrage syslog-ng" + return 1 + } + + # Verifier que syslog-ng fonctionne + sleep 2 + if pgrep -f "syslog-ng" >/dev/null; then + log_info "syslog-ng4 demarre avec succes" + else + log_error "syslog-ng4 ne semble pas fonctionner" + return 1 + fi +} + +# ============================================================================= +# CONFIGURATION CROWDSEC +# ============================================================================= + +configure_crowdsec() { + log_step "Configuration de CrowdSec" + + mkdir -p "$CROWDSEC_CONFIG_DIR" + mkdir -p "$CROWDSEC_DATA_DIR" + mkdir -p "$CROWDSEC_HUB_DIR" + mkdir -p "${CROWDSEC_CONFIG_DIR}/acquis.d" + + # Configuration acquisition syslog + cat > "${CROWDSEC_CONFIG_DIR}/acquis.d/syslog.yaml" << ACQUISCONF +# Acquisition syslog pour OpenWrt/SecuBox +# Source: syslog-ng4 via UDP + +source: syslog +listen_addr: ${SYSLOG_LISTEN_ADDR} +listen_port: ${SYSLOG_LISTEN_PORT} +labels: + type: syslog +ACQUISCONF + + log_info "Configuration acquisition syslog creee" + + # Verifier/corriger config.yaml + if [ -f "${CROWDSEC_CONFIG_DIR}/config.yaml" ]; then + # S'assurer que les chemins sont corrects + sed -i "s|data_dir:.*|data_dir: ${CROWDSEC_DATA_DIR}/|" "${CROWDSEC_CONFIG_DIR}/config.yaml" + sed -i "s|db_path:.*|db_path: ${CROWDSEC_DATA_DIR}/crowdsec.db|" "${CROWDSEC_CONFIG_DIR}/config.yaml" + log_info "Configuration CrowdSec mise a jour" + fi + + # Creer la configuration UCI pour CrowdSec + if [ ! -f /etc/config/crowdsec ]; then + cat > /etc/config/crowdsec << 'UCICONF' +config crowdsec 'crowdsec' + option enabled '1' + option data_dir '/srv/crowdsec/data' + option db_path '/srv/crowdsec/data/crowdsec.db' + +config acquisition 'acquisition' + option syslog_enabled '1' + option firewall_enabled '1' + option ssh_enabled '1' + option http_enabled '0' + +config hub 'hub' + option auto_install '1' + option collections 'crowdsecurity/linux crowdsecurity/sshd crowdsecurity/iptables' + option update_interval '7' + +config bouncer 'bouncer' + option enabled '1' + option ipv4 '1' + option ipv6 '1' + option deny_action 'drop' + option deny_log '1' + option update_frequency '10s' + option filter_input '1' + option filter_forward '1' +UCICONF + log_info "Configuration UCI CrowdSec creee" + fi +} + +# ============================================================================= +# ENREGISTREMENT CAPI ET INSTALLATION COLLECTIONS +# ============================================================================= + +register_and_setup_hub() { + log_step "Enregistrement CAPI et installation des collections" + + # Demarrer CrowdSec temporairement pour les commandes cscli + /etc/init.d/crowdsec start 2>/dev/null || true + sleep 3 + + # Enregistrement CAPI (Central API) + log_info "Enregistrement aupres de la CAPI CrowdSec..." + if cscli capi register 2>/dev/null; then + log_info "Enregistrement CAPI reussi" + else + log_warn "Echec enregistrement CAPI (peut-etre deja enregistre)" + fi + + # Mise a jour du hub + log_info "Mise a jour du hub CrowdSec..." + cscli hub update || log_warn "Echec mise a jour hub" + + # Installation des collections + log_info "Installation des collections..." + for collection in $COLLECTIONS; do + log_info "Installation de $collection..." + if cscli collections install "$collection" 2>/dev/null; then + log_info "Collection $collection installee" + else + log_warn "Echec installation de $collection (peut-etre deja installee)" + fi + done + + # Lister les collections installees + log_info "Collections installees:" + cscli collections list 2>/dev/null || true +} + +# ============================================================================= +# CONFIGURATION FIREWALL BOUNCER +# ============================================================================= + +configure_bouncer() { + log_step "Configuration du firewall bouncer" + + # Generer la cle API pour le bouncer + log_info "Generation de la cle API bouncer..." + + # Supprimer l'ancien bouncer s'il existe + cscli bouncers delete crowdsec-firewall-bouncer 2>/dev/null || true + + # Creer le nouveau bouncer et recuperer la cle + API_KEY=$(cscli bouncers add crowdsec-firewall-bouncer -o raw 2>/dev/null) + + if [ -z "$API_KEY" ] || [ "${#API_KEY}" -lt 10 ]; then + log_error "Echec generation de la cle API bouncer" + return 1 + fi + + log_info "Cle API bouncer generee" + + # Configuration du bouncer nftables + mkdir -p /etc/crowdsec/bouncers + + cat > /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml << BOUNCERCONF +# Configuration Firewall Bouncer pour OpenWrt/SecuBox +# Genere automatiquement par secubox-crowdsec-setup + +mode: nftables +pid_dir: /var/run/ +update_frequency: 10s +daemonize: true +log_mode: file +log_dir: /var/log/ +log_level: info +log_compression: true +log_max_size: 10 +log_max_backups: 3 +log_max_age: 30 + +api_url: http://${LAPI_LISTEN_ADDR}:${LAPI_LISTEN_PORT}/ +api_key: ${API_KEY} + +disable_ipv6: false + +# Configuration nftables optimisee pour fw4 +nftables: + ipv4: + enabled: true + set-only: true + table: crowdsec + chain: crowdsec-chain + priority: -10 + ipv6: + enabled: true + set-only: true + table: crowdsec6 + chain: crowdsec6-chain + priority: -10 + +deny_action: drop +deny_log: true +deny_log_prefix: "crowdsec: " + +# Timers +blacklists_ipv4: crowdsec-blacklists +blacklists_ipv6: crowdsec6-blacklists +BOUNCERCONF + + log_info "Configuration bouncer creee" + + # Sauvegarder la cle API dans UCI + uci_set crowdsec.bouncer.api_key="$API_KEY" + uci commit crowdsec 2>/dev/null || true +} + +# ============================================================================= +# CONFIGURATION WHITELIST +# ============================================================================= + +configure_whitelist() { + log_step "Configuration des whitelists" + + # Creer un fichier de whitelist pour les reseaux locaux + cat > "${CROWDSEC_CONFIG_DIR}/parsers/s02-enrich/secubox-whitelist.yaml" << 'WHITELIST' +# Whitelist SecuBox - Reseaux locaux +# Ne pas bannir les adresses RFC1918 et link-local + +name: secubox/whitelists +description: "Whitelist des reseaux locaux SecuBox" +whitelist: + reason: "Reseau local SecuBox" + ip: + - "127.0.0.0/8" + - "10.0.0.0/8" + - "172.16.0.0/12" + - "192.168.0.0/16" + - "169.254.0.0/16" + - "::1/128" + - "fe80::/10" + - "fc00::/7" +WHITELIST + + log_info "Whitelist reseaux locaux creee" +} + +# ============================================================================= +# DEMARRAGE DES SERVICES +# ============================================================================= + +start_services() { + log_step "Demarrage des services" + + # Ordre de demarrage: syslog-ng -> crowdsec -> bouncer + + # Redemarrer syslog-ng pour s'assurer qu'il fonctionne + log_info "Redemarrage syslog-ng..." + /etc/init.d/syslog-ng restart || log_warn "Probleme redemarrage syslog-ng" + sleep 2 + + # Demarrer CrowdSec + log_info "Demarrage CrowdSec..." + /etc/init.d/crowdsec enable + /etc/init.d/crowdsec restart || { + log_error "Echec demarrage CrowdSec" + return 1 + } + sleep 3 + + # Verifier que LAPI est disponible + local retries=10 + while [ $retries -gt 0 ]; do + if cscli lapi status >/dev/null 2>&1; then + log_info "LAPI CrowdSec disponible" + break + fi + retries=$((retries - 1)) + sleep 1 + done + + if [ $retries -eq 0 ]; then + log_error "LAPI CrowdSec non disponible apres 10 secondes" + return 1 + fi + + # Demarrer le bouncer + log_info "Demarrage du firewall bouncer..." + /etc/init.d/crowdsec-firewall-bouncer enable + /etc/init.d/crowdsec-firewall-bouncer restart || { + log_error "Echec demarrage bouncer" + return 1 + } + sleep 2 + + # Verifier que les services tournent + log_info "Verification des services..." + + local all_ok=1 + + if pgrep -f "syslog-ng" >/dev/null; then + log_info " syslog-ng: OK" + else + log_error " syslog-ng: ECHEC" + all_ok=0 + fi + + if pgrep -x crowdsec >/dev/null; then + log_info " crowdsec: OK" + else + log_error " crowdsec: ECHEC" + all_ok=0 + fi + + if pgrep -f "crowdsec-firewall-bouncer" >/dev/null; then + log_info " bouncer: OK" + else + log_error " bouncer: ECHEC" + all_ok=0 + fi + + # Verifier les tables nftables + if nft list tables 2>/dev/null | grep -q "crowdsec"; then + log_info " nftables crowdsec: OK" + else + log_warn " nftables crowdsec: Tables non creees (normal au premier demarrage)" + fi + + if [ "$all_ok" -eq 0 ]; then + return 1 + fi +} + +# ============================================================================= +# VERIFICATION ET RAPPORT +# ============================================================================= + +verify_installation() { + log_step "Verification de l'installation" + + local errors=0 + + # Test connectivite CAPI + log_info "Test connectivite CAPI..." + if cscli capi status 2>/dev/null | grep -qi "online\|connected"; then + log_info " CAPI: Connecte" + else + log_warn " CAPI: Deconnecte (verifier la connectivite Internet)" + fi + + # Afficher les metriques + log_info "Metriques CrowdSec:" + cscli metrics 2>/dev/null | head -30 || log_warn " Impossible de recuperer les metriques" + + # Lister les bouncers + log_info "Bouncers enregistres:" + cscli bouncers list 2>/dev/null || log_warn " Impossible de lister les bouncers" + + # Verifier l'acquisition + log_info "Sources d'acquisition:" + cscli metrics show acquisition 2>/dev/null | head -10 || true +} + +generate_report() { + log_step "Rapport d'installation" + + REPORT_FILE="/tmp/secubox-crowdsec-install-report.txt" + + cat > "$REPORT_FILE" << REPORT +================================================================================ + RAPPORT D'INSTALLATION SECUBOX CROWDSEC +================================================================================ +Date: $(date) +Version script: $VERSION +Systeme: OpenWrt $OPENWRT_VERSION ($OPENWRT_CODENAME) +Architecture: $ARCH_NAME + +SERVICES INSTALLES: +------------------- +$(opkg list-installed | grep -E "crowdsec|syslog-ng" || echo "Erreur listing paquets") + +STATUT SERVICES: +---------------- +syslog-ng: $(pgrep -f syslog-ng >/dev/null && echo "ACTIF" || echo "INACTIF") +crowdsec: $(pgrep -x crowdsec >/dev/null && echo "ACTIF" || echo "INACTIF") +bouncer: $(pgrep -f crowdsec-firewall-bouncer >/dev/null && echo "ACTIF" || echo "INACTIF") + +COLLECTIONS INSTALLEES: +----------------------- +$(cscli collections list 2>/dev/null || echo "Erreur listing collections") + +BOUNCERS: +--------- +$(cscli bouncers list 2>/dev/null || echo "Erreur listing bouncers") + +TABLES NFTABLES: +---------------- +$(nft list tables 2>/dev/null | grep crowdsec || echo "Aucune table crowdsec") + +CONFIGURATION: +-------------- +Syslog listen: ${SYSLOG_LISTEN_ADDR}:${SYSLOG_LISTEN_PORT} +LAPI listen: ${LAPI_LISTEN_ADDR}:${LAPI_LISTEN_PORT} +Data dir: ${CROWDSEC_DATA_DIR} +Config dir: ${CROWDSEC_CONFIG_DIR} + +COMMANDES UTILES: +----------------- +# Voir les decisions actives +cscli decisions list + +# Voir les alertes +cscli alerts list + +# Bannir une IP manuellement +cscli decisions add --ip --duration 24h --reason "Ban manuel" + +# Debannir une IP +cscli decisions delete --ip + +# Mettre a jour le hub +cscli hub update && cscli hub upgrade + +# Voir les metriques +cscli metrics + +# Logs CrowdSec +logread | grep crowdsec +cat /var/log/crowdsec.log + +================================================================================ +REPORT + + log_info "Rapport genere: $REPORT_FILE" + cat "$REPORT_FILE" +} + +# ============================================================================= +# DESINSTALLATION +# ============================================================================= + +uninstall() { + log_step "Desinstallation de CrowdSec SecuBox" + + # Confirmation + printf "ATTENTION: Ceci va supprimer CrowdSec et ses configurations.\n" + printf "Continuer? [y/N] " + read -r confirm + case "$confirm" in + [yY][eE][sS]|[yY]) + ;; + *) + log_info "Desinstallation annulee" + exit 0 + ;; + esac + + # Arreter les services + log_info "Arret des services..." + /etc/init.d/crowdsec-firewall-bouncer stop 2>/dev/null || true + /etc/init.d/crowdsec-firewall-bouncer disable 2>/dev/null || true + /etc/init.d/crowdsec stop 2>/dev/null || true + /etc/init.d/crowdsec disable 2>/dev/null || true + /etc/init.d/syslog-ng stop 2>/dev/null || true + /etc/init.d/syslog-ng disable 2>/dev/null || true + + # Supprimer les tables nftables + log_info "Suppression des tables nftables..." + nft delete table ip crowdsec 2>/dev/null || true + nft delete table ip6 crowdsec6 2>/dev/null || true + + # Desinstaller les paquets + log_info "Desinstallation des paquets..." + opkg remove crowdsec-firewall-bouncer-nftables 2>/dev/null || true + opkg remove crowdsec 2>/dev/null || true + opkg remove syslog-ng4 2>/dev/null || true + + # Reactiver logd + log_info "Reactivation de logd..." + /etc/init.d/log enable 2>/dev/null || true + /etc/init.d/log start 2>/dev/null || true + + # Optionnel: supprimer les configurations + printf "Supprimer les fichiers de configuration? [y/N] " + read -r confirm_conf + case "$confirm_conf" in + [yY][eE][sS]|[yY]) + rm -rf "$CROWDSEC_CONFIG_DIR" 2>/dev/null || true + rm -rf "$CROWDSEC_DATA_DIR" 2>/dev/null || true + rm -rf "$SYSLOG_NG_DIR" 2>/dev/null || true + rm -f /etc/config/crowdsec 2>/dev/null || true + log_info "Configurations supprimees" + ;; + *) + log_info "Configurations conservees" + ;; + esac + + log_info "Desinstallation terminee" +} + +# ============================================================================= +# STATUT +# ============================================================================= + +show_status() { + log_step "Statut CrowdSec SecuBox" + + printf "\n${BLUE}Services:${NC}\n" + printf " syslog-ng: " + if pgrep -f "syslog-ng" >/dev/null; then + printf "${GREEN}ACTIF${NC}\n" + else + printf "${RED}INACTIF${NC}\n" + fi + + printf " crowdsec: " + if pgrep -x crowdsec >/dev/null; then + printf "${GREEN}ACTIF${NC}\n" + else + printf "${RED}INACTIF${NC}\n" + fi + + printf " bouncer: " + if pgrep -f "crowdsec-firewall-bouncer" >/dev/null; then + printf "${GREEN}ACTIF${NC}\n" + else + printf "${RED}INACTIF${NC}\n" + fi + + printf "\n${BLUE}LAPI Status:${NC}\n" + cscli lapi status 2>/dev/null || printf " ${RED}Non disponible${NC}\n" + + printf "\n${BLUE}Decisions actives:${NC}\n" + cscli decisions list 2>/dev/null | head -10 || printf " Aucune ou erreur\n" + + printf "\n${BLUE}Tables nftables:${NC}\n" + nft list tables 2>/dev/null | grep crowdsec || printf " ${YELLOW}Aucune table crowdsec${NC}\n" +} + +# ============================================================================= +# REPARATION +# ============================================================================= + +repair() { + log_step "Reparation de l'installation CrowdSec" + + # Arreter les services + /etc/init.d/crowdsec-firewall-bouncer stop 2>/dev/null || true + /etc/init.d/crowdsec stop 2>/dev/null || true + + # Verifier/recreer les repertoires + mkdir -p "$CROWDSEC_DATA_DIR" + mkdir -p "$CROWDSEC_CONFIG_DIR" + + # Regenerer les credentials machine si necessaire + if [ ! -f "${CROWDSEC_CONFIG_DIR}/local_api_credentials.yaml" ]; then + log_info "Regeneration des credentials machine..." + cscli machines add localhost --auto --force 2>/dev/null || true + fi + + # Redemarrer CrowdSec + /etc/init.d/crowdsec start + sleep 3 + + # Verifier LAPI + if ! cscli lapi status >/dev/null 2>&1; then + log_error "LAPI toujours non disponible apres reparation" + return 1 + fi + + # Re-enregistrer le bouncer si necessaire + if ! cscli bouncers list 2>/dev/null | grep -q "crowdsec-firewall-bouncer"; then + log_info "Re-enregistrement du bouncer..." + configure_bouncer + fi + + # Redemarrer le bouncer + /etc/init.d/crowdsec-firewall-bouncer start + + log_info "Reparation terminee" + show_status +} + +# ============================================================================= +# MAIN +# ============================================================================= + +usage() { + cat << EOF +Usage: $SCRIPT_NAME [OPTIONS] + +Options: + --install Installation complete (defaut) + --uninstall Desinstallation complete + --status Afficher le statut des services + --repair Tenter de reparer une installation cassee + --help Afficher cette aide + +Version: $VERSION + +EOF +} + +main() { + check_root + detect_arch + detect_openwrt_version + + case "${1:-install}" in + --install|install) + log_step "Installation SecuBox CrowdSec v$VERSION" + + check_prerequisites + backup_configs + + # Piege pour rollback en cas d'erreur + trap 'rollback; exit 1' ERR + + install_packages + configure_syslog_ng + configure_crowdsec + register_and_setup_hub + configure_bouncer + configure_whitelist + start_services + verify_installation + generate_report + + # Desactiver le piege + trap - ERR + + log_step "Installation terminee avec succes!" + log_info "Consultez le rapport: /tmp/secubox-crowdsec-install-report.txt" + ;; + --uninstall|uninstall) + uninstall + ;; + --status|status) + show_status + ;; + --repair|repair) + repair + ;; + --help|-h|help) + usage + ;; + *) + log_error "Option inconnue: $1" + usage + exit 1 + ;; + esac +} + +main "$@"