Wazuh
The Wazuh sink appends events as JSON lines to a file that the Wazuh agent tails — Wazuh's recommended pattern for custom application logs. Every ECS field is available to the manager's JSON decoder, so writing rules requires no custom decoder work.
Laravel configuration#
SIEM_SINKS=wazuh
SIEM_WAZUH_PATH=/var/log/gatekeeper/events.json
The sink creates the directory (0750) and file (0640) if missing. Make sure the path is:
- writable by your PHP / queue worker user, and
- readable by the
wazuhagent user (add it to your app group, or relax thepermissionsconfig key).
| Key | Env | Default |
|---|---|---|
sinks.wazuh.path |
SIEM_WAZUH_PATH |
storage/logs/siem/events.json |
sinks.wazuh.permissions |
— | 0640 |
Agent configuration#
Add a localfile block to the agent's ossec.conf:
<localfile>
<log_format>json</log_format>
<location>/var/log/gatekeeper/events.json</location>
</localfile>
Restart the agent and verify events flow with php artisan siem:test --sink=wazuh.
Manager rules#
Match on any ECS field. A practical starter ruleset:
<group name="laravel,gatekeeper,">
<!-- Base rule: everything from Gatekeeper -->
<rule id="100100" level="3">
<decoded_as>json</decoded_as>
<field name="event.module">gatekeeper</field>
<description>Gatekeeper: $(event.action) from $(source.ip)</description>
</rule>
<!-- Honeypot hits: high-confidence attack traffic -->
<rule id="100101" level="10">
<if_sid>100100</if_sid>
<field name="event.action">honeypot_route_hit</field>
<description>Gatekeeper honeypot hit from $(source.ip): $(url.path)</description>
<mitre><id>T1595</id></mitre>
</rule>
<!-- Brute force -->
<rule id="100102" level="12">
<if_sid>100100</if_sid>
<field name="event.action">brute_force_detected</field>
<description>Brute force against $(service.name) from $(source.ip)</description>
<mitre><id>T1110</id></mitre>
</rule>
<!-- IDOR / enumeration -->
<rule id="100103" level="10">
<if_sid>100100</if_sid>
<field name="event.action">resource_enumeration_detected</field>
<description>Resource enumeration on $(service.name) from $(source.ip)</description>
<mitre><id>T1119</id></mitre>
</rule>
<!-- Correlation: failed logins followed by a success from the same IP -->
<rule id="100110" level="12" frequency="1" timeframe="300">
<if_sid>100100</if_sid>
<field name="event.action">login_succeeded</field>
<if_matched_sid>100102</if_matched_sid>
<same_field>source.ip</same_field>
<description>Successful login from $(source.ip) shortly after brute force detection</description>
<mitre><id>T1110</id><id>T1078</id></mitre>
</rule>
</group>
The correlation rule at the end is the payoff of application-layer telemetry: "brute force then a successful login from the same IP" is a compromised-account signal that infrastructure logs alone can't produce.
Log rotation#
Rotate with copytruncate so the agent keeps its file handle:
/var/log/gatekeeper/events.json {
daily
rotate 14
compress
missingok
notifempty
copytruncate
}
Alternative: syslog to the Wazuh manager#
If running an agent isn't possible (serverless, containers without sidecars), point the syslog sink at the Wazuh manager's <remote> syslog listener instead.