Skip to content
HoginHogin
Go back

Wazuh: enrolling agents, turning on FIM and rootcheck for Linux and Windows (part 2/6)

9 мин чтения

In part 1 we stood up a single-node Wazuh and got a dashboard with exactly one event source — the manager itself, agent ID 000. Without agents a SIEM is blind: it won’t see a tweak to /etc/sudoers, a new Run key in the Windows registry, or someone rebuilding sshd on a server. Part 2 is about the eyes: we enroll agents on Ubuntu and Windows, turn on File Integrity Monitoring (FIM) via syscheck, wire up rootcheck and SCA against CIS Benchmarks — and look at why out of the box this is usually a noise generator, not a signal.

Table of contents

Open Table of contents

Why an agent, and not syslog

You can get logs to the manager without an agent: wazuh-remoted accepts syslog. But everything interesting in Wazuh lives only on the agent — hashing a file, running a CIS check, or collecting a package inventory means executing code on the host itself. An agent is a single daemon (wazuh-agent, a systemd unit) that collects events locally and keeps a persistent encrypted connection to the manager. It needs two ports, both outbound:

The agent always initiates the connection itself: no inbound ports on the monitored host, which removes half the security objections up front.

Enrollment: register once, keys forever

Before sending events, an agent has to introduce itself — and be accepted. That is enrollment:

Enrollment: register once, then keys

The manager runs wazuh-authd by default, listening on 1515. Turn on password protection:

<!-- /var/ossec/etc/ossec.conf on the manager -->
<auth>
  <use_password>yes</use_password>
  <port>1515</port>
</auth>
echo 'S0meEnr0llPass' > /var/ossec/etc/authd.pass
chmod 640 /var/ossec/etc/authd.pass
systemctl restart wazuh-manager   # Docker from part 1: docker restart wazuh.manager

The password is shared by all agents and is used exactly once — at registration. After that, each side stores a symmetric per-agent key in /var/ossec/etc/client.keys, and all traffic on 1514 rides on that key. Get this straight immediately: the authd password is not “the password for events”, it is the key to the gate.

The manual route is the manage_agents utility on both sides: on the manager you add the agent (Add) and export its key (Extract), on the agent you import it. On Windows, manage_agents.exe in the install directory does the same. The manual route is for when 1515 is blocked by network segmentation or policy forbids auto-enrollment.

And keep the horizon in mind: Wazuh 5.0 rewrites the agent-server protocol from scratch (release notes). Enrollment details may change in 5.x, but the model “register once → key → event stream” will survive it — learn the model, not the buttons.

What you need to connect an Ubuntu server

You need root on the agent, network access to the manager on 1514/1515, and a minute:

# repository
apt-get update && apt-get install -y curl gnupg
curl -s https://packages.wazuh.com/key/GPG-KEY-WAZUH | \
  gpg --dearmor -o /usr/share/keyrings/wazuh.gpg
echo "deb [signed-by=/usr/share/keyrings/wazuh.gpg] https://packages.wazuh.com/4.x/apt/ stable main" \
  > /etc/apt/sources.list.d/wazuh.list
apt-get update

# install and enroll in one go
WAZUH_MANAGER="10.0.0.5" \
WAZUH_REGISTRATION_SERVER="10.0.0.5" \
WAZUH_REGISTRATION_PASSWORD="S0meEnr0llPass" \
apt-get install -y wazuh-agent

systemctl enable --now wazuh-agent

The package’s postinst script picks up the variables: it writes the manager address into /var/ossec/etc/ossec.conf and immediately calls authd for a key. If the agent was already installed unenrolled, the same works manually:

/var/ossec/bin/agent-auth -m 10.0.0.5 -P 'S0meEnr0llPass'
# then check <address> inside <client> → <server> in ossec.conf
systemctl restart wazuh-agent

Windows without clicking

The MSI installs silently and takes the same variables:

