feat: Add complete CrowdSec integration for OpenWrt 24.10+
New packages: - secubox-crowdsec-setup: Automated installation script with: - Prerequisites verification (RAM, flash, OpenWrt version) - syslog-ng4 configuration for log forwarding - CAPI registration and hub setup - nftables firewall bouncer configuration - Backup/rollback, repair, and uninstall modes - luci-app-secubox-crowdsec: LuCI dashboard with: - Service status and statistics dashboard - Active decisions (bans) management - Security alerts viewer - Collections and bouncers management - UCI-based settings configuration Enhanced existing packages: - luci-app-crowdsec-dashboard: Added acquisition configuration wizard - secubox-app-crowdsec: Improved defaults and configuration Documentation: - CROWDSEC-OPENWRT-24.md with architecture, installation, and troubleshooting Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2950bc9b2f
commit
252341e045
380
package/secubox/CROWDSEC-OPENWRT-24.md
Normal file
380
package/secubox/CROWDSEC-OPENWRT-24.md
Normal file
@ -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 <IP> --duration 24h --reason "Manual ban"
|
||||||
|
cscli decisions delete --ip <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
|
||||||
@ -230,6 +230,20 @@ var callServiceControl = rpc.declare({
|
|||||||
expect: { }
|
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) {
|
function formatDuration(seconds) {
|
||||||
if (!seconds) return 'N/A';
|
if (!seconds) return 'N/A';
|
||||||
if (seconds < 60) return seconds + 's';
|
if (seconds < 60) return seconds + 's';
|
||||||
@ -360,6 +374,10 @@ return baseclass.extend({
|
|||||||
// Service Control
|
// Service Control
|
||||||
serviceControl: callServiceControl,
|
serviceControl: callServiceControl,
|
||||||
|
|
||||||
|
// Acquisition Methods
|
||||||
|
configureAcquisition: callConfigureAcquisition,
|
||||||
|
getAcquisitionConfig: callAcquisitionConfig,
|
||||||
|
|
||||||
formatDuration: formatDuration,
|
formatDuration: formatDuration,
|
||||||
formatDate: formatDate,
|
formatDate: formatDate,
|
||||||
formatRelativeTime: formatRelativeTime,
|
formatRelativeTime: formatRelativeTime,
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
return view.extend({
|
return view.extend({
|
||||||
wizardData: {
|
wizardData: {
|
||||||
currentStep: 1,
|
currentStep: 1,
|
||||||
totalSteps: 7,
|
totalSteps: 8,
|
||||||
|
|
||||||
// Step 1 data
|
// Step 1 data
|
||||||
crowdsecRunning: false,
|
crowdsecRunning: false,
|
||||||
@ -39,20 +39,29 @@ return view.extend({
|
|||||||
hubUpdating: false,
|
hubUpdating: false,
|
||||||
hubUpdated: 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: [],
|
collections: [],
|
||||||
installing: false,
|
installing: false,
|
||||||
installed: false,
|
installed: false,
|
||||||
installStatus: '',
|
installStatus: '',
|
||||||
installedCount: 0,
|
installedCount: 0,
|
||||||
|
|
||||||
// Step 5 data (Bouncer)
|
// Step 6 data (Bouncer)
|
||||||
configuring: false,
|
configuring: false,
|
||||||
bouncerConfigured: false,
|
bouncerConfigured: false,
|
||||||
apiKey: '',
|
apiKey: '',
|
||||||
resetting: false,
|
resetting: false,
|
||||||
|
|
||||||
// Step 6 data (Services)
|
// Step 7 data (Services)
|
||||||
starting: false,
|
starting: false,
|
||||||
enabling: false,
|
enabling: false,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
@ -60,7 +69,7 @@ return view.extend({
|
|||||||
nftablesActive: false,
|
nftablesActive: false,
|
||||||
lapiConnected: false,
|
lapiConnected: false,
|
||||||
|
|
||||||
// Step 7 data (Complete)
|
// Step 8 data (Complete)
|
||||||
blockedIPs: 0,
|
blockedIPs: 0,
|
||||||
activeDecisions: 0
|
activeDecisions: 0
|
||||||
},
|
},
|
||||||
@ -171,10 +180,11 @@ return view.extend({
|
|||||||
{ number: 1, title: _('Welcome') },
|
{ number: 1, title: _('Welcome') },
|
||||||
{ number: 2, title: _('Console') },
|
{ number: 2, title: _('Console') },
|
||||||
{ number: 3, title: _('Update Hub') },
|
{ number: 3, title: _('Update Hub') },
|
||||||
{ number: 4, title: _('Install Packs') },
|
{ number: 4, title: _('Log Sources') },
|
||||||
{ number: 5, title: _('Configure Bouncer') },
|
{ number: 5, title: _('Install Packs') },
|
||||||
{ number: 6, title: _('Enable Services') },
|
{ number: 6, title: _('Configure Bouncer') },
|
||||||
{ number: 7, title: _('Complete') }
|
{ number: 7, title: _('Enable Services') },
|
||||||
|
{ number: 8, title: _('Complete') }
|
||||||
];
|
];
|
||||||
|
|
||||||
var stepper = E('div', { 'class': 'wizard-stepper' });
|
var stepper = E('div', { 'class': 'wizard-stepper' });
|
||||||
@ -209,13 +219,15 @@ return view.extend({
|
|||||||
case 3:
|
case 3:
|
||||||
return this.renderStep3Hub(data);
|
return this.renderStep3Hub(data);
|
||||||
case 4:
|
case 4:
|
||||||
return this.renderStep4Collections(data);
|
return this.renderStep4Acquisition(data);
|
||||||
case 5:
|
case 5:
|
||||||
return this.renderStep5Bouncer(data);
|
return this.renderStep5Collections(data);
|
||||||
case 6:
|
case 6:
|
||||||
return this.renderStep6Services(data);
|
return this.renderStep6Bouncer(data);
|
||||||
case 7:
|
case 7:
|
||||||
return this.renderStep7Complete(data);
|
return this.renderStep7Services(data);
|
||||||
|
case 8:
|
||||||
|
return this.renderStep8Complete(data);
|
||||||
default:
|
default:
|
||||||
return E('div', {}, _('Invalid step'));
|
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 = [
|
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/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 }
|
{ 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('div', { 'class': 'wizard-nav' }, [
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'cbi-button',
|
'class': 'cbi-button',
|
||||||
'click': L.bind(this.goToStep, this, 3),
|
'click': L.bind(this.goToStep, this, 4),
|
||||||
'disabled': this.wizardData.installing ? true : null
|
'disabled': this.wizardData.installing ? true : null
|
||||||
}, _('Back')),
|
}, _('Back')),
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'cbi-button',
|
'class': 'cbi-button',
|
||||||
'click': L.bind(this.goToStep, this, 5),
|
'click': L.bind(this.goToStep, this, 6),
|
||||||
'disabled': this.wizardData.installing ? true : null
|
'disabled': this.wizardData.installing ? true : null
|
||||||
}, _('Skip')),
|
}, _('Skip')),
|
||||||
E('button', {
|
E('button', {
|
||||||
@ -517,7 +705,7 @@ return view.extend({
|
|||||||
]);
|
]);
|
||||||
},
|
},
|
||||||
|
|
||||||
renderStep5Bouncer: function(data) {
|
renderStep6Bouncer: function(data) {
|
||||||
var self = this;
|
var self = this;
|
||||||
return E('div', { 'class': 'wizard-step' }, [
|
return E('div', { 'class': 'wizard-step' }, [
|
||||||
E('h2', {}, _('Configure Firewall Bouncer')),
|
E('h2', {}, _('Configure Firewall Bouncer')),
|
||||||
@ -628,13 +816,13 @@ return view.extend({
|
|||||||
E('div', { 'class': 'wizard-nav' }, [
|
E('div', { 'class': 'wizard-nav' }, [
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'cbi-button',
|
'class': 'cbi-button',
|
||||||
'click': L.bind(this.goToStep, this, 4),
|
'click': L.bind(this.goToStep, this, 5),
|
||||||
'disabled': this.wizardData.configuring ? true : null
|
'disabled': this.wizardData.configuring ? true : null
|
||||||
}, _('Back')),
|
}, _('Back')),
|
||||||
this.wizardData.bouncerConfigured ?
|
this.wizardData.bouncerConfigured ?
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'cbi-button cbi-button-positive',
|
'class': 'cbi-button cbi-button-positive',
|
||||||
'click': L.bind(this.goToStep, this, 6)
|
'click': L.bind(this.goToStep, this, 7)
|
||||||
}, _('Next')) :
|
}, _('Next')) :
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'cbi-button cbi-button-action',
|
'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' }, [
|
return E('div', { 'class': 'wizard-step' }, [
|
||||||
E('h2', {}, _('Enable & Start Services')),
|
E('h2', {}, _('Enable & Start Services')),
|
||||||
E('p', {}, _('Starting the firewall bouncer service and verifying operation...')),
|
E('p', {}, _('Starting the firewall bouncer service and verifying operation...')),
|
||||||
@ -678,13 +866,13 @@ return view.extend({
|
|||||||
E('div', { 'class': 'wizard-nav' }, [
|
E('div', { 'class': 'wizard-nav' }, [
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'cbi-button',
|
'class': 'cbi-button',
|
||||||
'click': L.bind(this.goToStep, this, 5),
|
'click': L.bind(this.goToStep, this, 6),
|
||||||
'disabled': this.wizardData.starting ? true : null
|
'disabled': this.wizardData.starting ? true : null
|
||||||
}, _('Back')),
|
}, _('Back')),
|
||||||
(this.wizardData.enabled && this.wizardData.running && this.wizardData.nftablesActive && this.wizardData.lapiConnected) ?
|
(this.wizardData.enabled && this.wizardData.running && this.wizardData.nftablesActive && this.wizardData.lapiConnected) ?
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'cbi-button cbi-button-positive',
|
'class': 'cbi-button cbi-button-positive',
|
||||||
'click': L.bind(this.goToStep, this, 7)
|
'click': L.bind(this.goToStep, this, 8)
|
||||||
}, _('Next')) :
|
}, _('Next')) :
|
||||||
E('button', {
|
E('button', {
|
||||||
'class': 'cbi-button cbi-button-action',
|
'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' }, [
|
return E('div', { 'class': 'wizard-step wizard-complete' }, [
|
||||||
E('div', { 'class': 'success-hero' }, [
|
E('div', { 'class': 'success-hero' }, [
|
||||||
E('div', { 'class': 'success-icon' }, '🎉'),
|
E('div', { 'class': 'success-icon' }, '🎉'),
|
||||||
@ -888,6 +1076,52 @@ return view.extend({
|
|||||||
}, this));
|
}, 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() {
|
handleInstallCollections: function() {
|
||||||
// Read from data-checked attributes (Unicode checkbox approach)
|
// Read from data-checked attributes (Unicode checkbox approach)
|
||||||
var items = document.querySelectorAll('.collection-item[data-collection]');
|
var items = document.querySelectorAll('.collection-item[data-collection]');
|
||||||
@ -898,7 +1132,7 @@ return view.extend({
|
|||||||
console.log('[Wizard] Selected collections:', selected);
|
console.log('[Wizard] Selected collections:', selected);
|
||||||
|
|
||||||
if (selected.length === 0) {
|
if (selected.length === 0) {
|
||||||
this.goToStep(5);
|
this.goToStep(6);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -922,8 +1156,8 @@ return view.extend({
|
|||||||
ui.addNotification(null, E('p', _('Installed %d collections').format(selected.length)), 'info');
|
ui.addNotification(null, E('p', _('Installed %d collections').format(selected.length)), 'info');
|
||||||
this.refreshView();
|
this.refreshView();
|
||||||
|
|
||||||
// Auto-advance after 2 seconds
|
// Auto-advance to Step 6 (Configure Bouncer) after 2 seconds
|
||||||
setTimeout(L.bind(function() { this.goToStep(5); }, this), 2000);
|
setTimeout(L.bind(function() { this.goToStep(6); }, this), 2000);
|
||||||
}, this)).catch(L.bind(function(err) {
|
}, this)).catch(L.bind(function(err) {
|
||||||
this.wizardData.installing = false;
|
this.wizardData.installing = false;
|
||||||
ui.addNotification(null, E('p', _('Installation failed: %s').format(err.message)), 'error');
|
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');
|
ui.addNotification(null, E('p', _('Bouncer configured successfully')), 'info');
|
||||||
this.refreshView();
|
this.refreshView();
|
||||||
|
|
||||||
// Auto-advance after 2 seconds
|
// Auto-advance to Step 7 (Enable Services) after 2 seconds
|
||||||
console.log('[Wizard] Auto-advancing to Step 6 in 2 seconds...');
|
console.log('[Wizard] Auto-advancing to Step 7 in 2 seconds...');
|
||||||
setTimeout(L.bind(function() { this.goToStep(6); }, this), 2000);
|
setTimeout(L.bind(function() { this.goToStep(7); }, this), 2000);
|
||||||
}, this)).catch(L.bind(function(err) {
|
}, this)).catch(L.bind(function(err) {
|
||||||
console.error('[Wizard] Configuration error:', err);
|
console.error('[Wizard] Configuration error:', err);
|
||||||
this.wizardData.configuring = false;
|
this.wizardData.configuring = false;
|
||||||
@ -1039,10 +1273,10 @@ return view.extend({
|
|||||||
// LAPI connection may take a few seconds to establish, so it's optional
|
// LAPI connection may take a few seconds to establish, so it's optional
|
||||||
if (this.wizardData.enabled && this.wizardData.running &&
|
if (this.wizardData.enabled && this.wizardData.running &&
|
||||||
this.wizardData.nftablesActive) {
|
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');
|
ui.addNotification(null, E('p', _('Services started successfully!')), 'info');
|
||||||
// Auto-advance after 2 seconds
|
// Auto-advance to Step 8 (Complete) after 2 seconds
|
||||||
setTimeout(L.bind(function() { this.goToStep(7); }, this), 2000);
|
setTimeout(L.bind(function() { this.goToStep(8); }, this), 2000);
|
||||||
} else {
|
} else {
|
||||||
console.log('[Wizard] Service startup incomplete');
|
console.log('[Wizard] Service startup incomplete');
|
||||||
ui.addNotification(null, E('p', _('Service startup incomplete. Check status and retry.')), 'warning');
|
ui.addNotification(null, E('p', _('Service startup incomplete. Check status and retry.')), 'warning');
|
||||||
|
|||||||
@ -14,6 +14,12 @@ secubox_log() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CSCLI="/usr/bin/cscli"
|
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 if cscli exists and crowdsec is running
|
||||||
check_cscli() {
|
check_cscli() {
|
||||||
@ -33,7 +39,7 @@ check_cscli() {
|
|||||||
get_decisions() {
|
get_decisions() {
|
||||||
check_cscli
|
check_cscli
|
||||||
local output
|
local output
|
||||||
output=$($CSCLI decisions list -o json 2>/dev/null)
|
output=$(run_cscli decisions list -o json)
|
||||||
if [ -z "$output" ] || [ "$output" = "null" ]; then
|
if [ -z "$output" ] || [ "$output" = "null" ]; then
|
||||||
echo '{"alerts":[]}'
|
echo '{"alerts":[]}'
|
||||||
else
|
else
|
||||||
@ -46,7 +52,7 @@ get_alerts() {
|
|||||||
local limit="${1:-50}"
|
local limit="${1:-50}"
|
||||||
check_cscli
|
check_cscli
|
||||||
local output
|
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
|
if [ -z "$output" ] || [ "$output" = "null" ]; then
|
||||||
echo '{"alerts":[]}'
|
echo '{"alerts":[]}'
|
||||||
else
|
else
|
||||||
@ -58,7 +64,7 @@ get_alerts() {
|
|||||||
get_metrics() {
|
get_metrics() {
|
||||||
check_cscli
|
check_cscli
|
||||||
local output
|
local output
|
||||||
output=$($CSCLI metrics -o json 2>/dev/null)
|
output=$(run_cscli metrics -o json 2>/dev/null)
|
||||||
if [ -z "$output" ]; then
|
if [ -z "$output" ]; then
|
||||||
echo '{}'
|
echo '{}'
|
||||||
else
|
else
|
||||||
@ -72,7 +78,7 @@ get_metrics() {
|
|||||||
get_bouncers() {
|
get_bouncers() {
|
||||||
check_cscli
|
check_cscli
|
||||||
local output
|
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
|
if [ -z "$output" ] || [ "$output" = "null" ]; then
|
||||||
echo '{"bouncers":[]}'
|
echo '{"bouncers":[]}'
|
||||||
else
|
else
|
||||||
@ -84,7 +90,7 @@ get_bouncers() {
|
|||||||
get_machines() {
|
get_machines() {
|
||||||
check_cscli
|
check_cscli
|
||||||
local output
|
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
|
if [ -z "$output" ] || [ "$output" = "null" ]; then
|
||||||
echo '{"machines":[]}'
|
echo '{"machines":[]}'
|
||||||
else
|
else
|
||||||
@ -96,7 +102,7 @@ get_machines() {
|
|||||||
get_hub() {
|
get_hub() {
|
||||||
check_cscli
|
check_cscli
|
||||||
local output
|
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
|
if [ -z "$output" ]; then
|
||||||
echo '{}'
|
echo '{}'
|
||||||
else
|
else
|
||||||
@ -124,7 +130,7 @@ get_status() {
|
|||||||
|
|
||||||
# Version
|
# Version
|
||||||
local 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}"
|
json_add_string "version" "${version:-unknown}"
|
||||||
|
|
||||||
# Uptime
|
# Uptime
|
||||||
@ -135,7 +141,7 @@ get_status() {
|
|||||||
# LAPI status (check if Local API is accessible)
|
# LAPI status (check if Local API is accessible)
|
||||||
local lapi_status="unavailable"
|
local lapi_status="unavailable"
|
||||||
if [ -x "$CSCLI" ]; then
|
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"
|
lapi_status="available"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@ -158,7 +164,7 @@ add_ban() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local result
|
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
|
if [ $? -eq 0 ]; then
|
||||||
secubox_log "CrowdSec ban added for $ip ($duration)"
|
secubox_log "CrowdSec ban added for $ip ($duration)"
|
||||||
@ -183,7 +189,7 @@ remove_ban() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local result
|
local result
|
||||||
result=$($CSCLI decisions delete --ip "$ip" 2>&1)
|
result=$(run_cscli decisions delete --ip "$ip" 2>&1)
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
if [ $? -eq 0 ]; then
|
||||||
secubox_log "CrowdSec ban removed for $ip"
|
secubox_log "CrowdSec ban removed for $ip"
|
||||||
@ -204,22 +210,22 @@ get_dashboard_stats() {
|
|||||||
|
|
||||||
# Count decisions
|
# Count decisions
|
||||||
local decisions_count
|
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}"
|
json_add_int "total_decisions" "${decisions_count:-0}"
|
||||||
|
|
||||||
# Count alerts (last 24h)
|
# Count alerts (last 24h)
|
||||||
local alerts_count
|
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}"
|
json_add_int "alerts_24h" "${alerts_count:-0}"
|
||||||
|
|
||||||
# Count bouncers
|
# Count bouncers
|
||||||
local bouncers_count
|
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}"
|
json_add_int "bouncers" "${bouncers_count:-0}"
|
||||||
|
|
||||||
# Top scenarios (from alerts)
|
# Top scenarios (from alerts)
|
||||||
local scenarios
|
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 | \
|
jsonfilter -e '@[*].scenario' 2>/dev/null | \
|
||||||
sort | uniq -c | sort -rn | head -5 | \
|
sort | uniq -c | sort -rn | head -5 | \
|
||||||
awk '{print "{\"scenario\":\"" $2 "\",\"count\":" $1 "}"}' | \
|
awk '{print "{\"scenario\":\"" $2 "\",\"count\":" $1 "}"}' | \
|
||||||
@ -229,7 +235,7 @@ get_dashboard_stats() {
|
|||||||
|
|
||||||
# Top countries (from decisions)
|
# Top countries (from decisions)
|
||||||
local countries
|
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 | \
|
jsonfilter -e '@[*].country' 2>/dev/null | \
|
||||||
sort | uniq -c | sort -rn | head -10 | \
|
sort | uniq -c | sort -rn | head -10 | \
|
||||||
awk '{print "{\"country\":\"" $2 "\",\"count\":" $1 "}"}' | \
|
awk '{print "{\"country\":\"" $2 "\",\"count\":" $1 "}"}' | \
|
||||||
@ -271,9 +277,9 @@ get_waf_status() {
|
|||||||
json_init
|
json_init
|
||||||
|
|
||||||
# Check if appsec is available (cscli appsec command)
|
# 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
|
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
|
if [ -n "$appsec_status" ] && [ "$appsec_status" != "null" ]; then
|
||||||
echo "$appsec_status"
|
echo "$appsec_status"
|
||||||
@ -340,7 +346,7 @@ configure_metrics() {
|
|||||||
get_collections() {
|
get_collections() {
|
||||||
check_cscli
|
check_cscli
|
||||||
local output
|
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
|
if [ -z "$output" ] || [ "$output" = "null" ]; then
|
||||||
echo '{"collections":[]}'
|
echo '{"collections":[]}'
|
||||||
else
|
else
|
||||||
@ -362,7 +368,7 @@ install_collection() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Install collection
|
# 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_boolean "success" 1
|
||||||
json_add_string "message" "Collection '$collection' installed successfully"
|
json_add_string "message" "Collection '$collection' installed successfully"
|
||||||
secubox_log "Installed collection: $collection"
|
secubox_log "Installed collection: $collection"
|
||||||
@ -388,7 +394,7 @@ remove_collection() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Remove collection
|
# 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_boolean "success" 1
|
||||||
json_add_string "message" "Collection '$collection' removed successfully"
|
json_add_string "message" "Collection '$collection' removed successfully"
|
||||||
secubox_log "Removed collection: $collection"
|
secubox_log "Removed collection: $collection"
|
||||||
@ -405,7 +411,7 @@ update_hub() {
|
|||||||
check_cscli
|
check_cscli
|
||||||
json_init
|
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_boolean "success" 1
|
||||||
json_add_string "message" "Hub index updated successfully"
|
json_add_string "message" "Hub index updated successfully"
|
||||||
secubox_log "Hub index updated"
|
secubox_log "Hub index updated"
|
||||||
@ -434,7 +440,7 @@ register_bouncer() {
|
|||||||
# Check if bouncer already exists (robust pattern matching)
|
# Check if bouncer already exists (robust pattern matching)
|
||||||
local exists=0
|
local exists=0
|
||||||
local bouncer_list
|
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
|
if echo "$bouncer_list" | grep -qE "\"name\"[[:space:]]*:[[:space:]]*\"$bouncer_name\""; then
|
||||||
exists=1
|
exists=1
|
||||||
fi
|
fi
|
||||||
@ -443,7 +449,7 @@ register_bouncer() {
|
|||||||
if [ "$exists" = "1" ]; then
|
if [ "$exists" = "1" ]; then
|
||||||
secubox_log "Bouncer '$bouncer_name' already exists, deleting for re-registration..."
|
secubox_log "Bouncer '$bouncer_name' already exists, deleting for re-registration..."
|
||||||
# Delete existing bouncer to get new API key
|
# 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..."
|
secubox_log "Warning: Could not delete bouncer via cscli, trying force..."
|
||||||
fi
|
fi
|
||||||
# Small delay to ensure deletion is processed
|
# Small delay to ensure deletion is processed
|
||||||
@ -451,7 +457,7 @@ register_bouncer() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Generate API key
|
# 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
|
if [ -n "$api_key" ] && [ "${#api_key}" -gt 10 ] && ! echo "$api_key" | grep -qi "error\|unable\|failed"; then
|
||||||
json_add_boolean "success" 1
|
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
|
sqlite3 /srv/crowdsec/data/crowdsec.db "DELETE FROM bouncers WHERE name='$bouncer_name';" 2>/dev/null
|
||||||
sleep 1
|
sleep 1
|
||||||
# Retry registration
|
# 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
|
if [ -n "$api_key" ] && [ "${#api_key}" -gt 10 ] && ! echo "$api_key" | grep -qi "error\|unable\|failed"; then
|
||||||
json_add_boolean "success" 1
|
json_add_boolean "success" 1
|
||||||
json_add_string "api_key" "$api_key"
|
json_add_string "api_key" "$api_key"
|
||||||
@ -501,7 +507,7 @@ delete_bouncer() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Delete bouncer
|
# 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_boolean "success" 1
|
||||||
json_add_string "message" "Bouncer '$bouncer_name' deleted successfully"
|
json_add_string "message" "Bouncer '$bouncer_name' deleted successfully"
|
||||||
secubox_log "Deleted bouncer: $bouncer_name"
|
secubox_log "Deleted bouncer: $bouncer_name"
|
||||||
@ -837,7 +843,7 @@ check_wizard_needed() {
|
|||||||
# Check if collections are installed
|
# Check if collections are installed
|
||||||
local collections_installed=0
|
local collections_installed=0
|
||||||
if [ -x "$CSCLI" ]; then
|
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
|
collections_installed=1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@ -862,7 +868,7 @@ get_wizard_state() {
|
|||||||
# Get collections count
|
# Get collections count
|
||||||
local collections_count=0
|
local collections_count=0
|
||||||
if [ -x "$CSCLI" ]; then
|
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
|
fi
|
||||||
|
|
||||||
json_add_int "collections_count" "$collections_count"
|
json_add_int "collections_count" "$collections_count"
|
||||||
@ -919,9 +925,9 @@ repair_lapi() {
|
|||||||
# Step 4: Re-register local machine if needed
|
# Step 4: Re-register local machine if needed
|
||||||
if [ -x "$CSCLI" ]; then
|
if [ -x "$CSCLI" ]; then
|
||||||
# Check if machine is registered and working
|
# 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
|
# 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; "
|
steps_done="${steps_done}Re-registered localhost machine; "
|
||||||
# Restart again to apply new credentials
|
# Restart again to apply new credentials
|
||||||
/etc/init.d/crowdsec restart >/dev/null 2>&1
|
/etc/init.d/crowdsec restart >/dev/null 2>&1
|
||||||
@ -935,7 +941,7 @@ repair_lapi() {
|
|||||||
# Step 5: Verify LAPI is now working
|
# Step 5: Verify LAPI is now working
|
||||||
local lapi_ok=0
|
local lapi_ok=0
|
||||||
if [ -x "$CSCLI" ]; then
|
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
|
lapi_ok=1
|
||||||
steps_done="${steps_done}LAPI verified working"
|
steps_done="${steps_done}LAPI verified working"
|
||||||
else
|
else
|
||||||
@ -973,7 +979,7 @@ reset_wizard() {
|
|||||||
|
|
||||||
# Step 2: Delete existing bouncer registration
|
# Step 2: Delete existing bouncer registration
|
||||||
if [ -x "$CSCLI" ]; then
|
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; "
|
steps_done="${steps_done}Deleted bouncer registration; "
|
||||||
|
|
||||||
# Also try database cleanup
|
# Also try database cleanup
|
||||||
@ -1023,7 +1029,7 @@ get_console_status() {
|
|||||||
|
|
||||||
# Try to get console status from cscli
|
# Try to get console status from cscli
|
||||||
local console_status
|
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
|
if [ -n "$console_status" ]; then
|
||||||
# Check if enrolled by looking for "Enrolled" or similar in output
|
# Check if enrolled by looking for "Enrolled" or similar in output
|
||||||
if echo "$console_status" | grep -qi "enrolled\|connected\|active"; then
|
if echo "$console_status" | grep -qi "enrolled\|connected\|active"; then
|
||||||
@ -1068,7 +1074,7 @@ console_enroll() {
|
|||||||
secubox_log "Enrolling CrowdSec Console with key..."
|
secubox_log "Enrolling CrowdSec Console with key..."
|
||||||
|
|
||||||
# Build enroll command
|
# Build enroll command
|
||||||
local enroll_cmd="$CSCLI console enroll $key"
|
local enroll_cmd="run_cscli console enroll $key"
|
||||||
if [ -n "$name" ]; then
|
if [ -n "$name" ]; then
|
||||||
enroll_cmd="$enroll_cmd --name \"$name\""
|
enroll_cmd="$enroll_cmd --name \"$name\""
|
||||||
fi
|
fi
|
||||||
@ -1085,9 +1091,9 @@ console_enroll() {
|
|||||||
secubox_log "Console enrollment successful"
|
secubox_log "Console enrollment successful"
|
||||||
|
|
||||||
# Enable sharing options by default
|
# Enable sharing options by default
|
||||||
$CSCLI console enable share_manual_decisions >/dev/null 2>&1
|
run_cscli console enable share_manual_decisions >/dev/null 2>&1
|
||||||
$CSCLI console enable share_tainted >/dev/null 2>&1
|
run_cscli console enable share_tainted >/dev/null 2>&1
|
||||||
$CSCLI console enable share_context >/dev/null 2>&1
|
run_cscli console enable share_context >/dev/null 2>&1
|
||||||
else
|
else
|
||||||
json_add_boolean "success" 0
|
json_add_boolean "success" 0
|
||||||
json_add_string "error" "Enrollment failed"
|
json_add_string "error" "Enrollment failed"
|
||||||
@ -1106,9 +1112,9 @@ console_disable() {
|
|||||||
secubox_log "Disabling CrowdSec Console enrollment..."
|
secubox_log "Disabling CrowdSec Console enrollment..."
|
||||||
|
|
||||||
# Disable all sharing
|
# Disable all sharing
|
||||||
$CSCLI console disable share_manual_decisions >/dev/null 2>&1
|
run_cscli console disable share_manual_decisions >/dev/null 2>&1
|
||||||
$CSCLI console disable share_tainted >/dev/null 2>&1
|
run_cscli console disable share_tainted >/dev/null 2>&1
|
||||||
$CSCLI console disable share_context >/dev/null 2>&1
|
run_cscli console disable share_context >/dev/null 2>&1
|
||||||
|
|
||||||
# Remove console config
|
# Remove console config
|
||||||
if [ -f "/etc/crowdsec/console.yaml" ]; then
|
if [ -f "/etc/crowdsec/console.yaml" ]; then
|
||||||
@ -1122,6 +1128,159 @@ console_disable() {
|
|||||||
json_dump
|
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 (start/stop/restart/reload)
|
||||||
service_control() {
|
service_control() {
|
||||||
local action="$1"
|
local action="$1"
|
||||||
@ -1164,7 +1323,7 @@ service_control() {
|
|||||||
# Main dispatcher
|
# Main dispatcher
|
||||||
case "$1" in
|
case "$1" in
|
||||||
list)
|
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)
|
call)
|
||||||
case "$2" in
|
case "$2" in
|
||||||
@ -1298,6 +1457,18 @@ case "$1" in
|
|||||||
action=$(echo "$input" | jsonfilter -e '@.action' 2>/dev/null)
|
action=$(echo "$input" | jsonfilter -e '@.action' 2>/dev/null)
|
||||||
service_control "$action"
|
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"}'
|
echo '{"error": "Unknown method"}'
|
||||||
;;
|
;;
|
||||||
|
|||||||
@ -21,7 +21,8 @@
|
|||||||
"nftables_stats",
|
"nftables_stats",
|
||||||
"check_wizard_needed",
|
"check_wizard_needed",
|
||||||
"wizard_state",
|
"wizard_state",
|
||||||
"console_status"
|
"console_status",
|
||||||
|
"acquisition_config"
|
||||||
],
|
],
|
||||||
"file": [ "read", "stat" ]
|
"file": [ "read", "stat" ]
|
||||||
},
|
},
|
||||||
@ -44,10 +45,12 @@
|
|||||||
"repair_lapi",
|
"repair_lapi",
|
||||||
"console_enroll",
|
"console_enroll",
|
||||||
"console_disable",
|
"console_disable",
|
||||||
"service_control"
|
"service_control",
|
||||||
|
"configure_acquisition",
|
||||||
|
"reset_wizard"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"uci": [ "crowdsec-dashboard" ]
|
"uci": [ "crowdsec", "crowdsec-dashboard" ]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
70
package/secubox/luci-app-secubox-crowdsec/Makefile
Normal file
70
package/secubox/luci-app-secubox-crowdsec/Makefile
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
#
|
||||||
|
# LuCI SecuBox CrowdSec Dashboard
|
||||||
|
# Copyright (C) 2025 CyberMind.fr - Gandalf <gandalf@gk2.net>
|
||||||
|
#
|
||||||
|
|
||||||
|
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 <gandalf@gk2.net>
|
||||||
|
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))
|
||||||
@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -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
|
||||||
|
});
|
||||||
@ -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
|
||||||
|
});
|
||||||
@ -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
|
||||||
|
});
|
||||||
@ -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
|
||||||
|
});
|
||||||
@ -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();
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -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'
|
||||||
@ -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
|
||||||
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk
|
|||||||
|
|
||||||
PKG_NAME:=crowdsec
|
PKG_NAME:=crowdsec
|
||||||
PKG_VERSION:=1.7.4
|
PKG_VERSION:=1.7.4
|
||||||
PKG_RELEASE:=2
|
PKG_RELEASE:=3
|
||||||
PKG_ARCH:=all
|
PKG_ARCH:=all
|
||||||
|
|
||||||
PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION).tar.gz
|
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/* \
|
$(GO_PKG_BUILD_DIR)/src/$(GO_PKG)/config/patterns/* \
|
||||||
$(1)/etc/crowdsec/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)/srv/crowdsec/data/
|
||||||
|
|
||||||
$(INSTALL_DIR) $(1)/etc/init.d
|
$(INSTALL_DIR) $(1)/etc/init.d
|
||||||
@ -227,6 +242,7 @@ endef
|
|||||||
|
|
||||||
define Package/crowdsec/conffiles
|
define Package/crowdsec/conffiles
|
||||||
/etc/crowdsec/
|
/etc/crowdsec/
|
||||||
|
/etc/crowdsec/acquis.d/
|
||||||
/etc/config/crowdsec
|
/etc/config/crowdsec
|
||||||
endef
|
endef
|
||||||
|
|
||||||
|
|||||||
@ -3,30 +3,35 @@
|
|||||||
## Version
|
## Version
|
||||||
- **Package**: secubox-app-crowdsec
|
- **Package**: secubox-app-crowdsec
|
||||||
- **CrowdSec Core**: v1.7.4
|
- **CrowdSec Core**: v1.7.4
|
||||||
- **Release**: 1
|
- **Release**: 3
|
||||||
- **Last Updated**: December 30, 2024
|
- **Last Updated**: January 2025
|
||||||
|
|
||||||
## Description
|
## 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)
|
## Key Features (v1.7.4)
|
||||||
- ✅ WAF capability with DropRequest helper for request blocking
|
- WAF capability with DropRequest helper for request blocking
|
||||||
- ✅ Refactored syslog acquisition using RestartableStreamer
|
- Refactored syslog acquisition using RestartableStreamer
|
||||||
- ✅ Optional pure-go SQLite driver for better compatibility
|
- Optional pure-go SQLite driver for better compatibility
|
||||||
- ✅ Enhanced logging configuration with syslog media support
|
- Enhanced logging configuration with syslog media support
|
||||||
- ✅ Configurable usage metrics export (api.server.disable_usage_metrics_export)
|
- Configurable usage metrics export (api.server.disable_usage_metrics_export)
|
||||||
- ✅ Fixed LAPI metrics cardinality issues with Prometheus
|
- Fixed LAPI metrics cardinality issues with Prometheus
|
||||||
- ✅ Data race prevention in Docker acquisition
|
- Data race prevention in Docker acquisition
|
||||||
- ✅ Database query optimization for decision streams
|
- Database query optimization for decision streams
|
||||||
|
- **Automatic OpenWrt log acquisition configuration**
|
||||||
|
- **UCI-based acquisition management**
|
||||||
|
|
||||||
## Package Contents
|
## Package Contents
|
||||||
- **Makefile**: OpenWrt package definition for CrowdSec v1.7.4
|
- **Makefile**: OpenWrt package definition for CrowdSec v1.7.4
|
||||||
- **files/**: Configuration and init scripts
|
- **files/**: Configuration and init scripts
|
||||||
- `crowdsec.initd`: Init script for service management
|
- `crowdsec.initd`: Init script for service management
|
||||||
- `crowdsec.config`: UCI configuration
|
- `crowdsec.config`: UCI configuration (with acquisition settings)
|
||||||
- `crowdsec.defaults`: Default configuration (uci-defaults)
|
- `crowdsec.defaults`: Default configuration with auto-detection
|
||||||
- **patches/**: Patches for OpenWrt compatibility
|
- `acquis.d/`: Acquisition configuration templates
|
||||||
- `001-fix_config_data_dir.patch`: Fix data directory path for OpenWrt
|
- `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
|
## Installation
|
||||||
```bash
|
```bash
|
||||||
@ -35,17 +40,82 @@ cd /home/reepost/CyberMindStudio/_files/secubox-openwrt
|
|||||||
make package/secubox/secubox-app-crowdsec/compile V=s
|
make package/secubox/secubox-app-crowdsec/compile V=s
|
||||||
|
|
||||||
# Install on router
|
# Install on router
|
||||||
opkg install crowdsec_1.7.4-1_*.ipk
|
opkg install crowdsec_1.7.4-3_*.ipk
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## 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`
|
- 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`
|
- Profiles: `/etc/crowdsec/profiles.yaml`
|
||||||
- Local API: `/etc/crowdsec/local_api_credentials.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
|
## Service Management
|
||||||
```bash
|
```bash
|
||||||
@ -68,6 +138,9 @@ CrowdSec CLI is available via `cscli`:
|
|||||||
# Check version
|
# Check version
|
||||||
cscli version
|
cscli version
|
||||||
|
|
||||||
|
# Check acquisition status
|
||||||
|
cscli metrics show acquisition
|
||||||
|
|
||||||
# List decisions
|
# List decisions
|
||||||
cscli decisions list
|
cscli decisions list
|
||||||
|
|
||||||
@ -78,14 +151,48 @@ cscli alerts list
|
|||||||
cscli collections list
|
cscli collections list
|
||||||
cscli collections install crowdsecurity/nginx
|
cscli collections install crowdsecurity/nginx
|
||||||
|
|
||||||
|
# Manage Hub
|
||||||
|
cscli hub update
|
||||||
|
cscli hub upgrade
|
||||||
|
|
||||||
# Manage bouncers
|
# Manage bouncers
|
||||||
cscli bouncers list
|
cscli bouncers list
|
||||||
cscli bouncers add firewall-bouncer
|
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
|
## Integration with SecuBox
|
||||||
This package integrates with:
|
This package integrates with:
|
||||||
- **luci-app-crowdsec-dashboard** v0.5.0+
|
- **luci-app-crowdsec-dashboard** v0.5.0+
|
||||||
|
- **secubox-app-crowdsec-bouncer** - Firewall bouncer
|
||||||
- **SecuBox Theme System**
|
- **SecuBox Theme System**
|
||||||
- **SecuBox Logging** (`secubox-log`)
|
- **SecuBox Logging** (`secubox-log`)
|
||||||
|
|
||||||
@ -98,11 +205,20 @@ This package integrates with:
|
|||||||
- Upstream: https://github.com/crowdsecurity/crowdsec
|
- Upstream: https://github.com/crowdsecurity/crowdsec
|
||||||
- Documentation: https://docs.crowdsec.net/
|
- Documentation: https://docs.crowdsec.net/
|
||||||
- Hub: https://hub.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
|
- SecuBox Project: https://cybermind.fr
|
||||||
|
|
||||||
## Changelog
|
## 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
|
- Updated from v1.6.2 to v1.7.4
|
||||||
- Added WAF/AppSec support
|
- Added WAF/AppSec support
|
||||||
- Improved syslog acquisition
|
- Improved syslog acquisition
|
||||||
|
|||||||
@ -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
|
||||||
@ -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
|
||||||
@ -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
|
||||||
@ -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
|
||||||
@ -2,3 +2,29 @@ config crowdsec 'crowdsec'
|
|||||||
option data_dir '/srv/crowdsec/data'
|
option data_dir '/srv/crowdsec/data'
|
||||||
option db_path '/srv/crowdsec/data/crowdsec.db'
|
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'
|
||||||
|
|
||||||
|
|||||||
@ -1,52 +1,317 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# CrowdSec UCI Defaults Script
|
||||||
|
# Configures CrowdSec on first install with automatic acquisition setup
|
||||||
|
#
|
||||||
|
|
||||||
CONFIG=/etc/crowdsec/config.yaml
|
CONFIG=/etc/crowdsec/config.yaml
|
||||||
data_dir=`uci get "crowdsec.crowdsec.data_dir"`
|
ACQUIS_DIR=/etc/crowdsec/acquis.d
|
||||||
sed -i "s,^\(\s*data_dir\s*:\s*\).*\$,\1$data_dir," $CONFIG
|
UCI_CONFIG=/etc/config/crowdsec
|
||||||
db_path=`uci get "crowdsec.crowdsec.db_path"`
|
|
||||||
sed -i "s,^\(\s*db_path\s*:\s*\).*\$,\1$db_path," $CONFIG
|
|
||||||
|
|
||||||
# Create data dir & permissions if needed
|
# Load UCI functions
|
||||||
if [ ! -d "${data_dir}" ]; then
|
. /lib/functions.sh
|
||||||
mkdir -m 0755 -p "${data_dir}"
|
|
||||||
fi;
|
# 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
|
# Create machine-id if not exists
|
||||||
if [ ! -f /etc/machine-id ]; then
|
setup_machine_id() {
|
||||||
cat /proc/sys/kernel/random/uuid | tr -d "-" > /etc/machine-id
|
if [ ! -f /etc/machine-id ]; then
|
||||||
fi
|
cat /proc/sys/kernel/random/uuid | tr -d "-" > /etc/machine-id
|
||||||
|
echo "Created machine-id"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Register local API machine
|
# Register local API machine
|
||||||
if grep -q "login:" /etc/crowdsec/local_api_credentials.yaml 2>/dev/null; then
|
register_lapi() {
|
||||||
echo "Local API already registered"
|
if grep -q "login:" /etc/crowdsec/local_api_credentials.yaml 2>/dev/null; then
|
||||||
else
|
echo "Local API already registered"
|
||||||
echo "Registering local API machine..."
|
else
|
||||||
cscli -c /etc/crowdsec/config.yaml machines add -a -f /etc/crowdsec/local_api_credentials.yaml
|
echo "Registering local API machine..."
|
||||||
fi
|
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
|
# Register with Central API (CAPI) for threat intelligence sharing
|
||||||
if ! grep -q "login:" /etc/crowdsec/online_api_credentials.yaml 2>/dev/null; then
|
register_capi() {
|
||||||
echo "Registering with Central API (CAPI)..."
|
if ! grep -q "login:" /etc/crowdsec/online_api_credentials.yaml 2>/dev/null; then
|
||||||
if cscli capi register 2>/dev/null; then
|
echo "Registering with Central API (CAPI)..."
|
||||||
echo "Successfully registered with Central API"
|
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
|
else
|
||||||
echo "WARNING: CAPI registration failed - will run in local-only mode"
|
echo "Central API already registered"
|
||||||
# Create minimal credentials file to prevent errors
|
|
||||||
echo "url: https://api.crowdsec.net/" > /etc/crowdsec/online_api_credentials.yaml
|
|
||||||
fi
|
fi
|
||||||
else
|
}
|
||||||
echo "Central API already registered"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Update hub index
|
# 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
|
update_hub() {
|
||||||
echo "Updating hub index..."
|
local update_interval
|
||||||
cscli hub update 2>/dev/null || true
|
update_interval=$(get_uci_value "hub" "update_interval" "7")
|
||||||
fi
|
|
||||||
|
|
||||||
# Install default collections
|
if [ "$update_interval" = "0" ]; then
|
||||||
cscli collections install crowdsecurity/linux 2>/dev/null || true
|
echo "Hub auto-update disabled"
|
||||||
cscli parsers install crowdsecurity/whitelists 2>/dev/null || true
|
return 0
|
||||||
cscli hub upgrade 2>/dev/null || true
|
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
|
exit 0
|
||||||
|
|||||||
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
53
package/secubox/secubox-crowdsec-setup/Makefile
Normal file
53
package/secubox/secubox-crowdsec-setup/Makefile
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# SPDX-License-Identifier: MIT
|
||||||
|
#
|
||||||
|
# SecuBox CrowdSec Setup Package
|
||||||
|
# Copyright (C) 2025 CyberMind.fr - Gandalf <gandalf@gk2.net>
|
||||||
|
#
|
||||||
|
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
|
||||||
|
PKG_NAME:=secubox-crowdsec-setup
|
||||||
|
PKG_VERSION:=1.0.0
|
||||||
|
PKG_RELEASE:=1
|
||||||
|
|
||||||
|
PKG_MAINTAINER:=Gerald Kerma <gandalf@gk2.net>
|
||||||
|
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))
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user