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:
parent
a3f3b2dd8c
commit
b1c34021db
@ -1679,3 +1679,21 @@ git checkout HEAD -- index.html
|
|||||||
- Site now displays medieval/renaissance cinematic presentation
|
- Site now displays medieval/renaissance cinematic presentation
|
||||||
- Title: "Les Seigneurs de La Chambre - Présentation Cinématique"
|
- Title: "Les Seigneurs de La Chambre - Présentation Cinématique"
|
||||||
- Description: "seigneurs de la Chambre" (from UCI config)
|
- 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
|
||||||
|
|||||||
@ -64,6 +64,14 @@ _Last updated: 2026-02-14 (WAF architecture configured)_
|
|||||||
|
|
||||||
### Just Completed (2026-02-14)
|
### 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)
|
- **MetaBlogizer SDLC Content Restoration** — DONE (2026-02-14)
|
||||||
- sdlc.gk2.secubox.in was showing GK2 Hub template instead of original content
|
- sdlc.gk2.secubox.in was showing GK2 Hub template instead of original content
|
||||||
- GK2 Hub generator had overwritten local index.html
|
- GK2 Hub generator had overwritten local index.html
|
||||||
|
|||||||
30
package/secubox/luci-app-wazuh/Makefile
Normal file
30
package/secubox/luci-app-wazuh/Makefile
Normal 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)))
|
||||||
@ -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();
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -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();
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -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();
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -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));
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -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();
|
||||||
|
}
|
||||||
|
});
|
||||||
387
package/secubox/luci-app-wazuh/root/usr/libexec/rpcd/luci.wazuh
Normal file
387
package/secubox/luci-app-wazuh/root/usr/libexec/rpcd/luci.wazuh
Normal 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
|
||||||
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user