How to check in one minute
1. Is there anything for MikroTik in your ruleset?
grep -ril -e mikrotik -e routeros /var/ossec/ruleset/ /var/ossec/etc/decoders/ /var/ossec/etc/rules/
On a stock 4.14.7 manager this prints nothing.
2. Are the logs arriving? With <logall>yes</logall> on for a few minutes:
grep -e 'login failure for user' -e 'logged in from' -e ' in:' /var/ossec/logs/archives/archives.log | tail -n 3
Lines there but no alerts: this page. No lines: check the <remote> syslog block and <allowed-ips> on the manager first. Turn logall off afterwards.
Why it happens
We searched the full 4.14.7 ruleset (ruleset/decoders and ruleset/rules) for mikrotik and routeros: zero files. A RouterOS line such as
system,error,critical login failure for user admin from 203.0.113.5 via ssh
has no program name in the form Wazuh's pre-decoder expects, and no stock decoder matches its text. Without a decoder there are no fields, and without a rule there is no alert, whatever the level.
Fix
1. Send the right topics from RouterOS
Create a remote logging action pointing at the Wazuh manager, then send the topics you want to it. In RouterOS 7, for example:
/system logging action add name=wazuh target=remote remote=MANAGER_IP remote-port=514
/system logging add topics=account action=wazuh
/system logging add topics=critical action=wazuh
/system logging add topics=firewall action=wazuh
Login failures carry the critical topic, logins and logouts account. Firewall lines are only produced by rules that have log=yes; set a log-prefix if you want to tell rules apart. Parameter names differ between RouterOS releases; check with /system logging action print.
2. Add the decoder
Save as /var/ossec/etc/decoders/mikrotik_decoders.xml:
<!-- MikroTik RouterOS: login events and firewall rules with log=yes. ATK, tested on Wazuh 4.14.7. -->
<decoder name="mikrotik-login">
<prematch type="pcre2">\blogin failure for user \S+ from \S+ via \S+|\buser \S+ logged (?:in|out) from \S+ via \S+</prematch>
</decoder>
<decoder name="mikrotik-login-fields">
<parent>mikrotik-login</parent>
<regex type="pcre2">login (failure) for user (\S+) from (\S+) via (\S+)</regex>
<order>result, dstuser, srcip, protocol</order>
</decoder>
<decoder name="mikrotik-login-fields">
<parent>mikrotik-login</parent>
<regex type="pcre2">user (\S+) logged (in|out) from (\S+) via (\S+)</regex>
<order>dstuser, result, srcip, protocol</order>
</decoder>
<decoder name="mikrotik-firewall">
<prematch type="pcre2">\bin:\S+ out:\S+.*\bproto \S+</prematch>
</decoder>
<decoder name="mikrotik-firewall-fields">
<parent>mikrotik-firewall</parent>
<regex type="pcre2">(\S+): in:(\S+) out:</regex>
<order>chain, in_interface</order>
</decoder>
<decoder name="mikrotik-firewall-fields">
<parent>mikrotik-firewall</parent>
<regex type="pcre2">proto (\w+)(?: \([^)]*\))?, ([0-9a-fA-F.:]+?):(\d+)->([0-9a-fA-F.:]+?):(\d+)</regex>
<order>protocol, srcip, srcport, dstip, dstport</order>
</decoder>
3. Add the rules
Save as /var/ossec/etc/rules/mikrotik_rules.xml. IDs 100300–100310 are in the custom range; change them if they collide with yours.
<group name="mikrotik,">
<rule id="100300" level="0">
<decoded_as>mikrotik-login</decoded_as>
<description>MikroTik: login event.</description>
</rule>
<rule id="100301" level="5">
<if_sid>100300</if_sid>
<field name="result">^failure$</field>
<description>MikroTik: login failure for $(dstuser) from $(srcip) via $(protocol).</description>
<group>authentication_failed,</group>
</rule>
<rule id="100302" level="3">
<if_sid>100300</if_sid>
<field name="result">^in$</field>
<description>MikroTik: $(dstuser) logged in from $(srcip) via $(protocol).</description>
<group>authentication_success,</group>
</rule>
<rule id="100303" level="10" frequency="8" timeframe="120">
<if_matched_sid>100301</if_matched_sid>
<same_source_ip />
<description>MikroTik: multiple login failures from $(srcip).</description>
<group>authentication_failures,</group>
</rule>
<rule id="100310" level="3">
<decoded_as>mikrotik-firewall</decoded_as>
<description>MikroTik: firewall rule with log=yes matched ($(chain)) $(srcip) -> $(dstip):$(dstport).</description>
<group>firewall,</group>
</rule>
</group>
Then /var/ossec/bin/wazuh-analysisd -t and restart the manager.
What we measured
| Line sent (UDP 514, Wazuh 4.14.7) | Result |
|---|---|
login failure for user admin from 203.0.113.5 via ssh | 100301, level 5, srcip, dstuser, protocol read |
| the same, 8 times in a few seconds | 7 × 100301, then 100303 level 10 on the 8th |
user admin logged in from 192.168.88.2 via winbox | 100302, level 3 (logout stays at level 0) |
input: in:ether1 out:(unknown 0), ... proto TCP (SYN), 203.0.113.5:51234->192.168.88.1:22 | 100310, level 3, chain, srcip, dstip, dstport read; a log-prefix before the chain is ignored |
an ordinary sshd failed-password line | untouched: still stock 5760 |
wazuh-analysisd -t: no warnings, no 7617/7619. The same lines prefixed with a BSD timestamp and host name (Oct 2 15:00:01 MikroTik ...) decode the same way.
Limits of what we measured
Measured on a Wazuh 4.14.7 manager container receiving syslog over UDP 514 from 127.0.0.1, and in wazuh-logtest. We did not run a MikroTik router: the log lines were written by us in the RouterOS message format (the logged in form matches the example in MikroTik's documentation). We have not measured the cef remote log format, IPv6 firewall lines, other RouterOS topics (dhcp, wireless, vpn), or other Wazuh versions. The firewall rule tells you a rule with log=yes matched; RouterOS does not put the action in the line, so use log-prefix to carry it.