msiexec.exe /i wazuh-agent-4.14.7-1.msi /q `
  WAZUH_MANAGER="10.0.0.5" `
  WAZUH_REGISTRATION_SERVER="10.0.0.5" `
  WAZUH_REGISTRATION_PASSWORD="S0meEnr0llPass"
NET START WazuhSvc

The config is C:\Program Files (x86)\ossec-agent\ossec.conf — the same XML as on Linux. The service is called WazuhSvc; restart it with Restart-Service WazuhSvc. From here the platforms barely differ: same sections, different paths.

syscheck: FIM you can actually read

The agent’s main module is syscheck — it is the File Integrity Monitoring. The logic: on first start syscheck walks the configured paths and builds a baseline — hashes (MD5/SHA1/SHA256), size, permissions, owner, modification time. Then three watching modes:

syscheck: from file change to alert

A practical block for an Ubuntu agent — critical paths, realtime, diffs in alerts, and whodata on the most important bits:

<syscheck>
  <disabled>no</disabled>
  <frequency>43200</frequency>
  <scan_on_start>yes</scan_on_start>

  <!-- critical paths: realtime + the diff right in the alert -->
  <directories check_all="yes" realtime="yes" report_changes="yes">/etc,/root,/bin,/sbin</directories>
  <directories check_all="yes" realtime="yes" report_changes="yes">/usr/bin,/usr/sbin</directories>

  <!-- the crown jewels: also "who changed it" via auditd -->
  <directories check_all="yes" whodata="yes" report_changes="yes">/etc/ssh,/etc/sudoers.d</directories>

  <!-- permanent noise goes away -->
  <ignore>/etc/mtab</ignore>
  <ignore type="sregex">^/etc/apparmor.d/cache</ignore>

  <!-- secret contents must not leak into alerts -->
  <nodiff>/etc/shadow,/etc/gshadow</nodiff>
</syscheck>

Element by element:

On Windows, directories are joined by the registry — the persistence classic:

<windows_registry check_all="yes" realtime="yes" report_changes="yes" whodata="yes">HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run</windows_registry>
<windows_registry check_all="yes" realtime="yes" report_changes="yes">HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services</windows_registry>

A modified file maps to rule 550, a deleted one to 551, a new one to 553; the registry has its own rule line. All of it lands in dashboard → Modules → Integrity monitoring — with the diff and the before/after.

rootcheck and SCA: different questions

rootcheck is an old but cheap detector of system anomalies: hidden processes and files (a ps output that disagrees with /proc), odd entries under /dev, rootkit and trojan signatures from the shipped lists. It will not outsmart modern tooling, but it costs pennies and on Linux agents just sits in the config:

<rootcheck>
  <disabled>no</disabled>
</rootcheck>

SCA (Security Configuration Assessment) answers a different question: “does this host comply with a formal standard?”. Policies are YAML files with checks from CIS Benchmarks: kernel parameters, file permissions, services, the registry on Windows. The output is pass/fail per check. This is not intrusion detection — it is configuration drift control: a server that “suddenly” no longer matches its baseline is an event in itself.

For an Ubuntu agent, wire up the ready-made CIS policy:

<sca>
  <enabled>yes</enabled>
  <scan_on_start>yes</scan_on_start>
  <interval>12h</interval>
  <policies>
    <policy>cis_ubuntu22.04.yml</policy>
  </policies>
</sca>

Policy files live in /var/ossec/ruleset/sca/ on the agent. Match the policy to the distro: cis_ubuntu22.04 on Debian yields a stream of false fails before you break anything yourself.

Agent groups: config from one point

Past three agents, editing ossec.conf on each host is hopeless. Groups fix it: the manager keeps a shared config per group and ships it to the members.

/var/ossec/bin/agent_groups -a -g prod-linux -q        # create the group
/var/ossec/bin/agent_groups -a -d 002 -g prod-linux -q # add an agent (ID from agent_control -ls)
/var/ossec/bin/agent_groups -l -g prod-linux           # check membership

The group config is /var/ossec/etc/shared/prod-linux/agent.conf:

<agent_config>
  <syscheck>
    <directories check_all="yes" realtime="yes" report_changes="yes">/etc,/root,/bin,/sbin,/usr/bin,/usr/sbin</directories>
    <ignore>/etc/mtab</ignore>
    <nodiff>/etc/shadow,/etc/gshadow</nodiff>
  </syscheck>
</agent_config>

Sections from agent.conf take priority over the agent’s local ossec.conf — keep one module configured in one place, or in a month you won’t untangle whose directive won.

Noise versus signal

The very same syscheck produces opposite results depending on the paths:

Noise versus signal

DimensionDefault syscheckTuned FIM
Pathsa broad “just in case” setonly critical directories
Modemostly periodic scansrealtime, whodata on the critical
Who changedunknownuser and executing process
Diffnonereport_changes + nodiff for secrets
SCAwhatever it foundCIS policy for your specific distro
In alertsa stream of benign eventsa handful of real-change signals

The usual mistakes that get FIM switched off within a week:

How to verify it works

On the manager (in part 1’s Docker setup — via docker exec -ti wazuh.manager):

/var/ossec/bin/agent_control -ls

The agent must show Active. Then a FIM smoke test — on the agent:

echo '# fim smoke test' | sudo tee /etc/ssh/sshd_config.d/99-fim-test.conf

Within seconds the event arrives: tail -f /var/ossec/logs/alerts/alerts.json | grep fim-test shows an alert with rule 553, and dashboard → Modules → Integrity monitoring shows the entry with its diff. SCA results live in Endpoints Summary → agent → SCA — the CIS policy score. Remove the test file, wait for rule 551 — the loop is closed.

Bottom line

Agents are the eyes of Wazuh, and connecting them takes minutes: a repository, env variables, a key. The real work is not “enabling FIM” but carving the noise out of it: short path lists, ignore entries for the eternal churners, nodiff for secrets, whodata on the handful of directories that truly matter, SCA matched to your distro. An hour of tuning separates an endless stream of alerts about benign file changes from the few events you actually want to react to. Next in the series: custom rules and decoders — what to do when stock 550 and 553 say “a file changed” but what you need is “someone got root”.


Share this post:

Next Post
Wazuh from scratch: standing up an open-source SIEM/XDR in one evening (part 1/6)