feat(wazuh): Add LuCI dashboard for Wazuh SIEM integration

Create luci-app-wazuh package with unified security monitoring dashboard
inspired by SysWarden's layered defense model:

- 4 views: Overview, Alerts, File Integrity, Agents
- RPCD handler with 12 API methods for status, alerts, FIM, agent control
- SysWarden-style 4-layer security visualization:
  - Layer 1: Vortex Firewall + nftables (kernel-level)
  - Layer 2: CrowdSec + Bouncer (IPS)
  - Layer 3: Wazuh Manager (SIEM/XDR)
  - Layer 4: mitmproxy + HAProxy (WAF)
- CrowdSec integration for threat correlation
- Real-time polling and auto-refresh
- Simplified printf-based JSON output (avoids jshn segfault)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-02-14 14:45:05 +01:00
parent a3f3b2dd8c
commit b1c34021db
11 changed files with 1461 additions and 0 deletions

View File

@ -1679,3 +1679,21 @@ git checkout HEAD -- index.html
- Site now displays medieval/renaissance cinematic presentation
- Title: "Les Seigneurs de La Chambre - Présentation Cinématique"
- Description: "seigneurs de la Chambre" (from UCI config)
### 2026-02-14: Wazuh SIEM LuCI Dashboard Integration
- **Created luci-app-wazuh package** - unified Wazuh SIEM monitoring dashboard
- 4 views: Overview, Alerts, FIM (File Integrity), Agents
- SysWarden-inspired 4-layer security visualization:
- Layer 1: Vortex Firewall + nftables (kernel-level)
- Layer 2: CrowdSec + Bouncer (IPS)
- Layer 3: Wazuh Manager (SIEM/XDR)
- Layer 4: mitmproxy + HAProxy (WAF)
- **RPCD handler (luci.wazuh)** with 12 API methods:
- get_overview, get_agent_status, get_manager_status
- get_alerts, get_alert_summary
- get_fim_events, get_fim_config
- list_agents, get_crowdsec_correlation
- start_agent, stop_agent, restart_agent
- **API wrapper (wazuh/api.js)** with helper functions for alert levels and timestamps
- **Fixed jshn segfault issue** - simplified to printf-based JSON output
- Tested all RPCD methods via ubus calls

View File

@ -64,6 +64,14 @@ _Last updated: 2026-02-14 (WAF architecture configured)_
### Just Completed (2026-02-14)
- **Wazuh SIEM LuCI Dashboard** — DONE (2026-02-14)
- Created `luci-app-wazuh` package for unified Wazuh security monitoring
- 4 views: Overview, Alerts, File Integrity, Agents
- SysWarden-inspired 4-layer security visualization
- RPCD handler (luci.wazuh) with 12 API methods
- CrowdSec integration for threat correlation display
- Full RPCD testing verified via ubus calls
- **MetaBlogizer SDLC Content Restoration** — DONE (2026-02-14)
- sdlc.gk2.secubox.in was showing GK2 Hub template instead of original content
- GK2 Hub generator had overwritten local index.html

View File

