Add SIEM
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
# REFERENCE for deploying the SIEM node (wazuh1.bitasylum.org) with the upstream
|
||||
# Wazuh single-node compose. Wazuh is managed on that node, NOT by the fleet
|
||||
# serving stack — so these creds live there, not in the serving repo's runtime.
|
||||
# Copy to upstream/single-node/.env on the SIEM node and CHANGE every password.
|
||||
|
||||
# Wazuh release — pin to current stable (github.com/wazuh/wazuh-docker/releases).
|
||||
WAZUH_VERSION=4.12.0
|
||||
|
||||
# --- credentials (ROTATE — these are public defaults) ----------------------
|
||||
INDEXER_USERNAME=admin
|
||||
INDEXER_PASSWORD=ChangeMe-Indexer-Adm1n!
|
||||
API_USERNAME=wazuh-wui
|
||||
API_PASSWORD=ChangeMe-Api-Passw0rd!
|
||||
DASHBOARD_USERNAME=kibanaserver
|
||||
DASHBOARD_PASSWORD=ChangeMe-Dash-Passw0rd!
|
||||
|
||||
# --- fleet integration (set ON the node / in the manager config) -----------
|
||||
# TCP syslog ingest port the manager listens on (config/fleet-remote-syslog.xml)
|
||||
# and the override publishes. The serving-host audit-collector relays here.
|
||||
WAZUH_SYSLOG_PORT=514
|
||||
# Source the manager accepts syslog from = the SERVING HOST's IP (goes in the
|
||||
# <allowed-ips> of config/fleet-remote-syslog.xml). Confirm the real source in the
|
||||
# manager logs and tighten to /32.
|
||||
WAZUH_ALLOWED_IPS=SET-TO-SERVING-HOST-IP/32
|
||||
@@ -0,0 +1,7 @@
|
||||
# The upstream Wazuh deployment is cloned in, not vendored — never commit it,
|
||||
# its generated TLS certs, or any data/volumes.
|
||||
upstream/
|
||||
*.pem
|
||||
*.key
|
||||
.env
|
||||
data/
|
||||
@@ -0,0 +1,128 @@
|
||||
# Wazuh SIEM — fleet integration
|
||||
|
||||
The fleet's auditd stream is analysed by **Wazuh running on its own node:
|
||||
`wazuh1.bitasylum.org`** (deployed + managed independently — keeping the
|
||||
memory-hungry indexer off the serving host, which runs SPDK = the fleet's root
|
||||
disk / a SPOF; see TODO #6/#7). This directory is the **fleet ↔ Wazuh
|
||||
integration**, not a deployment of Wazuh.
|
||||
|
||||
**Ingestion is agentless** (no agent in the diskless image — that would carry
|
||||
per-host registration state, breaking invariant #1). The host side is unchanged;
|
||||
the serving-host collector relays a copy to the SIEM node:
|
||||
|
||||
```
|
||||
host: auditd → audisp-syslog (LOCAL6) → rsyslog omfwd ─┐
|
||||
▼ TCP/514
|
||||
serving host: audit-collector (rsyslog) ──relay(omfwd)──► wazuh1.bitasylum.org
|
||||
│ still writes durable per-host files (manager :514 syslog
|
||||
└ disk-assisted queue (store-and-forward) → indexer → dashboard)
|
||||
```
|
||||
|
||||
The collector keeps its durable file copy + disk queue and **relays a copy**, so a
|
||||
SIEM outage never drops audit records or blocks the file writes.
|
||||
|
||||
## To turn it on (two ends)
|
||||
|
||||
**1. On the SIEM node (`wazuh1`):** add the syslog reception block — insert
|
||||
[config/fleet-remote-syslog.xml](config/fleet-remote-syslog.xml) into the
|
||||
manager's `ossec.conf`, set `<allowed-ips>` to the **serving host's IP**, and make
|
||||
sure the manager's TCP syslog port is reachable (the upstream single-node compose +
|
||||
[docker-compose.override.yml](docker-compose.override.yml) publish `514/tcp`).
|
||||
Restart the manager.
|
||||
|
||||
**2. On the serving host:** the relay is already wired in
|
||||
[../audit-collector/rsyslog.conf](../audit-collector/rsyslog.conf), targeting
|
||||
`wazuh1.bitasylum.org:514`. Apply it by rebuilding the collector image (the conf
|
||||
is COPY'd in at build):
|
||||
|
||||
```bash
|
||||
cd serving && docker compose build audit && docker compose up -d audit
|
||||
# the serving host must RESOLVE + ROUTE to wazuh1.bitasylum.org
|
||||
```
|
||||
|
||||
## Decode the fleet's auditd (custom decoder + rules)
|
||||
|
||||
The fleet ships auditd **agentlessly** via audisp-syslog, and STIG sets
|
||||
`name_format = hostname` (keep it — compliance), which prepends `node=<host>` to
|
||||
each record. Wazuh's built-in auditd decoder expects the line to start with
|
||||
`type=`, so it decodes **nothing** (`"decoder":{}`) — `rule.groups: audit` stays
|
||||
empty. Install the fleet decoder + rules **on the master** so the records decode + alert.
|
||||
|
||||
**Declarative (recommended) — bind-mount via compose**, using the image's
|
||||
`/wazuh-config-mount` mechanism (entrypoint copies the tree into `/var/ossec` at
|
||||
startup; correct ownership, survives `--force-recreate`, no `docker cp`). Add to
|
||||
the `wazuh.master` service (see [docker-compose.override.yml](docker-compose.override.yml)) —
|
||||
put the files next to your compose / `wazuh_manager.conf`:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ./config/local_decoder.xml:/wazuh-config-mount/etc/decoders/local_decoder.xml
|
||||
- ./config/local_rules.xml:/wazuh-config-mount/etc/rules/local_rules.xml
|
||||
```
|
||||
```bash
|
||||
docker compose up -d --force-recreate wazuh.master
|
||||
docker exec <master> grep -iE 'error|rules|decoder' /var/ossec/logs/ossec.log | tail # no errors
|
||||
# verify decoding interactively — paste a full_log line (the `Jun 27 ... node=...`):
|
||||
docker exec -it <master> /var/ossec/bin/wazuh-logtest
|
||||
```
|
||||
In a cluster, mount on the **master only** — Wazuh syncs `etc/decoders` + `etc/rules`
|
||||
to the workers. A config change needs a recreate/restart (the copy is at startup).
|
||||
|
||||
> Quick one-off test without editing compose (lost on recreate):
|
||||
> `docker cp config/local_decoder.xml <master>:/var/ossec/etc/decoders/ && docker cp config/local_rules.xml <master>:/var/ossec/etc/rules/ && docker exec <master> /var/ossec/bin/wazuh-control restart`
|
||||
`wazuh-logtest` should show `decoder 'fleet-auditd'` and `audit.type` extracted,
|
||||
firing rule `100100` (+ a higher rule for auth/SELinux types). Then in the
|
||||
dashboard: **`rule.groups: fleet_audit`** populates. (The base rule is **level 0**
|
||||
— captured but not alerted — so the high-volume SYSCALL/PATH stream doesn't flood
|
||||
alerts; only security-relevant types alert. Tune `config/local_rules.xml`.)
|
||||
|
||||
**Full audit trail (not just alerts):** to make *every* record searchable
|
||||
(forensics / ISM retention), enable archive indexing — `<logall_json>yes</logall_json>`
|
||||
on the manager **and** `archives.enabled: true` in the manager's
|
||||
`/etc/filebeat/filebeat.yml`, then add the `wazuh-archives-*` index pattern in the
|
||||
dashboard. High-volume — slim the auditd rules on the host or filter before
|
||||
indexing.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
# on the SIEM node, after a diskless host has booted + generated auditd events:
|
||||
docker compose logs wazuh.manager | grep -i syslog # listener up, source accepted
|
||||
# Wazuh dashboard → Security events: auditd records from the boot-LAN hosts appear,
|
||||
# decoded (type=SYSCALL/USER_LOGIN/...) and rule-tagged.
|
||||
```
|
||||
|
||||
If nothing arrives: the manager logs rejected sources — confirm `<allowed-ips>`
|
||||
matches the serving host's IP **as wazuh1 sees it** (may be its management IP, not
|
||||
`SERVER_IP`), then tighten to a /32.
|
||||
|
||||
## ⚠ Protect the hop — it's now on the wire
|
||||
|
||||
Co-located it was loopback; serving-host → `wazuh1` now crosses the network, and
|
||||
audit records are sensitive (ISM: protect logs in transit). The relay ships
|
||||
**plaintext 514** for first bring-up — only acceptable on a **trusted private
|
||||
path**. Before relying on it, switch to **syslog-over-TLS** (`gtls`, port 6514,
|
||||
the manager's cert + the fleet CA) on both the relay (`omfwd` with the TLS driver)
|
||||
and the manager `<remote>` block. Same CA work as the secure-path / audit-offload
|
||||
TLS TODO.
|
||||
|
||||
## Production checklist
|
||||
|
||||
- **TLS the relay → wazuh1 hop** (above).
|
||||
- **Rotate all Wazuh credentials** on the node (indexer admin, API, dashboard).
|
||||
- **Restrict the dashboard/API** (`:443`/`:55000`) to admin hosts / VPN — never the
|
||||
boot LAN.
|
||||
- **`allowed-ips` → /32** of the serving host.
|
||||
- **Retention/archival**: indexer ISM for the ISM-mandated retention + cold archive.
|
||||
- **SSP**: update the "Detect" section + ISM event-logging controls once
|
||||
bench-verified, and record the retention design.
|
||||
|
||||
## Files here
|
||||
|
||||
| File | Where it's used |
|
||||
|---|---|
|
||||
| `config/fleet-remote-syslog.xml` | the `<remote>` block to add to the **manager** on wazuh1 |
|
||||
| `docker-compose.override.yml` | reference overlay if wazuh1 uses the upstream single-node compose |
|
||||
| `.env.example` | reference env for deploying the wazuh1 node |
|
||||
|
||||
(The actual fleet-side relay lives in `../audit-collector/rsyslog.conf`.)
|
||||
@@ -0,0 +1,27 @@
|
||||
<!--
|
||||
Fleet syslog reception for the Wazuh manager — apply this ON THE SIEM NODE
|
||||
(wazuh1.bitasylum.org), inside the <ossec_config> of the manager's ossec.conf
|
||||
(upstream single-node: config/wazuh_cluster/wazuh_manager.conf), then restart
|
||||
the manager.
|
||||
|
||||
It makes the manager accept the auditd stream RELAYED by the fleet's
|
||||
serving-host audit-collector over TCP/514. The source the manager sees is the
|
||||
SERVING HOST's IP (the collector is host-networked there), so set <allowed-ips>
|
||||
to that address — confirm the exact source in `docker compose logs wazuh.manager`
|
||||
(rejected sources are logged) and tighten to a /32.
|
||||
|
||||
The diskless hosts stay AGENTLESS (no per-host state, invariant #1); the secure
|
||||
agent listener (1514) + enrollment (1515) are left as the manager default for
|
||||
any future enrolment-registered ephemeral agents.
|
||||
|
||||
⚠ PLAINTEXT — pair with a TRUSTED serving→SIEM path, or switch to TLS (see
|
||||
README): <protocol>tcp</protocol> on 6514 with the manager's cert + the fleet CA.
|
||||
-->
|
||||
<remote>
|
||||
<connection>syslog</connection>
|
||||
<port>514</port>
|
||||
<protocol>tcp</protocol>
|
||||
<allowed-ips>SET-TO-SERVING-HOST-IP/32</allowed-ips>
|
||||
<!-- e.g. <allowed-ips>192.168.50.10/32</allowed-ips> (the serving host as seen
|
||||
by wazuh1 — may be its management IP, not SERVER_IP, if a different route) -->
|
||||
</remote>
|
||||
@@ -0,0 +1,45 @@
|
||||
<!--
|
||||
Fleet auditd-over-syslog decoder — install on the SIEM node (wazuh.master) at
|
||||
/var/ossec/etc/decoders/local_decoder.xml, then restart the manager.
|
||||
|
||||
WHY: the fleet ships auditd agentlessly via audisp-syslog. STIG hardening sets
|
||||
auditd `name_format = hostname` (SSG auditd_name_format — keep it, it's
|
||||
compliance), which prepends `node=<host>` to every record. Wazuh's BUILT-IN
|
||||
auditd decoder expects the line to start with `type=`, so the node= prefix made
|
||||
it match nothing (`"decoder":{}`). This decoder handles the node= variant.
|
||||
|
||||
Pre-decoding strips the syslog header + program (`... audispd[709]: `), so the
|
||||
text these decoders see starts at `node=...`.
|
||||
|
||||
Verify with: docker exec -it <master> /var/ossec/bin/wazuh-logtest
|
||||
(paste a full_log line from archives.json; confirm decoder.name = fleet-auditd
|
||||
and audit.type is extracted, then adjust if your build reports the child name).
|
||||
-->
|
||||
<decoder name="fleet-auditd">
|
||||
<program_name>audispd</program_name>
|
||||
</decoder>
|
||||
|
||||
<decoder name="fleet-auditd">
|
||||
<parent>fleet-auditd</parent>
|
||||
<regex type="pcre2">^node=(\S+) type=(\w+) msg=audit\(([\d.]+):(\d+)\):</regex>
|
||||
<order>audit.node,audit.type,audit.timestamp,audit.serial</order>
|
||||
</decoder>
|
||||
|
||||
<!-- best-effort common fields (present depending on record type) -->
|
||||
<decoder name="fleet-auditd">
|
||||
<parent>fleet-auditd</parent>
|
||||
<regex type="pcre2"> auid=(\d+)</regex>
|
||||
<order>audit.auid</order>
|
||||
</decoder>
|
||||
|
||||
<decoder name="fleet-auditd">
|
||||
<parent>fleet-auditd</parent>
|
||||
<regex type="pcre2"> (?:res|success)=(\S+)</regex>
|
||||
<order>audit.res</order>
|
||||
</decoder>
|
||||
|
||||
<decoder name="fleet-auditd">
|
||||
<parent>fleet-auditd</parent>
|
||||
<regex type="pcre2"> exe="([^"]+)"</regex>
|
||||
<order>audit.exe</order>
|
||||
</decoder>
|
||||
@@ -0,0 +1,51 @@
|
||||
<!--
|
||||
Fleet auditd rules — install on the SIEM node (wazuh.master) at
|
||||
/var/ossec/etc/rules/local_rules.xml, then restart the manager. Pairs with
|
||||
local_decoder.xml.
|
||||
|
||||
Design: the BASE rule is level 0 — every decoded fleet auditd record is captured
|
||||
(searchable, archived) but does NOT raise an alert, so the high-volume
|
||||
SYSCALL/PATH/PROCTITLE/EOE stream (e.g. Chromium) doesn't flood alerts.json.
|
||||
Security-relevant record TYPES are bumped to alerting levels below. Tune to your
|
||||
detection needs. To see the FULL audit trail (not just alerts) in the dashboard,
|
||||
enable archive indexing (wazuh-archives-*) — see README.
|
||||
-->
|
||||
<group name="fleet_audit,">
|
||||
|
||||
<!-- base: any decoded fleet auditd record (level 0 = logged, not alerted) -->
|
||||
<rule id="100100" level="0">
|
||||
<decoded_as>fleet-auditd</decoded_as>
|
||||
<description>Fleet auditd: $(audit.type) on $(audit.node)</description>
|
||||
</rule>
|
||||
|
||||
<!-- authentication / credential events -->
|
||||
<rule id="100110" level="5">
|
||||
<if_sid>100100</if_sid>
|
||||
<field name="audit.type">^USER_LOGIN$|^USER_AUTH$|^USER_ACCT$|^USER_ERR$|^CRED_</field>
|
||||
<description>Fleet audit: authentication event $(audit.type) on $(audit.node)</description>
|
||||
<group>authentication,</group>
|
||||
</rule>
|
||||
|
||||
<!-- failed auth (res/success = failed/no/0) -> higher -->
|
||||
<rule id="100111" level="8">
|
||||
<if_sid>100110</if_sid>
|
||||
<field name="audit.res">^failed$|^no$|^0$</field>
|
||||
<description>Fleet audit: FAILED authentication $(audit.type) on $(audit.node)</description>
|
||||
<group>authentication_failed,</group>
|
||||
</rule>
|
||||
|
||||
<!-- SELinux denials / kernel anomaly records -->
|
||||
<rule id="100120" level="7">
|
||||
<if_sid>100100</if_sid>
|
||||
<field name="audit.type">^AVC$|^SELINUX_ERR$|^ANOM_|^USER_AVC$</field>
|
||||
<description>Fleet audit: SELinux/anomaly $(audit.type) on $(audit.node)</description>
|
||||
</rule>
|
||||
|
||||
<!-- privilege use / config changes worth surfacing -->
|
||||
<rule id="100130" level="5">
|
||||
<if_sid>100100</if_sid>
|
||||
<field name="audit.type">^USER_CMD$|^USYS_CONFIG$|^ADD_USER$|^DEL_USER$|^ADD_GROUP$|^ROLE_</field>
|
||||
<description>Fleet audit: privileged/config event $(audit.type) on $(audit.node)</description>
|
||||
</rule>
|
||||
|
||||
</group>
|
||||
@@ -0,0 +1,32 @@
|
||||
# REFERENCE (applied ON the SIEM node, wazuh1.bitasylum.org, if you deploy it with
|
||||
# the upstream Wazuh single-node compose). NOT used by the fleet serving stack —
|
||||
# Wazuh runs on its own node, managed independently. The only fleet-specific bit
|
||||
# is publishing the manager's TCP syslog listener so the serving-host
|
||||
# audit-collector relay can reach it:
|
||||
#
|
||||
# cd upstream/single-node
|
||||
# docker compose -f docker-compose.yml -f /path/to/docker-compose.override.yml up -d
|
||||
#
|
||||
# On a dedicated node the indexer gets full resources — no co-location memory caps.
|
||||
#
|
||||
# ⚠ SERVICE NAME depends on the upstream deployment you used:
|
||||
# - single-node compose -> service is `wazuh.manager`
|
||||
# - multi-node compose -> service is `wazuh.master` (the syslog receiver)
|
||||
# Match your deployment. The upstream publishes 514/UDP only — the fleet relay is
|
||||
# TCP, so the ONE required change is adding 514/tcp on whichever node runs the
|
||||
# syslog `<remote>` block. (Recreate the container after a port change.)
|
||||
services:
|
||||
wazuh.master: # rename to wazuh.manager for a single-node deployment
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "514:514/tcp" # TCP syslog ingest for the fleet audit relay
|
||||
volumes:
|
||||
# Declarative custom decoder + rules via the image's /wazuh-config-mount
|
||||
# mechanism (entrypoint copies the tree into /var/ossec at startup — correct
|
||||
# ownership, survives --force-recreate, no docker cp). In a cluster, put them
|
||||
# on the master only; Wazuh syncs etc/decoders + etc/rules to the workers.
|
||||
# NOTE: relative bind paths resolve to the COMPOSE PROJECT DIR, so keep these
|
||||
# files alongside the compose you run (or use an absolute path). A config
|
||||
# change needs a recreate/restart (the copy happens at startup).
|
||||
- ./config/local_decoder.xml:/wazuh-config-mount/etc/decoders/local_decoder.xml
|
||||
- ./config/local_rules.xml:/wazuh-config-mount/etc/rules/local_rules.xml
|
||||
Reference in New Issue
Block a user