Skip to content

Commit 89276ef

Browse files
Hasitha9796claude
andauthored
Add Wazuh Case Management plugin integration (#94)
* Add Wazuh Case Management plugin integration Adds the wazuh_case_management OpenSearch Dashboards plugin, which provides native incident-response and case-management capabilities within the Wazuh Dashboard — including alert linking, observable/IOC tracking, Kanban board, activity timeline, analytics dashboard, and webhook notifications. Compatible with Wazuh 4.14.5 / OpenSearch Dashboards 2.19.5. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Simplify Integration Testing section in README Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Fix build-from-source steps to use wazuh-dashboard repo Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add pre-built plugin zip and update install instructions Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Address PR #94 review: remove committed secrets/binary/debug route, harden webhook - Removed server/routes/debug.ts and its registration in server/routes/index.ts: an unauthenticated (validate: false) endpoint that echoed request bodies and wrote/deleted OpenSearch documents. - Removed the committed wazuhCaseManagement-2.19.5.zip prebuilt binary; README now documents build-from-source only, so the installed artifact can be verified against the source that produced it. - server/routes/webhooks.ts: replaced the wide-open schema.object({}, {unknowns:'allow'}) with an explicit schema for the fields the handler actually reads (id, rule.id, agent.id, ...), and added an optional shared-secret check (WAZUH_CASE_MANAGEMENT_WEBHOOK_SECRET env var + X-Wazuh-Webhook-Secret header, compared with timingSafeEqual) since the endpoint had no application-level auth of its own. - README: corrected the "configurable outbound webhooks" claim — only an inbound alert-to-case webhook exists — and documented the new shared secret configuration. Note on scope: this branch has also been rebuilt on top of the current main so it no longer carries the unrelated wazuh_decoder_rule_tool/ changes (including a committed .env with lab sudo/SSH credentials) that were bundled into the original PR — those belong to PR #92 and are fully absent from this branch's history, not just reverted in the latest commit. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ce75433 commit 89276ef

54 files changed

Lines changed: 9380 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# Wazuh Case Management — Wazuh Integration
2+
3+
## Table of Contents
4+
5+
* [Introduction](#introduction)
6+
* [Prerequisites](#prerequisites)
7+
* [Installation and Configuration](#installation-and-configuration)
8+
* [Installing the Plugin](#installing-the-plugin)
9+
* [Initial Wazuh Configuration](#initial-wazuh-configuration)
10+
* [Using the Integration Files](#using-the-integration-files)
11+
* [Integration Steps](#integration-steps)
12+
* [Integration Testing](#integration-testing)
13+
* [Sources](#sources)
14+
15+
---
16+
17+
### Introduction
18+
19+
The **Wazuh Case Management** plugin is an OpenSearch Dashboards plugin that adds a full incident-response and case-management workflow natively inside the Wazuh Dashboard. Security analysts can create cases, link Wazuh alerts to them, track observables (IPs, hashes, URLs) and flag indicators of compromise (IOCs), manage work through a Kanban board, and measure response performance via an analytics dashboard.
20+
21+
Key capabilities:
22+
23+
- **Case lifecycle management** — create, assign, prioritize, and close security incident cases.
24+
- **Alert linking** — search and attach existing Wazuh alerts directly to a case.
25+
- **Observables & IOC tracking** — document artefacts and flag them as IOCs.
26+
- **Kanban board** — drag-and-drop workflow visualization across Open / In-Progress / Resolved / Closed columns.
27+
- **Activity timeline** — full audit trail of every case change with timestamps and actor.
28+
- **Analytics dashboard** — MTTR, case-load, severity breakdowns, and SLA insights.
29+
- **Automated case creation from alerts** — an inbound webhook endpoint accepts Wazuh alerts (e.g. from the Manager's integrator) and automatically opens or updates a case.
30+
31+
---
32+
33+
### Prerequisites
34+
35+
- **Wazuh** 4.14.5 or later (Wazuh Manager + Wazuh Dashboard).
36+
- **OpenSearch Dashboards** 2.19.5 (bundled with Wazuh Dashboard 4.14.5).
37+
- **Node.js** v18.x and **Yarn** 1.x (for building from source).
38+
- Network connectivity between the Wazuh Dashboard host and the OpenSearch cluster.
39+
- A user account with permissions to create and manage OpenSearch indices (`wazuh-case-management-*`).
40+
41+
---
42+
43+
### Installation and Configuration
44+
45+
#### Installing the Plugin
46+
47+
The plugin is distributed as source only — build the installable zip yourself so
48+
you can verify exactly what gets installed on the Dashboard host (a pre-built
49+
binary artifact is not committed to this repository for that reason).
50+
51+
```bash
52+
# Prerequisites: Node 18, Yarn
53+
git clone https://github.com/wazuh/wazuh-dashboard.git
54+
cd wazuh-dashboard
55+
git checkout 4.14.5
56+
57+
# Copy the plugin into the plugins directory
58+
cp -r /path/to/wazuh_case_management plugins/wazuh-case-management
59+
60+
# Bootstrap dependencies
61+
yarn osd bootstrap
62+
63+
# Build the plugin
64+
cd plugins/wazuh-case-management
65+
yarn build
66+
67+
# Install the generated zip
68+
sudo -u wazuh-dashboard /usr/share/wazuh-dashboard/bin/opensearch-dashboards-plugin \
69+
install file://$(pwd)/build/wazuh-case-management-*.zip
70+
71+
sudo systemctl restart wazuh-dashboard
72+
```
73+
74+
#### Initial Wazuh Configuration
75+
76+
No changes to the Wazuh Manager configuration are required. The plugin communicates directly with the underlying OpenSearch cluster using the Dashboard's built-in HTTP client. It will automatically create two indices on first startup:
77+
78+
| Index | Purpose |
79+
|---|---|
80+
| `wazuh-case-management-cases` | Stores all case data |
81+
| `wazuh-case-management-counters` | Atomic counter for sequential case IDs |
82+
83+
Ensure the OpenSearch user configured in `opensearch_dashboards.yml` has at minimum the following privileges on the `wazuh-case-management-*` index pattern:
84+
85+
```
86+
indices_allow: ["read", "write", "create_index", "delete", "manage"]
87+
```
88+
89+
#### Configuring the Alert Webhook
90+
91+
The plugin exposes an inbound endpoint, `POST /api/wazuh-case-management/webhook/alert`, that
92+
the Wazuh Manager (via an [integrator](https://documentation.wazuh.com/current/user-manual/manager/manual-integration.html)
93+
or custom script) can call to automatically open or update a case from an alert.
94+
95+
Set the `WAZUH_CASE_MANAGEMENT_WEBHOOK_SECRET` environment variable on the Dashboard process
96+
to a random shared secret, then have the caller send it back on every request as the
97+
`X-Wazuh-Webhook-Secret` header — requests without a matching header are rejected with `403`.
98+
If the variable is left unset, the endpoint accepts unauthenticated requests (logged as a
99+
warning on startup); this is only appropriate when the endpoint is not reachable from anything
100+
other than the trusted alert source.
101+
102+
There is currently no outbound webhook support (e.g. pushing case events to an external
103+
SOAR/ticketing platform) — only this inbound, alert-to-case direction exists.
104+
105+
#### Using the Integration Files
106+
107+
The plugin source is organized as a standard OpenSearch Dashboards plugin:
108+
109+
```
110+
wazuh_case_management/
111+
├── common/ # Shared types, constants, and index definitions
112+
├── public/ # React frontend (components, pages, styles, services)
113+
│ ├── components/ # Reusable UI components (KanbanBoard, Timeline, IOC section …)
114+
│ └── pages/ # Full page views (CaseList, CaseDetail, Dashboard, Monitor …)
115+
├── server/ # Node.js backend routes and OpenSearch services
116+
│ ├── routes/ # REST API handlers (cases, alerts, comments, webhooks …)
117+
│ └── services/ # OpenSearch query helpers
118+
├── opensearch_dashboards.json
119+
├── package.json
120+
└── tsconfig.json
121+
```
122+
123+
---
124+
125+
### Integration Steps
126+
127+
1. **Install the plugin** using one of the methods above.
128+
2. **Restart** the Wazuh Dashboard service.
129+
3. **Open** the Wazuh Dashboard and navigate to the **Case Management** application from the left-hand navigation or the Wazuh app selector.
130+
4. **Create a case** — click *Create Case*, fill in the title, description, assignee, severity, and priority.
131+
5. **Link alerts** — from the case detail page, click *Link Alert* and search for existing Wazuh alerts by rule ID, agent, or keyword.
132+
6. **Add observables** — document relevant artefacts (IPs, hashes, domains, URLs) and mark IOCs as needed.
133+
7. **Track work** — use the Kanban board to move cases across workflow stages.
134+
8. **Close the case** — set the status to *Resolved* or *Closed*. The plugin automatically calculates MTTR for analytics.
135+
9. *(Optional)* Wire up the Wazuh Manager's [integrator](https://documentation.wazuh.com/current/user-manual/manager/manual-integration.html) to call the alert webhook so matching alerts automatically open or update a case — see [Configuring the Alert Webhook](#installing-the-plugin).
136+
137+
---
138+
139+
### Integration Testing
140+
141+
After installation, navigate to `https://<dashboard-host>/app/wazuh-case-management` in a browser and confirm the Case Management application loads. Create a test case, link a Wazuh alert, and verify it appears in the case detail page.
142+
143+
---
144+
145+
### Sources
146+
147+
- [Wazuh Documentation](https://documentation.wazuh.com)
148+
- [OpenSearch Dashboards Plugin Development Guide](https://opensearch.org/docs/latest/dashboards/extension-points/)
149+
- [Wazuh GitHub Repository](https://github.com/wazuh/wazuh)
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*
2+
* Wazuh Case Management Plugin
3+
* Shared constants between server and client
4+
*/
5+
6+
// ─── Plugin Identification ────────────────────────────────────
7+
export const PLUGIN_ID = 'wazuhCaseManagement';
8+
export const PLUGIN_NAME = 'Case Management';
9+
export const PLUGIN_DESCRIPTION = 'Security incident case management for Wazuh';
10+
11+
// ─── OpenSearch Index ─────────────────────────────────────────
12+
export const CASE_INDEX = 'wazuh-case-management-cases';
13+
export const CASE_INDEX_PATTERN = 'wazuh-case-management-*';
14+
export const CASE_COUNTER_INDEX = 'wazuh-case-management-counter';
15+
export const MONITOR_CONFIG_INDEX = 'wazuh-case-management-monitor';
16+
17+
// ─── Wazuh Alert Index ────────────────────────────────────────
18+
export const WAZUH_ALERTS_INDEX_PATTERN = 'wazuh-alerts-*';
19+
20+
// ─── API Routes ───────────────────────────────────────────────
21+
export const API_PREFIX = '/api/wazuh-case-management';
22+
23+
export const API_ROUTES = {
24+
// Cases
25+
CASES: `${API_PREFIX}/cases`,
26+
CASE_BY_ID: `${API_PREFIX}/cases/{id}`,
27+
CASE_STATUS: `${API_PREFIX}/cases/{id}/status`,
28+
CASE_ASSIGN: `${API_PREFIX}/cases/{id}/assign`,
29+
30+
// Comments
31+
CASE_COMMENTS: `${API_PREFIX}/cases/{id}/comments`,
32+
CASE_COMMENT_BY_ID: `${API_PREFIX}/cases/{id}/comments/{commentId}`,
33+
34+
// Alerts
35+
CASE_ALERTS: `${API_PREFIX}/cases/{id}/alerts`,
36+
CASE_ALERT_BY_ID: `${API_PREFIX}/cases/{id}/alerts/{alertId}`,
37+
ALERTS_SEARCH: `${API_PREFIX}/alerts/search`,
38+
39+
// Observables
40+
CASE_OBSERVABLES: `${API_PREFIX}/cases/{id}/observables`,
41+
CASE_OBSERVABLE_BY_ID: `${API_PREFIX}/cases/{id}/observables/{observableId}`,
42+
43+
// Analytics
44+
ANALYTICS_SUMMARY: `${API_PREFIX}/analytics/summary`,
45+
ANALYTICS_TRENDS: `${API_PREFIX}/analytics/trends`,
46+
47+
// Settings
48+
SETTINGS: `${API_PREFIX}/settings`,
49+
50+
// Users
51+
ME: `${API_PREFIX}/me`,
52+
USERS: `${API_PREFIX}/users`,
53+
54+
// Auto Monitor
55+
MONITOR: `${API_PREFIX}/monitor`,
56+
MONITOR_STATUS: `${API_PREFIX}/monitor/status`,
57+
} as const;
58+
59+
// ─── Case ID Configuration ───────────────────────────────────
60+
export const CASE_ID_PREFIX = 'CASE';
61+
export const CASE_ID_SEPARATOR = '-';
62+
63+
// ─── Status Definitions ──────────────────────────────────────
64+
export const CASE_STATUSES = [
65+
{ value: 'open', label: 'Open', color: '#1D76EE', icon: 'folderOpen' },
66+
{ value: 'in_progress', label: 'In Progress', color: '#4D9FF5', icon: 'playFilled' },
67+
{ value: 'waiting', label: 'Waiting', color: '#F5A623', icon: 'clock' },
68+
{ value: 'resolved', label: 'Resolved', color: '#00BB7A', icon: 'checkInCircleFilled' },
69+
{ value: 'closed', label: 'Closed', color: '#6b7280', icon: 'cross' },
70+
] as const;
71+
72+
// Valid status transitions
73+
export const STATUS_TRANSITIONS: Record<string, string[]> = {
74+
open: ['in_progress', 'waiting', 'closed'],
75+
in_progress: ['waiting', 'resolved', 'closed'],
76+
waiting: ['in_progress', 'resolved', 'closed'],
77+
resolved: ['closed', 'in_progress'], // can reopen
78+
closed: ['open'], // can reopen
79+
};
80+
81+
// ─── Severity Definitions ────────────────────────────────────
82+
export const CASE_SEVERITIES = [
83+
{ value: 'informational', label: 'Informational', color: '#A9AEC4', order: 0 },
84+
{ value: 'low', label: 'Low', color: '#00BB7A', order: 1 },
85+
{ value: 'medium', label: 'Medium', color: '#F5A623', order: 2 },
86+
{ value: 'high', label: 'High', color: '#EE3434', order: 3 },
87+
{ value: 'critical', label: 'Critical', color: '#9333EA', order: 4 },
88+
] as const;
89+
90+
// ─── Priority Definitions ────────────────────────────────────
91+
export const CASE_PRIORITIES = [
92+
{ value: 'P1', label: 'P1 — Urgent', color: '#ef4444', order: 0 },
93+
{ value: 'P2', label: 'P2 — High', color: '#f97316', order: 1 },
94+
{ value: 'P3', label: 'P3 — Medium', color: '#f59e0b', order: 2 },
95+
{ value: 'P4', label: 'P4 — Low', color: '#94a3b8', order: 3 },
96+
] as const;
97+
98+
// ─── Category Definitions ────────────────────────────────────
99+
export const CASE_CATEGORIES = [
100+
{ value: 'malware', label: 'Malware', icon: 'bug' },
101+
{ value: 'intrusion_attempt', label: 'Intrusion Attempt', icon: 'lock' },
102+
{ value: 'data_exfiltration', label: 'Data Exfiltration', icon: 'exportAction' },
103+
{ value: 'policy_violation', label: 'Policy Violation', icon: 'alert' },
104+
{ value: 'vulnerability', label: 'Vulnerability', icon: 'securitySignal' },
105+
{ value: 'phishing', label: 'Phishing', icon: 'email' },
106+
{ value: 'denial_of_service', label: 'Denial of Service', icon: 'offline' },
107+
{ value: 'insider_threat', label: 'Insider Threat', icon: 'user' },
108+
{ value: 'unauthorized_access', label: 'Unauthorized Access', icon: 'crossInACircleFilled' },
109+
{ value: 'other', label: 'Other', icon: 'questionInCircle' },
110+
] as const;
111+
112+
// ─── Observable Type Definitions ─────────────────────────────
113+
export const OBSERVABLE_TYPES = [
114+
{ value: 'ip', label: 'IP Address' },
115+
{ value: 'domain', label: 'Domain' },
116+
{ value: 'url', label: 'URL' },
117+
{ value: 'hash_md5', label: 'Hash (MD5)' },
118+
{ value: 'hash_sha1', label: 'Hash (SHA-1)' },
119+
{ value: 'hash_sha256', label: 'Hash (SHA-256)' },
120+
{ value: 'email', label: 'Email Address' },
121+
{ value: 'filename', label: 'Filename' },
122+
{ value: 'hostname', label: 'Hostname' },
123+
{ value: 'port', label: 'Port' },
124+
{ value: 'registry_key', label: 'Registry Key' },
125+
{ value: 'user_account', label: 'User Account' },
126+
{ value: 'process', label: 'Process' },
127+
{ value: 'other', label: 'Other' },
128+
] as const;
129+
130+
// ─── Pagination Defaults ─────────────────────────────────────
131+
export const DEFAULT_PAGE_SIZE = 20;
132+
export const MAX_PAGE_SIZE = 100;
133+
export const DEFAULT_SORT_FIELD = 'created_at';
134+
export const DEFAULT_SORT_ORDER = 'desc';
135+
136+
// ─── UI Theme Colors ─────────────────────────────────────────
137+
export const THEME = {
138+
background: '#1D1E24',
139+
surface: '#25263A',
140+
surfaceHover: '#2E2F42',
141+
border: '#3D3E5A',
142+
primary: '#1D76EE',
143+
primaryLight: '#4D9FF5',
144+
primaryDark: '#1558B0',
145+
success: '#00BB7A',
146+
warning: '#F5A623',
147+
danger: '#EE3434',
148+
critical: '#9333EA',
149+
info: '#4D9FF5',
150+
textPrimary: '#FFFFFF',
151+
textSecondary: '#A9AEC4',
152+
textMuted: '#6B7280',
153+
} as const;
154+
155+
// ─── TLP Definitions ─────────────────────────────────────────
156+
export const TLP_LEVELS = [
157+
{ value: 'WHITE', label: 'TLP:WHITE', color: '#FFFFFF', bg: 'rgba(255,255,255,0.1)' },
158+
{ value: 'GREEN', label: 'TLP:GREEN', color: '#00BB7A', bg: 'rgba(0,187,122,0.15)' },
159+
{ value: 'AMBER', label: 'TLP:AMBER', color: '#F5A623', bg: 'rgba(245,166,35,0.15)' },
160+
{ value: 'RED', label: 'TLP:RED', color: '#EE3434', bg: 'rgba(238,52,52,0.15)' },
161+
] as const;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* Wazuh Case Management Plugin
3+
* Common module entry point
4+
*/
5+
6+
export * from './types';
7+
export * from './constants';

0 commit comments

Comments
 (0)