@ -0,0 +1,30 @@
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-wazuh
PKG_VERSION:=1.0.0
PKG_RELEASE:=1
PKG_MAINTAINER:=SecuBox <dev@secubox.in>
LUCI_TITLE:=LuCI Wazuh SIEM Dashboard
LUCI_DESCRIPTION:=Unified security monitoring dashboard for Wazuh SIEM/XDR integration
LUCI_DEPENDS:=+luci-base +secubox-app-wazuh
LUCI_PKGARCH:=all
include $(TOPDIR)/feeds/luci/luci.mk
define Package/$(PKG_NAME)/install
$(INSTALL_DIR) $(1)/www/luci-static/resources/wazuh
$(INSTALL_DIR) $(1)/www/luci-static/resources/view/wazuh
$(INSTALL_DIR) $(1)/usr/libexec/rpcd
$(INSTALL_DIR) $(1)/usr/share/luci/menu.d
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
$(INSTALL_DATA) ./htdocs/luci-static/resources/wazuh/*.js $(1)/www/luci-static/resources/wazuh/
$(INSTALL_DATA) ./htdocs/luci-static/resources/view/wazuh/*.js $(1)/www/luci-static/resources/view/wazuh/
$(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.wazuh $(1)/usr/libexec/rpcd/
$(INSTALL_DATA) ./root/usr/share/luci/menu.d/*.json $(1)/usr/share/luci/menu.d/
$(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/*.json $(1)/usr/share/rpcd/acl.d/
endef
$(eval $(call BuildPackage,$(PKG_NAME)))

View File

@ -0,0 +1,224 @@
'use strict';
'require view';
'require dom';
'require poll';
'require wazuh.api as api';
return view.extend({
handleSaveApply: null,
handleSave: null,
handleReset: null,
load: function() {
return api.listAgents();
},
render: function(data) {
var agents = data.agents || [];
// Calculate statistics
var connected = agents.filter(function(a) { return a.status === 'active' || a.status === 'connected'; }).length;
var disconnected = agents.filter(function(a) { return a.status === 'disconnected'; }).length;
var pending = agents.filter(function(a) { return a.status === 'pending' || a.status === 'never_connected'; }).length;
var view = E('div', { 'class': 'cbi-map' }, [
E('h2', {}, _('Wazuh Agents')),
E('div', { 'class': 'cbi-map-descr' },
_('Manage security agents across your infrastructure')
),
// Agent Statistics
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Agent Status Summary')),
E('div', { 'style': 'display: flex; gap: 1rem; flex-wrap: wrap;' }, [
this.renderStatCard('Connected', connected, 'success'),
this.renderStatCard('Disconnected', disconnected, 'danger'),
this.renderStatCard('Pending', pending, 'warning'),
this.renderStatCard('Total', agents.length, 'info')
])
]),
// Actions
E('div', { 'class': 'cbi-section' }, [
E('div', { 'style': 'display: flex; gap: 1rem; flex-wrap: wrap;' }, [
E('button', {
'class': 'btn cbi-button cbi-button-action',
'click': L.bind(this.handleRefresh, this)
}, _('Refresh')),
E('a', {
'href': 'https://wazuh.gk2.secubox.in/app/wazuh#/agents-preview',
'target': '_blank',
'class': 'btn cbi-button'
}, _('View in Wazuh Dashboard'))
])
]),
// Agents Table
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Registered Agents')),
E('div', { 'id': 'agents-container' }, [
this.renderAgentsTable(agents)
])
]),
// Local Agent Quick Actions
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Local Agent Control')),
E('div', { 'class': 'cbi-value', 'style': 'background: var(--background-color-high); padding: 1rem; border-radius: 8px;' }, [
E('p', { 'style': 'margin-bottom: 1rem;' },
_('Control the Wazuh agent running on this SecuBox device')
),
E('div', { 'style': 'display: flex; gap: 0.5rem; flex-wrap: wrap;' }, [
E('button', {
'class': 'btn cbi-button cbi-button-apply',
'click': L.bind(this.handleStartAgent, this)
}, _('Start Agent')),
E('button', {
'class': 'btn cbi-button cbi-button-remove',
'click': L.bind(this.handleStopAgent, this)
}, _('Stop Agent')),
E('button', {
'class': 'btn cbi-button cbi-button-action',
'click': L.bind(this.handleRestartAgent, this)
}, _('Restart Agent'))
])
])
]),
// Agent Installation Guide
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Deploy New Agent')),
E('div', { 'style': 'background: var(--background-color-high); padding: 1rem; border-radius: 8px;' }, [
E('p', {}, _('To register a new agent with the Wazuh Manager:')),
E('ol', { 'style': 'margin: 1rem 0; padding-left: 1.5rem;' }, [
E('li', {}, _('Install Wazuh agent on the target system')),
E('li', {}, _('Configure agent to connect to manager: 192.168.255.50')),
E('li', {}, _('Register with: /var/ossec/bin/agent-auth -m 192.168.255.50')),
E('li', {}, _('Start the agent service'))
]),
E('div', { 'style': 'margin-top: 1rem;' }, [
E('a', {
'href': 'https://documentation.wazuh.com/current/installation-guide/wazuh-agent/index.html',
'target': '_blank',
'class': 'btn cbi-button'
}, _('Agent Installation Guide'))
])
])
])
]);
poll.add(L.bind(this.pollAgents, this), 30);
return view;
},
renderStatCard: function(label, count, badgeClass) {
return E('div', {
'style': 'text-align: center; padding: 1rem; background: var(--background-color-high); border-radius: 8px; min-width: 120px;'
}, [
E('div', { 'style': 'font-size: 2.5em; font-weight: bold;' }, String(count)),
E('div', { 'class': 'badge ' + badgeClass, 'style': 'font-size: 0.9em;' }, label)
]);
},
renderAgentsTable: function(agents) {
if (!agents || agents.length === 0) {
return E('div', { 'class': 'cbi-value', 'style': 'text-align: center; padding: 2rem;' },
_('No agents registered')
);
}
var rows = [
E('tr', { 'class': 'tr' }, [
E('th', { 'class': 'th', 'style': 'width: 60px;' }, _('ID')),
E('th', { 'class': 'th' }, _('Name')),
E('th', { 'class': 'th', 'style': 'width: 120px;' }, _('IP Address')),
E('th', { 'class': 'th', 'style': 'width: 100px;' }, _('Status')),
E('th', { 'class': 'th', 'style': 'width: 100px;' }, _('OS')),
E('th', { 'class': 'th', 'style': 'width: 100px;' }, _('Version')),
E('th', { 'class': 'th', 'style': 'width: 150px;' }, _('Last Keep Alive'))
])
];
agents.forEach(function(agent) {
var statusClass = (agent.status === 'active' || agent.status === 'connected') ? 'success' :
(agent.status === 'disconnected' ? 'danger' : 'warning');
var statusText = agent.status === 'active' ? 'Connected' :
(agent.status === 'connected' ? 'Connected' :
(agent.status === 'disconnected' ? 'Disconnected' :
(agent.status === 'pending' ? 'Pending' :
(agent.status === 'never_connected' ? 'Never Connected' : agent.status || 'Unknown'))));
rows.push(E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, [
E('code', {}, agent.id || '-')
]),
E('td', { 'class': 'td', 'style': 'font-weight: bold;' }, agent.name || '-'),
E('td', { 'class': 'td', 'style': 'font-family: monospace;' }, agent.ip || '-'),
E('td', { 'class': 'td' }, [
E('span', { 'class': 'badge ' + statusClass }, statusText)
]),
E('td', { 'class': 'td', 'style': 'font-size: 0.85em;' },
agent.os_name || agent.os || '-'
),
E('td', { 'class': 'td', 'style': 'font-family: monospace; font-size: 0.85em;' },
agent.version || '-'
),
E('td', { 'class': 'td', 'style': 'font-size: 0.85em;' },
api.formatTime(agent.lastKeepAlive || agent.last_keepalive)
)
]));
});
return E('table', { 'class': 'table' }, rows);
},
handleRefresh: function() {
var container = document.getElementById('agents-container');
if (container) {
container.innerHTML = '<div style="text-align: center; padding: 2rem;">Loading...</div>';
}
var self = this;
return api.listAgents().then(function(data) {
var agents = data.agents || [];
if (container) {
dom.content(container, self.renderAgentsTable(agents));
}
});
},
handleStartAgent: function() {
return api.startAgent().then(function(res) {
if (res.success) {
L.ui.addNotification(null, E('p', _('Wazuh agent started successfully')), 'info');
} else {
L.ui.addNotification(null, E('p', _('Failed to start agent: %s').format(res.error || 'Unknown error')), 'error');
}
});
},
handleStopAgent: function() {
return api.stopAgent().then(function(res) {
if (res.success) {
L.ui.addNotification(null, E('p', _('Wazuh agent stopped')), 'info');
} else {
L.ui.addNotification(null, E('p', _('Failed to stop agent: %s').format(res.error || 'Unknown error')), 'error');
}
});
},
handleRestartAgent: function() {
return api.restartAgent().then(function(res) {
if (res.success) {
L.ui.addNotification(null, E('p', _('Wazuh agent restarted successfully')), 'info');
} else {
L.ui.addNotification(null, E('p', _('Failed to restart agent: %s').format(res.error || 'Unknown error')), 'error');
}
});
},
pollAgents: function() {
return this.handleRefresh();
}
});

View File

@ -0,0 +1,156 @@
'use strict';
'require view';
'require dom';
'require poll';
'require wazuh.api as api';
return view.extend({
handleSaveApply: null,
handleSave: null,
handleReset: null,
load: function() {
return api.getAlerts(50, 0);
},
render: function(data) {
var alerts = data.alerts || [];
var self = this;
var view = E('div', { 'class': 'cbi-map' }, [
E('h2', {}, _('Wazuh Security Alerts')),
E('div', { 'class': 'cbi-map-descr' },
_('Real-time security events from Wazuh SIEM/XDR')
),
// Filter Controls
E('div', { 'class': 'cbi-section' }, [
E('div', { 'style': 'display: flex; gap: 1rem; align-items: center; flex-wrap: wrap;' }, [
E('label', {}, _('Filter by Level:')),
E('select', {
'id': 'level-filter',
'class': 'cbi-input-select',
'change': L.bind(this.handleFilterChange, this)
}, [
E('option', { 'value': '0' }, _('All Levels')),
E('option', { 'value': '12' }, _('Critical (12+)')),
E('option', { 'value': '9' }, _('High (9+)')),
E('option', { 'value': '5' }, _('Medium (5+)')),
E('option', { 'value': '1' }, _('Low (1+)'))
]),
E('label', { 'style': 'margin-left: 1rem;' }, _('Count:')),
E('select', {
'id': 'count-filter',
'class': 'cbi-input-select',
'change': L.bind(this.handleFilterChange, this)
}, [
E('option', { 'value': '20' }, '20'),
E('option', { 'value': '50', 'selected': 'selected' }, '50'),
E('option', { 'value': '100' }, '100'),
E('option', { 'value': '200' }, '200')
]),
E('button', {
'class': 'btn cbi-button cbi-button-action',
'style': 'margin-left: 1rem;',
'click': L.bind(this.handleRefresh, this)
}, _('Refresh'))
])
]),
// Alerts Table
E('div', { 'class': 'cbi-section' }, [
E('div', { 'id': 'alerts-container' }, [
this.renderAlertsTable(alerts)
])
]),
// Legend
E('div', { 'class': 'cbi-section' }, [
E('h4', {}, _('Severity Legend')),
E('div', { 'style': 'display: flex; gap: 1rem; flex-wrap: wrap;' }, [
E('span', { 'class': 'badge danger', 'style': 'padding: 0.3rem 0.6rem;' }, _('Critical (12+)')),
E('span', { 'class': 'badge warning', 'style': 'padding: 0.3rem 0.6rem;' }, _('High (9-11)')),
E('span', { 'class': 'badge notice', 'style': 'padding: 0.3rem 0.6rem;' }, _('Medium (5-8)')),
E('span', { 'class': 'badge info', 'style': 'padding: 0.3rem 0.6rem;' }, _('Low (1-4)'))
])
])
]);
// Setup auto-refresh
poll.add(L.bind(this.pollAlerts, this), 60);
return view;
},
renderAlertsTable: function(alerts) {
if (!alerts || alerts.length === 0) {
return E('div', { 'class': 'cbi-value', 'style': 'text-align: center; padding: 2rem;' },
_('No alerts found')
);
}
var rows = [
E('tr', { 'class': 'tr' }, [
E('th', { 'class': 'th', 'style': 'width: 60px;' }, _('Level')),
E('th', { 'class': 'th', 'style': 'width: 150px;' }, _('Time')),
E('th', { 'class': 'th', 'style': 'width: 100px;' }, _('Agent')),
E('th', { 'class': 'th', 'style': 'width: 120px;' }, _('Rule ID')),
E('th', { 'class': 'th' }, _('Description')),
E('th', { 'class': 'th', 'style': 'width: 150px;' }, _('Source'))
])
];
var self = this;
alerts.forEach(function(alert) {
var levelInfo = api.formatLevel(alert.rule_level || 0);
rows.push(E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, [
E('span', { 'class': 'badge ' + levelInfo.class }, String(alert.rule_level || 0))
]),
E('td', { 'class': 'td', 'style': 'font-size: 0.85em;' },
api.formatTime(alert.timestamp)
),
E('td', { 'class': 'td' }, alert.agent_name || '-'),
E('td', { 'class': 'td' }, [
E('code', {}, alert.rule_id || '-')
]),
E('td', { 'class': 'td' }, alert.rule_description || '-'),
E('td', { 'class': 'td', 'style': 'font-family: monospace; font-size: 0.85em;' },
alert.src_ip || alert.data_srcip || '-'
)
]));
});
return E('table', { 'class': 'table' }, rows);
},
handleFilterChange: function() {
this.handleRefresh();
},
handleRefresh: function() {
var levelEl = document.getElementById('level-filter');
var countEl = document.getElementById('count-filter');
var level = levelEl ? parseInt(levelEl.value) : 0;
var count = countEl ? parseInt(countEl.value) : 50;
var container = document.getElementById('alerts-container');
if (container) {
container.innerHTML = '<div style="text-align: center; padding: 2rem;">Loading...</div>';
}
var self = this;
return api.getAlerts(count, level).then(function(data) {
var alerts = data.alerts || [];
if (container) {
dom.content(container, self.renderAlertsTable(alerts));
}
});
},
pollAlerts: function() {
return this.handleRefresh();
}
});

View File

@ -0,0 +1,197 @@
'use strict';
'require view';
'require dom';
'require poll';
'require wazuh.api as api';
return view.extend({
handleSaveApply: null,
handleSave: null,
handleReset: null,
load: function() {
return Promise.all([
api.getFIMEvents(50),
api.getFIMConfig()
]);
},
render: function(data) {
var events = data[0].events || [];
var config = data[1] || {};
var view = E('div', { 'class': 'cbi-map' }, [
E('h2', {}, _('File Integrity Monitoring')),
E('div', { 'class': 'cbi-map-descr' },
_('Track changes to critical files and directories')
),
// FIM Configuration Summary
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Monitored Paths')),
E('div', { 'style': 'display: flex; flex-wrap: wrap; gap: 1rem;' }, [
this.renderConfigCard('Directories', config.directories || [], 'folder'),
this.renderConfigCard('Registry Keys', config.registry || [], 'key'),
this.renderConfigCard('Ignored Paths', config.ignore || [], 'blocked')
])
]),
// Statistics
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Event Statistics')),
E('div', { 'style': 'display: flex; gap: 1rem; flex-wrap: wrap;' }, [
this.renderStatCard('Added', this.countByType(events, 'added'), 'success'),
this.renderStatCard('Modified', this.countByType(events, 'modified'), 'warning'),
this.renderStatCard('Deleted', this.countByType(events, 'deleted'), 'danger'),
this.renderStatCard('Total Events', events.length, 'info')
])
]),
// FIM Events Table
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Recent File Changes')),
E('div', { 'style': 'margin-bottom: 1rem;' }, [
E('button', {
'class': 'btn cbi-button cbi-button-action',
'click': L.bind(this.handleRefresh, this)
}, _('Refresh'))
]),
E('div', { 'id': 'fim-events-container' }, [
this.renderEventsTable(events)
])
]),
// Event Type Legend
E('div', { 'class': 'cbi-section' }, [
E('h4', {}, _('Event Types')),
E('div', { 'style': 'display: flex; gap: 1rem; flex-wrap: wrap;' }, [
E('span', {}, [
E('span', { 'class': 'badge success', 'style': 'margin-right: 0.5rem;' }, '+'),
_('Added')
]),
E('span', {}, [
E('span', { 'class': 'badge warning', 'style': 'margin-right: 0.5rem;' }, '~'),
_('Modified')
]),
E('span', {}, [
E('span', { 'class': 'badge danger', 'style': 'margin-right: 0.5rem;' }, '-'),
_('Deleted')
])
])
])
]);
poll.add(L.bind(this.pollEvents, this), 60);
return view;
},
renderConfigCard: function(title, items, icon) {
var content = items.length > 0
? items.slice(0, 5).map(function(item) {
return E('div', { 'style': 'font-family: monospace; font-size: 0.85em; padding: 0.2rem 0;' },
typeof item === 'string' ? item : item.path || JSON.stringify(item)
);
})
: [E('div', { 'style': 'color: var(--text-color-low);' }, _('None configured'))];
if (items.length > 5) {
content.push(E('div', { 'style': 'color: var(--text-color-low); font-style: italic;' },
_('... and %d more').format(items.length - 5)
));
}
return E('div', {
'style': 'flex: 1; min-width: 250px; background: var(--background-color-high); padding: 1rem; border-radius: 8px;'
}, [
E('div', { 'style': 'font-weight: bold; margin-bottom: 0.5rem;' }, title),
E('div', {}, content)
]);
},
renderStatCard: function(label, count, badgeClass) {
return E('div', {
'style': 'text-align: center; padding: 1rem; background: var(--background-color-high); border-radius: 8px; min-width: 100px;'
}, [
E('div', { 'style': 'font-size: 2em; font-weight: bold;' }, String(count)),
E('div', { 'class': 'badge ' + badgeClass }, label)
]);
},
countByType: function(events, type) {
return events.filter(function(e) { return e.event_type === type; }).length;
},
renderEventsTable: function(events) {
if (!events || events.length === 0) {
return E('div', { 'class': 'cbi-value', 'style': 'text-align: center; padding: 2rem;' },
_('No file integrity events found')
);
}
var rows = [
E('tr', { 'class': 'tr' }, [
E('th', { 'class': 'th', 'style': 'width: 60px;' }, _('Type')),
E('th', { 'class': 'th', 'style': 'width: 150px;' }, _('Time')),
E('th', { 'class': 'th' }, _('Path')),
E('th', { 'class': 'th', 'style': 'width: 100px;' }, _('Mode')),
E('th', { 'class': 'th', 'style': 'width: 100px;' }, _('User')),
E('th', { 'class': 'th', 'style': 'width: 120px;' }, _('Size'))
])
];
events.forEach(function(event) {
var typeClass = event.event_type === 'added' ? 'success' :
(event.event_type === 'deleted' ? 'danger' : 'warning');
var typeSymbol = event.event_type === 'added' ? '+' :
(event.event_type === 'deleted' ? '-' : '~');
rows.push(E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, [
E('span', { 'class': 'badge ' + typeClass }, typeSymbol)
]),
E('td', { 'class': 'td', 'style': 'font-size: 0.85em;' },
api.formatTime(event.timestamp)
),
E('td', { 'class': 'td', 'style': 'font-family: monospace; word-break: break-all;' },
event.path || '-'
),
E('td', { 'class': 'td', 'style': 'font-family: monospace;' },
event.perm || event.mode || '-'
),
E('td', { 'class': 'td' }, event.uname || event.user || '-'),
E('td', { 'class': 'td', 'style': 'font-family: monospace;' },
event.size ? this.formatSize(event.size) : '-'
)
]));
}, this);
return E('table', { 'class': 'table' }, rows);
},
formatSize: function(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
if (bytes < 1024 * 1024 * 1024) return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
return (bytes / (1024 * 1024 * 1024)).toFixed(1) + ' GB';
},
handleRefresh: function() {
var container = document.getElementById('fim-events-container');
if (container) {
container.innerHTML = '<div style="text-align: center; padding: 2rem;">Loading...</div>';
}
var self = this;
return api.getFIMEvents(50).then(function(data) {
var events = data.events || [];
if (container) {
dom.content(container, self.renderEventsTable(events));
}
});
},
pollEvents: function() {
return this.handleRefresh();
}
});

View File

@ -0,0 +1,199 @@
'use strict';
'require view';
'require dom';
'require poll';
'require wazuh.api as api';
return view.extend({
handleSaveApply: null,
handleSave: null,
handleReset: null,
load: function() {
return Promise.all([
api.getOverview(),
api.getAlertSummary(),
api.getCrowdSecCorrelation()
]);
},
render: function(data) {
var overview = data[0] || {};
var alerts = data[1] || {};
var crowdsec = data[2] || {};
var view = E('div', { 'class': 'cbi-map' }, [
E('h2', {}, _('Wazuh SIEM Dashboard')),
E('div', { 'class': 'cbi-map-descr' },
_('Security Information and Event Management powered by Wazuh XDR')
),
// Status Cards Row
E('div', { 'class': 'cbi-section', 'style': 'display: flex; flex-wrap: wrap; gap: 1rem;' }, [
// Agent Status Card
this.renderStatusCard(
'Agent Status',
overview.agent ? (overview.agent.connected ? 'Connected' : (overview.agent.running ? 'Running' : 'Stopped')) : 'Unknown',
overview.agent && overview.agent.connected ? 'success' : (overview.agent && overview.agent.running ? 'warning' : 'danger'),
'Local security agent monitoring this device'
),
// Manager Status Card
this.renderStatusCard(
'Manager Status',
overview.manager ? (overview.manager.running ? 'Running' : 'Stopped') : 'Unknown',
overview.manager && overview.manager.running ? 'success' : 'danger',
'Central SIEM manager in LXC container'
),
// Indexer Status Card
this.renderStatusCard(
'Indexer Health',
overview.manager ? (overview.manager.indexer_status || 'Unknown') : 'Unknown',
overview.manager && overview.manager.indexer_status === 'green' ? 'success' :
(overview.manager && overview.manager.indexer_status === 'yellow' ? 'warning' : 'danger'),
'OpenSearch cluster for alert storage'
),
// CrowdSec Integration Card
this.renderStatusCard(
'CrowdSec Integration',
crowdsec.crowdsec_running ? 'Active' : 'Inactive',
crowdsec.crowdsec_running ? 'success' : 'notice',
crowdsec.active_decisions + ' active ban decisions'
)
]),
// Alert Summary Section
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Alert Summary')),
E('div', { 'style': 'display: flex; flex-wrap: wrap; gap: 1rem;' }, [
this.renderAlertBadge('Critical', alerts.critical || 0, 'danger'),
this.renderAlertBadge('High', alerts.high || 0, 'warning'),
this.renderAlertBadge('Medium', alerts.medium || 0, 'notice'),
this.renderAlertBadge('Low', alerts.low || 0, 'info'),
this.renderAlertBadge('Total', alerts.total || 0, 'secondary')
])
]),
// Quick Actions Section
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Quick Actions')),
E('div', { 'style': 'display: flex; gap: 1rem; flex-wrap: wrap;' }, [
E('a', {
'href': 'https://wazuh.gk2.secubox.in',
'target': '_blank',
'class': 'btn cbi-button cbi-button-action'
}, _('Open Wazuh Dashboard')),
E('button', {
'class': 'btn cbi-button cbi-button-apply',
'click': L.bind(this.handleRestartAgent, this)
}, _('Restart Agent')),
E('a', {
'href': L.url('admin/services/wazuh/alerts'),
'class': 'btn cbi-button'
}, _('View Alerts')),
E('a', {
'href': L.url('admin/services/wazuh/fim'),
'class': 'btn cbi-button'
}, _('File Integrity'))
])
]),
// Security Layers Info
E('div', { 'class': 'cbi-section' }, [
E('h3', {}, _('Security Layers (SysWarden-Inspired)')),
E('table', { 'class': 'table' }, [
E('tr', { 'class': 'tr' }, [
E('th', { 'class': 'th' }, _('Layer')),
E('th', { 'class': 'th' }, _('Component')),
E('th', { 'class': 'th' }, _('Function')),
E('th', { 'class': 'th' }, _('Status'))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, _('Layer 1: Firewall')),
E('td', { 'class': 'td' }, _('Vortex Firewall + nftables')),
E('td', { 'class': 'td' }, _('Kernel-level IP blocking')),
E('td', { 'class': 'td' }, E('span', { 'class': 'badge success' }, _('Active')))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, _('Layer 2: IPS')),
E('td', { 'class': 'td' }, _('CrowdSec + Bouncer')),
E('td', { 'class': 'td' }, _('Behavior-based threat detection')),
E('td', { 'class': 'td' }, E('span', {
'class': 'badge ' + (crowdsec.crowdsec_running ? 'success' : 'danger')
}, crowdsec.crowdsec_running ? _('Active') : _('Inactive')))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, _('Layer 3: SIEM/XDR')),
E('td', { 'class': 'td' }, _('Wazuh Manager')),
E('td', { 'class': 'td' }, _('Log analysis, FIM, threat correlation')),
E('td', { 'class': 'td' }, E('span', {
'class': 'badge ' + (overview.manager && overview.manager.running ? 'success' : 'danger')
}, overview.manager && overview.manager.running ? _('Active') : _('Inactive')))
]),
E('tr', { 'class': 'tr' }, [
E('td', { 'class': 'td' }, _('Layer 4: WAF')),
E('td', { 'class': 'td' }, _('mitmproxy + HAProxy')),
E('td', { 'class': 'td' }, _('Web application firewall')),
E('td', { 'class': 'td' }, E('span', { 'class': 'badge success' }, _('Active')))
])
])
])
]);
// Setup polling for real-time updates
poll.add(L.bind(this.pollStatus, this), 30);
return view;
},
renderStatusCard: function(title, status, statusClass, description) {
return E('div', {
'class': 'cbi-value',
'style': 'flex: 1; min-width: 200px; background: var(--background-color-high); padding: 1rem; border-radius: 8px; border-left: 4px solid var(--' + statusClass + '-color, #666);'
}, [
E('div', { 'style': 'font-weight: bold; margin-bottom: 0.5rem;' }, title),
E('div', {
'class': 'badge ' + statusClass,
'style': 'font-size: 1.2em; padding: 0.3rem 0.6rem;'
}, status),
E('div', { 'style': 'font-size: 0.85em; color: var(--text-color-low); margin-top: 0.5rem;' }, description)
]);
},
renderAlertBadge: function(label, count, badgeClass) {
return E('div', {
'style': 'text-align: center; padding: 0.5rem 1rem; background: var(--background-color-high); border-radius: 8px; min-width: 80px;'
}, [
E('div', { 'style': 'font-size: 1.5em; font-weight: bold;' }, String(count)),
E('div', { 'class': 'badge ' + badgeClass }, label)
]);
},
handleRestartAgent: function() {
var self = this;
return api.restartAgent().then(function(res) {
if (res.success) {
L.ui.addNotification(null, E('p', _('Wazuh agent restarted successfully')), 'info');
} else {
L.ui.addNotification(null, E('p', _('Failed to restart agent')), 'error');
}
return self.load().then(L.bind(self.render, self));
});
},
pollStatus: function() {
return api.getOverview().then(L.bind(function(overview) {
// Update status indicators
var agentBadge = document.querySelector('.cbi-section .badge');
if (agentBadge && overview.agent) {
agentBadge.textContent = overview.agent.connected ? 'Connected' :
(overview.agent.running ? 'Running' : 'Stopped');
}
}, this));
}
});

View File

@ -0,0 +1,165 @@
'use strict';
'require rpc';
var callWazuh = rpc.declare({
object: 'luci.wazuh',
method: 'get_overview',
expect: {}
});
var callAgentStatus = rpc.declare({
object: 'luci.wazuh',
method: 'get_agent_status',
expect: {}
});
var callManagerStatus = rpc.declare({
object: 'luci.wazuh',
method: 'get_manager_status',
expect: {}
});
var callGetAlerts = rpc.declare({
object: 'luci.wazuh',
method: 'get_alerts',
params: ['count', 'level'],
expect: {}
});
var callAlertSummary = rpc.declare({
object: 'luci.wazuh',
method: 'get_alert_summary',
expect: {}
});
var callFIMEvents = rpc.declare({
object: 'luci.wazuh',
method: 'get_fim_events',
params: ['count'],
expect: {}
});
var callFIMConfig = rpc.declare({
object: 'luci.wazuh',
method: 'get_fim_config',
expect: {}
});
var callListAgents = rpc.declare({
object: 'luci.wazuh',
method: 'list_agents',
expect: {}
});
var callCrowdSecCorrelation = rpc.declare({
object: 'luci.wazuh',
method: 'get_crowdsec_correlation',
expect: {}
});
var callStartAgent = rpc.declare({
object: 'luci.wazuh',
method: 'start_agent',
expect: {}
});
var callStopAgent = rpc.declare({
object: 'luci.wazuh',
method: 'stop_agent',
expect: {}
});
var callRestartAgent = rpc.declare({
object: 'luci.wazuh',
method: 'restart_agent',
expect: {}
});
return L.Class.extend({
getOverview: function() {
return callWazuh().then(function(res) {
return res.result || res;
});
},
getAgentStatus: function() {
return callAgentStatus().then(function(res) {
return res.result || res;
});
},
getManagerStatus: function() {
return callManagerStatus().then(function(res) {
return res.result || res;
});
},
getAlerts: function(count, level) {
return callGetAlerts(count || 20, level || 0).then(function(res) {
return res.result || res;
});
},
getAlertSummary: function() {
return callAlertSummary().then(function(res) {
return res.result || res;
});
},
getFIMEvents: function(count) {
return callFIMEvents(count || 50).then(function(res) {
return res.result || res;
});
},
getFIMConfig: function() {
return callFIMConfig().then(function(res) {
return res.result || res;
});
},
listAgents: function() {
return callListAgents().then(function(res) {
return res.result || res;
});
},
getCrowdSecCorrelation: function() {
return callCrowdSecCorrelation().then(function(res) {
return res.result || res;
});
},
startAgent: function() {
return callStartAgent().then(function(res) {
return res.result || res;
});
},
stopAgent: function() {
return callStopAgent().then(function(res) {
return res.result || res;
});
},
restartAgent: function() {
return callRestartAgent().then(function(res) {
return res.result || res;
});
},
// Helper to format alert level
formatLevel: function(level) {
if (level >= 12) return { text: 'Critical', class: 'danger' };
if (level >= 9) return { text: 'High', class: 'warning' };
if (level >= 5) return { text: 'Medium', class: 'notice' };
return { text: 'Low', class: 'info' };
},
// Helper to format timestamp
formatTime: function(timestamp) {
if (!timestamp) return '-';
var d = new Date(timestamp);
return d.toLocaleString();
}
});

View File

@ -0,0 +1,387 @@
#!/bin/sh
# SecuBox Wazuh LuCI RPCD Handler
# Provides API for Wazuh dashboard
WAZUH_DIR="/var/ossec"
WAZUH_MANAGER_CONTAINER="wazuh"
WAZUH_AGENT_CONTAINER="wazuh-agent"
MANAGER_IP="192.168.255.50"
# ============================================
# CrowdSec Integration
# ============================================
get_crowdsec_correlation() {
local crowdsec_running="false"
local decisions=0
local wazuh_parser="false"
if pgrep crowdsec >/dev/null 2>&1; then
crowdsec_running="true"
decisions=$(cscli decisions list -o json 2>/dev/null | grep -c '"id"' 2>/dev/null || echo 0)
fi
if [ -f "/etc/crowdsec/parsers/s01-parse/wazuh.yaml" ]; then
wazuh_parser="true"
fi
printf '{"crowdsec_running":%s,"active_decisions":%d,"wazuh_parser_enabled":%s}\n' \
"$crowdsec_running" "$decisions" "$wazuh_parser"
}
# ============================================
# Agent Status
# ============================================
get_agent_status() {
local agent_running="false"
local agent_connected="false"
local agent_id=""
local agent_name=""
local manager_ip=""
if lxc-info -n "$WAZUH_AGENT_CONTAINER" -s 2>/dev/null | grep -q RUNNING; then
agent_running="true"
if lxc-attach -n "$WAZUH_AGENT_CONTAINER" -- pgrep -f wazuh-agentd >/dev/null 2>&1; then
agent_connected="true"
local client_keys=$(lxc-attach -n "$WAZUH_AGENT_CONTAINER" -- cat "$WAZUH_DIR/etc/client.keys" 2>/dev/null | head -1)
if [ -n "$client_keys" ]; then
agent_id=$(echo "$client_keys" | awk '{print $1}')
agent_name=$(echo "$client_keys" | awk '{print $2}')
fi
fi
fi
manager_ip=$(uci -q get wazuh.main.manager_ip 2>/dev/null || echo "$MANAGER_IP")
printf '{"running":%s,"connected":%s,"agent_id":"%s","agent_name":"%s","manager_ip":"%s","container":"%s"}\n' \
"$agent_running" "$agent_connected" "$agent_id" "$agent_name" "$manager_ip" "$WAZUH_AGENT_CONTAINER"
}
# ============================================
# Manager Status
# ============================================
get_manager_status() {
local manager_running="false"
local indexer_running="false"
local dashboard_running="false"
local cluster_health="unknown"
local agents_total=0
local agents_active=0
if lxc-info -n "$WAZUH_MANAGER_CONTAINER" -s 2>/dev/null | grep -q RUNNING; then
manager_running="true"
if lxc-attach -n "$WAZUH_MANAGER_CONTAINER" -- netstat -tlnp 2>/dev/null | grep -q ":9200 "; then
indexer_running="true"
cluster_health=$(lxc-attach -n "$WAZUH_MANAGER_CONTAINER" -- curl -sk -u admin:admin "https://127.0.0.1:9200/_cluster/health" 2>/dev/null | grep -o '"status":"[^"]*"' | cut -d'"' -f4 || echo "unknown")
fi
if lxc-attach -n "$WAZUH_MANAGER_CONTAINER" -- netstat -tlnp 2>/dev/null | grep -q ":5601 "; then
dashboard_running="true"
fi
local agent_list=$(lxc-attach -n "$WAZUH_MANAGER_CONTAINER" -- /var/ossec/bin/agent_control -l 2>/dev/null)
agents_total=$(echo "$agent_list" | grep -c "ID:" 2>/dev/null || echo 0)
agents_active=$(echo "$agent_list" | grep -c "Active" 2>/dev/null || echo 0)
fi
printf '{"running":%s,"indexer_running":%s,"dashboard_running":%s,"cluster_health":"%s","agents_total":%d,"agents_active":%d,"dashboard_url":"https://wazuh.gk2.secubox.in","container":"%s"}\n' \
"$manager_running" "$indexer_running" "$dashboard_running" "$cluster_health" "$agents_total" "$agents_active" "$WAZUH_MANAGER_CONTAINER"
}
# ============================================
# Alert Summary
# ============================================
get_alert_summary() {
local critical=0
local high=0
local medium=0
local low=0
local total=0
if lxc-info -n "$WAZUH_MANAGER_CONTAINER" -s 2>/dev/null | grep -q RUNNING; then
# Count alerts by severity using simple grep
local alerts_file="/var/ossec/logs/alerts/alerts.json"
critical=$(lxc-attach -n "$WAZUH_MANAGER_CONTAINER" -- sh -c "tail -n 1000 $alerts_file 2>/dev/null | grep -c '\"level\":1[2-5]' 2>/dev/null" || echo 0)
high=$(lxc-attach -n "$WAZUH_MANAGER_CONTAINER" -- sh -c "tail -n 1000 $alerts_file 2>/dev/null | grep -c '\"level\":[9]' 2>/dev/null" || echo 0)
medium=$(lxc-attach -n "$WAZUH_MANAGER_CONTAINER" -- sh -c "tail -n 1000 $alerts_file 2>/dev/null | grep -c '\"level\":[5-8]' 2>/dev/null" || echo 0)
low=$(lxc-attach -n "$WAZUH_MANAGER_CONTAINER" -- sh -c "tail -n 1000 $alerts_file 2>/dev/null | grep -c '\"level\":[1-4]' 2>/dev/null" || echo 0)
# Ensure values are numeric
critical=${critical:-0}
high=${high:-0}
medium=${medium:-0}
low=${low:-0}
# Strip any non-numeric chars
critical=$(echo "$critical" | tr -cd '0-9')
high=$(echo "$high" | tr -cd '0-9')
medium=$(echo "$medium" | tr -cd '0-9')
low=$(echo "$low" | tr -cd '0-9')
[ -z "$critical" ] && critical=0
[ -z "$high" ] && high=0
[ -z "$medium" ] && medium=0
[ -z "$low" ] && low=0
total=$((critical + high + medium + low))
fi
printf '{"critical":%d,"high":%d,"medium":%d,"low":%d,"total":%d}\n' \
"$critical" "$high" "$medium" "$low" "$total"
}
# ============================================
# Alerts List
# ============================================
get_alerts() {
local count="${1:-20}"
local level="${2:-0}"
printf '{"alerts":['
if lxc-info -n "$WAZUH_MANAGER_CONTAINER" -s 2>/dev/null | grep -q RUNNING; then
local first=1
lxc-attach -n "$WAZUH_MANAGER_CONTAINER" -- tail -n "$count" /var/ossec/logs/alerts/alerts.json 2>/dev/null | while read -r line; do
if [ -n "$line" ]; then
local rule_level=$(echo "$line" | grep -oE '"level":[0-9]+' | head -1 | cut -d':' -f2)
if [ -n "$rule_level" ] && [ "$rule_level" -ge "$level" ] 2>/dev/null; then
local timestamp=$(echo "$line" | grep -oE '"timestamp":"[^"]*"' | head -1 | cut -d'"' -f4)
local rule_id=$(echo "$line" | grep -oE '"id":"[0-9]+"' | head -1 | cut -d'"' -f4)
local rule_desc=$(echo "$line" | grep -oE '"description":"[^"]*"' | head -1 | cut -d'"' -f4 | sed 's/"/\\"/g')
local agent_name=$(echo "$line" | grep -oE '"name":"[^"]*"' | head -1 | cut -d'"' -f4)
local src_ip=$(echo "$line" | grep -oE '"srcip":"[^"]*"' | head -1 | cut -d'"' -f4)
[ $first -eq 0 ] && printf ','
first=0
printf '{"timestamp":"%s","rule_level":%s,"rule_id":"%s","rule_description":"%s","agent_name":"%s","src_ip":"%s"}' \
"$timestamp" "$rule_level" "$rule_id" "$rule_desc" "$agent_name" "$src_ip"
fi
fi
done
fi
printf ']}\n'
}
# ============================================
# FIM Functions
# ============================================
get_fim_events() {
local count="${1:-50}"
printf '{"events":['
if lxc-info -n "$WAZUH_MANAGER_CONTAINER" -s 2>/dev/null | grep -q RUNNING; then
local first=1
lxc-attach -n "$WAZUH_MANAGER_CONTAINER" -- grep -E 'syscheck' /var/ossec/logs/alerts/alerts.json 2>/dev/null | tail -n "$count" | while read -r line; do
if [ -n "$line" ]; then
local timestamp=$(echo "$line" | grep -oE '"timestamp":"[^"]*"' | head -1 | cut -d'"' -f4)
local path=$(echo "$line" | grep -oE '"path":"[^"]*"' | head -1 | cut -d'"' -f4 | sed 's/"/\\"/g')
local event_type=$(echo "$line" | grep -oE '"event":"[^"]*"' | head -1 | cut -d'"' -f4)
[ $first -eq 0 ] && printf ','
first=0
printf '{"timestamp":"%s","path":"%s","event_type":"%s"}' \
"$timestamp" "$path" "$event_type"
fi
done
fi
printf ']}\n'
}
get_fim_config() {
printf '{"directories":["/etc/config","/etc/init.d","/usr/sbin","/usr/libexec/rpcd","/srv/haproxy/config","/etc/passwd","/etc/shadow"]}\n'
}
# ============================================
# Agent Management
# ============================================
list_agents() {
printf '{"agents":['
if lxc-info -n "$WAZUH_MANAGER_CONTAINER" -s 2>/dev/null | grep -q RUNNING; then
local first=1
lxc-attach -n "$WAZUH_MANAGER_CONTAINER" -- /var/ossec/bin/agent_control -l 2>/dev/null | grep "ID:" | while read -r line; do
local id=$(echo "$line" | sed 's/.*ID: \([0-9]*\),.*/\1/')
local name=$(echo "$line" | sed 's/.*Name: \([^,]*\),.*/\1/')
local ip=$(echo "$line" | sed 's/.*IP: \([^,]*\),.*/\1/')
local status=$(echo "$line" | sed 's/.*IP: [^,]*, \(.*\)/\1/' | tr -d ' ')
[ $first -eq 0 ] && printf ','
first=0
printf '{"id":"%s","name":"%s","ip":"%s","status":"%s"}' \
"$id" "$name" "$ip" "$status"
done
fi
printf ']}\n'
}
# ============================================
# Service Control
# ============================================
start_agent() {
local success="false"
if lxc-info -n "$WAZUH_AGENT_CONTAINER" -s 2>/dev/null | grep -q RUNNING; then
lxc-attach -n "$WAZUH_AGENT_CONTAINER" -- /var/ossec/bin/wazuh-control start >/dev/null 2>&1
success="true"
fi
printf '{"success":%s}\n' "$success"
}
stop_agent() {
local success="false"
if lxc-info -n "$WAZUH_AGENT_CONTAINER" -s 2>/dev/null | grep -q RUNNING; then
lxc-attach -n "$WAZUH_AGENT_CONTAINER" -- /var/ossec/bin/wazuh-control stop >/dev/null 2>&1
success="true"
fi
printf '{"success":%s}\n' "$success"
}
restart_agent() {
local success="false"
if lxc-info -n "$WAZUH_AGENT_CONTAINER" -s 2>/dev/null | grep -q RUNNING; then
lxc-attach -n "$WAZUH_AGENT_CONTAINER" -- /var/ossec/bin/wazuh-control restart >/dev/null 2>&1
success="true"
fi
printf '{"success":%s}\n' "$success"
}
# ============================================
# Overview / Dashboard Data
# ============================================
get_overview() {
local agent_running="false"
local agent_connected="false"
local manager_running="false"
local indexer_status="unknown"
local dashboard_accessible="false"
local critical=0
local high=0
local total=0
local crowdsec_integrated="false"
# Agent status
if lxc-info -n "$WAZUH_AGENT_CONTAINER" -s 2>/dev/null | grep -q RUNNING; then
agent_running="true"
if lxc-attach -n "$WAZUH_AGENT_CONTAINER" -- pgrep -f wazuh-agentd >/dev/null 2>&1; then
agent_connected="true"
fi
fi
# Manager status
if lxc-info -n "$WAZUH_MANAGER_CONTAINER" -s 2>/dev/null | grep -q RUNNING; then
manager_running="true"
indexer_status=$(lxc-attach -n "$WAZUH_MANAGER_CONTAINER" -- curl -sk -u admin:admin "https://127.0.0.1:9200/_cluster/health" 2>/dev/null | grep -o '"status":"[^"]*"' | cut -d'"' -f4 || echo "unknown")
[ -z "$indexer_status" ] && indexer_status="unknown"
if lxc-attach -n "$WAZUH_MANAGER_CONTAINER" -- netstat -tlnp 2>/dev/null | grep -q ":5601 "; then
dashboard_accessible="true"
fi
# Alert counts - run directly in container to avoid variable issues
local alerts_file="/var/ossec/logs/alerts/alerts.json"
critical=$(lxc-attach -n "$WAZUH_MANAGER_CONTAINER" -- sh -c "tail -n 500 $alerts_file 2>/dev/null | grep -c '\"level\":1[2-5]' 2>/dev/null" || echo 0)
high=$(lxc-attach -n "$WAZUH_MANAGER_CONTAINER" -- sh -c "tail -n 500 $alerts_file 2>/dev/null | grep -c '\"level\":[9]' 2>/dev/null" || echo 0)
total=$(lxc-attach -n "$WAZUH_MANAGER_CONTAINER" -- sh -c "tail -n 500 $alerts_file 2>/dev/null | wc -l 2>/dev/null" || echo 0)
# Strip non-numeric chars
critical=$(echo "$critical" | tr -cd '0-9')
high=$(echo "$high" | tr -cd '0-9')
total=$(echo "$total" | tr -cd '0-9')
[ -z "$critical" ] && critical=0
[ -z "$high" ] && high=0
[ -z "$total" ] && total=0
fi
# CrowdSec
if pgrep crowdsec >/dev/null 2>&1; then
crowdsec_integrated="true"
fi
printf '{"agent":{"running":%s,"connected":%s},"manager":{"running":%s,"indexer_status":"%s","dashboard_accessible":%s},"alerts":{"critical":%d,"high":%d,"total":%d},"crowdsec_integrated":%s}\n' \
"$agent_running" "$agent_connected" "$manager_running" "$indexer_status" "$dashboard_accessible" "$critical" "$high" "$total" "$crowdsec_integrated"
}
# ============================================
# RPCD Interface
# ============================================
case "$1" in
list)
cat <<'EOF'
{
"get_overview": {},
"get_agent_status": {},
"get_manager_status": {},
"get_alerts": {"count": 20, "level": 0},
"get_alert_summary": {},
"get_fim_events": {"count": 50},
"get_fim_config": {},
"list_agents": {},
"get_crowdsec_correlation": {},
"start_agent": {},
"stop_agent": {},
"restart_agent": {}
}
EOF
;;
call)
case "$2" in
get_overview)
get_overview
;;
get_agent_status)
get_agent_status
;;
get_manager_status)
get_manager_status
;;
get_alerts)
read -r input
count=$(echo "$input" | grep -oE '"count":[0-9]+' | cut -d':' -f2 || echo 20)
level=$(echo "$input" | grep -oE '"level":[0-9]+' | cut -d':' -f2 || echo 0)
[ -z "$count" ] && count=20
[ -z "$level" ] && level=0
get_alerts "$count" "$level"
;;
get_alert_summary)
get_alert_summary
;;
get_fim_events)
read -r input
count=$(echo "$input" | grep -oE '"count":[0-9]+' | cut -d':' -f2 || echo 50)
[ -z "$count" ] && count=50
get_fim_events "$count"
;;
get_fim_config)
get_fim_config
;;
list_agents)
list_agents
;;
get_crowdsec_correlation)
get_crowdsec_correlation
;;
start_agent)
start_agent
;;
stop_agent)
stop_agent
;;
restart_agent)
restart_agent
;;
*)
echo '{"error": "Unknown method"}'
;;
esac
;;
*)
echo '{"error": "Unknown command"}'
;;
esac

