capitalize headings
This commit is contained in:
16 files changed
+65
-65
No files matched your search
@@ -30,14 +30,14 @@ ID: 012, Name: file-server, IP: any, Active
|
||||
ID: 013, Name: netbird, IP: any, Active
|
||||
```
|
||||
|
||||
## Why agent-based, and why self-hosted
|
||||
## Why Agent-based, and Why Self-hosted
|
||||
|
||||
The alternative to an agent on every host is a purely network-level monitoring approach — watching traffic at the firewall in [`docs/firewall-and-network.md`](firewall-and-network.md) and inferring what's happening inside each host. That approach can't see file integrity changes, local privilege escalation, or rootkit behavior — the things that matter most once an attacker is already past the network perimeter. An agent on every host closes that gap at the cost of real per-host overhead and a rollout that has to cover 100% of the fleet to be trustworthy — a partial rollout would create a false sense of coverage that's arguably worse than no agent at all. Self-hosting the manager/indexer/dashboard stack, rather than using a SaaS SIEM, keeps every alert's raw data (hostnames, internal paths, credential-adjacent metadata) inside the same trust boundary Vault protects in [`docs/secrets-and-pki.md`](secrets-and-pki.md), instead of shipping it to a third party.
|
||||
|
||||
> [!TIP]
|
||||
> **The manager watches itself, as two independent things sharing one VM.** The manager/indexer/dashboard Docker stack and the native agent monitoring that same host are unrelated — the manager isn't self-aware, it's just also enrolled as agent `000`. And the two halves of "how Wazuh is configured" have different discipline levels on purpose: the compose stack itself is tracked in its own Gitea repo, but the actual detection rules and decoders live in a Docker volume, edited directly through the dashboard or manager API — not version-controlled. If the manager goes down entirely, agents don't lose events: they buffer locally and replay once it's back, so an outage costs visibility, not data.
|
||||
|
||||
## A fleet-wide bug this system caught in itself
|
||||
## A Fleet-wide Bug This System Caught In Itself
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Wazuh's own default agent log-queue buffer (1024 entries) overflowed fleet-wide during Ansible orchestration bursts — enough hosts logging enough events at once that the queue filled before the agent could forward them, silently dropping data rather than erroring loudly. Found, not assumed: root-caused to the default queue size specifically, then fixed with a dedicated tuning role that raises `logcollector.queue_size` on every agent. That role is deliberately narrow — it only touches queue size and enabled/started state; syscheck ignores, rootcheck ignores, and other per-agent tuning are still one-off manual edits, honestly left uncovered rather than implied as handled by a role that doesn't actually reach them. This is the real root-cause comment from the fix itself, not a summary of it:
|
||||
@@ -52,11 +52,11 @@ The alternative to an agent on every host is a purely network-level monitoring a
|
||||
> # 9 of the fleet's agents.
|
||||
> ```
|
||||
|
||||
## How an alert gets handled
|
||||
## How An Alert Gets Handled
|
||||
|
||||

|
||||
|
||||
## The curated "don't re-investigate this" runbook
|
||||
## The Curated "don't re-investigate this" Runbook
|
||||
|
||||
Rather than re-triaging the same recurring alert from scratch every time it fires, routine noise gets root-caused once and recorded — this is the difference between a SOC that actually scales past a handful of hosts and one that drowns in its own alert volume. A representative slice of what's actually in it, real rule IDs and all:
|
||||
|
||||
@@ -72,7 +72,7 @@ Rather than re-triaging the same recurring alert from scratch every time it fire
|
||||
> [!TIP]
|
||||
> **One catch worth calling out in detail**: rule `19011`, "nftables default deny not detected," turned out to be a false positive *in the check itself*, not a real gap. The check greps for a raw nftables `policy drop` string that firewalld — the actual firewall in use, per [`docs/firewall-and-network.md`](firewall-and-network.md) — simply never produces, because firewalld implements default-deny through its own zone/chain structure instead. The fix was verifying the real firewall posture directly against firewalld's own state, not touching the firewall itself. This is a useful pattern in general: a security tool's own check can be wrong about a system it wasn't originally written for, and the fix is understanding the actual system, not chasing the tool's assumption.
|
||||
|
||||
## Querying and testing directly
|
||||
## Querying And Testing Directly
|
||||
|
||||
Alert data lives in a searchable OpenSearch-based index, reachable directly without going through the [Dashy SOC Board](dashy-soc-board.md) — a terms-aggregation on `rule.id`, with sub-aggregations for `rule.description`, max `rule.level`, `agent.name`, and max `timestamp`, returns a fleet-wide "what's actually firing and where" summary in one call — useful when the dashboard's own pre-aggregated view isn't granular enough for a specific question.
|
||||
|
||||
@@ -101,7 +101,7 @@ $ echo 'Aug 28 12:00:00 caddy sshd[12345]: Failed password for invalid user test
|
||||
|
||||
One rule-writing gotcha that's easy to get backwards: a rule's `<decoded_as>` field has to reference the *parent* decoder's name, never a child regex-decoder further down the same chain — `wazuh-logtest` always reports the parent's name in its output regardless of which child actually matched, so debugging by what the tool prints back leads to the wrong name. And `wazuh-logtest` re-reads the rule/decoder files fresh on every invocation, independent of whether the live daemon has actually reloaded them — a rule that tests correctly can still be inert in production until `wazuh-control restart` actually loads it.
|
||||
|
||||
## Compliance hardening: justified exceptions, not blanket enforcement
|
||||
## Compliance Hardening: justified exceptions, not blanket enforcement
|
||||
|
||||

|
||||
|
||||
@@ -111,7 +111,7 @@ This is the same tag-driven selection model described in [`docs/iac-pipeline.md`
|
||||
|
||||
The authentication stack is configured to fail **safe** under a specific failure mode rather than fail open — a subtler design choice than it sounds, and the kind of detail that's easy to get backwards under pressure while writing the config.
|
||||
|
||||
## Automated benchmarking: native, not containerized
|
||||
## Automated Benchmarking: native, not containerized
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The fleet's automated security-benchmark tool (Docker Bench for Security) runs as a **native binary**, not through its own official container image — that image hit a real binary-compatibility wall between two different C library implementations (glibc vs. musl) that made it fail to run reliably. Running it natively instead of debugging a broken official container was the pragmatic call: the tool's job is auditing Docker, not testing container-runtime compatibility edge cases. It runs weekly, deliberately timed against real observed cron load from the other scheduled jobs described in [`docs/cron-scheduler.md`](cron-scheduler.md), rather than an arbitrary default schedule that might collide with heavier jobs.
|
||||
|
||||
Reference in new issue
Block a user