View File

@ -0,0 +1,46 @@
{
"admin/services/wazuh": {
"title": "Wazuh SIEM",
"order": 15,
"action": {
"type": "view",
"path": "wazuh/overview"
},
"depends": {
"acl": ["luci-app-wazuh"],
"uci": {"wazuh": true}
}
},
"admin/services/wazuh/overview": {
"title": "Overview",
"order": 1,
"action": {
"type": "view",
"path": "wazuh/overview"
}
},
"admin/services/wazuh/alerts": {
"title": "Alerts",
"order": 2,
"action": {
"type": "view",
"path": "wazuh/alerts"
}
},
"admin/services/wazuh/fim": {
"title": "File Integrity",
"order": 3,
"action": {
"type": "view",
"path": "wazuh/fim"
}
},
"admin/services/wazuh/agents": {
"title": "Agents",
"order": 4,
"action": {
"type": "view",
"path": "wazuh/agents"
}
}
}

View File

@ -0,0 +1,31 @@
{
"luci-app-wazuh": {
"description": "Grant access to Wazuh SIEM dashboard",
"read": {
"ubus": {
"luci.wazuh": [
"get_overview",
"get_agent_status",
"get_manager_status",
"get_alerts",
"get_alert_summary",
"get_fim_events",
"get_fim_config",
"list_agents",
"get_crowdsec_correlation"
]
},
"uci": ["wazuh", "wazuh-manager"]
},
"write": {
"ubus": {
"luci.wazuh": [
"start_agent",
"stop_agent",
"restart_agent"
]
},
"uci": ["wazuh", "wazuh-manager"]
}
}
}