Catalyst 3850: send syslog to a SIEM instead of polling SNMP
In short.
logging host <siem> transport udp port 514,logging trap informational,logging source-interface Vlan1, millisecond timestamps, andarchivewithlog configso every configuration line is a syslog message. On the Wazuh side, a remote syslog listener, the stock Cisco IOS decoder, and one local rule for the events you want to be woken up for. Verify by flapping a spare port and watching the alert arrive.
I want my Catalyst switches in the SIEM without SNMP polling
SNMP polling tells you a counter went up, thirty seconds later, if you built the graph. Syslog tells you the port went down, when, and what else happened in the same second. For a two-member stack per site the events that matter are a short list: link state, LACP bundling, logins, and configuration changes. All four are syslog messages already; the switch just has to be told where to send them. The decision to use syslog rather than SNMP polling is in the decision post at the end.
What you need
- A WS-C3850-12X48U stack on IOS-XE 16.12 with a management SVI on VLAN 1.
- A Wazuh manager reachable from that SVI. In the examples it is at
10.1.10.30on the OPS-INT VLAN, which the firewall allows from management. - NTP working on the switch, because an event with the wrong time is worse than no event.
show ntp statusshould say synchronised before you start. - A spare access port on the stack to flap for the test.
flowchart LR
SW["sw-a.site.example<br/>source Vlan1"] -- "UDP 514, informational" --> WZ["Wazuh manager 10.1.10.30<br/>remote syslog listener"]
WZ --> DEC["cisco-ios decoder"]
DEC --> R["Local rules:<br/>LINK, LINEPROTO, EC, SEC_LOGIN, PARSER"]
R --> AL["Alert"]Steps
- Fix the timestamps first, or every message that follows is dated by uptime:
sw-a.site.example# configure terminal
sw-a.site.example(config)# service timestamps log datetime msec localtime show-timezone year
sw-a.site.example(config)# service timestamps debug datetime msec localtime show-timezone year
sw-a.site.example(config)# clock timezone AEST 10 0
sw-a.site.example(config)# ntp server 10.1.10.1
- Point logging at the SIEM, from the management SVI, at informational level. The source interface matters: without it the switch picks whichever interface the route leaves by, and the SIEM's allow-list and hostname mapping break when that changes.
origin-id hostnameputs the FQDN in every message so the SIEM does not have to guess from the source address.
sw-a.site.example(config)# logging host 10.1.10.30 transport udp port 514
sw-a.site.example(config)# logging source-interface Vlan1
sw-a.site.example(config)# logging trap informational
sw-a.site.example(config)# logging origin-id hostname
sw-a.site.example(config)# logging buffered 65536 informational
Informational is the level at which %LINEPROTO-5-UPDOWN and the LACP messages appear. Notifications (level 5) would keep those but lose some useful level 6 messages; debugging (level 7) is noise.
- Log logins, both outcomes:
sw-a.site.example(config)# login on-failure log
sw-a.site.example(config)# login on-success log
- Log every configuration command as a syslog message. This is the
archivefeature; withoutnotify syslogthe log stays on the switch where nobody reads it.hidekeyskeeps passwords typed into config out of the SIEM.
sw-a.site.example(config)# archive
sw-a.site.example(config-archive)# log config
sw-a.site.example(config-archive-log-cfg)# logging enable
sw-a.site.example(config-archive-log-cfg)# logging size 500
sw-a.site.example(config-archive-log-cfg)# notify syslog contenttype plaintext
sw-a.site.example(config-archive-log-cfg)# hidekeys
sw-a.site.example(config-archive-log-cfg)# end
sw-a.site.example# write memory
- On the Wazuh manager, enable the remote syslog listener and restrict it to the management network. This goes in
/var/ossec/etc/ossec.confinside<ossec_config>:
<remote>
<connection>syslog</connection>
<port>514</port>
<protocol>udp</protocol>
<allowed-ips>10.1.1.0/24</allowed-ips>
<local_ip>10.1.10.30</local_ip>
</remote>
Wazuh ships a decoder named cisco-ios that understands the %FACILITY-SEVERITY-MNEMONIC: format, and a base set of Cisco IOS rules. You do not write a decoder; you add rules on top of it.
- Add a local rule for the events you want to see as alerts. One rule per mnemonic group is enough; put them in
/var/ossec/etc/rules/local_rules.xml:
<group name="cisco-ios,local,">
<rule id="100300" level="7">
<decoded_as>cisco-ios</decoded_as>
<match>%LINK-3-UPDOWN|%LINEPROTO-5-UPDOWN</match>
<description>Cisco IOS: port state change</description>
</rule>
<rule id="100301" level="9">
<decoded_as>cisco-ios</decoded_as>
<match>%EC-5-UNBUNDLE|%EC-5-BUNDLE|%EC-5-CANNOT_BUNDLE</match>
<description>Cisco IOS: LACP bundle change</description>
</rule>
<rule id="100302" level="10">
<decoded_as>cisco-ios</decoded_as>
<match>%SEC_LOGIN-4-LOGIN_FAILED</match>
<description>Cisco IOS: login failed</description>
</rule>
<rule id="100303" level="8">
<decoded_as>cisco-ios</decoded_as>
<match>%PARSER-5-CFGLOG_LOGGEDCMD|%SYS-5-CONFIG_I</match>
<description>Cisco IOS: configuration changed</description>
</rule>
</group>
Then restart the manager with systemctl restart wazuh-manager and check /var/ossec/logs/ossec.log for a rule syntax complaint.
Verify it worked
Pick the spare port and flap it from the switch:
sw-a.site.example# configure terminal
sw-a.site.example(config)# interface GigabitEthernet1/0/47
sw-a.site.example(config-if)# shutdown
sw-a.site.example(config-if)# no shutdown
sw-a.site.example(config-if)# end
show logging on the switch should end with the %LINK-5-CHANGED, %LINEPROTO-5-UPDOWN and %PARSER-5-CFGLOG_LOGGEDCMD lines for what you just did, with a millisecond timestamp and the right year. On the manager, the same lines should appear in /var/ossec/logs/alerts/alerts.log within a second or two, tagged with rules 100300 and 100303 and the switch's FQDN as the hostname. If the raw line reaches the manager but no alert fires, paste it into /var/ossec/bin/wazuh-logtest; it shows which decoder matched and which rule, or that none did.
Then log in once with a wrong password from the jumphost and confirm rule 100302 fires. Then stop, because the next thing it fires on should be real.
Gotchas
logging trapsets the level for remote syslog only.logging bufferedandlogging consoleare separate, and the console at level 7 on a 9600 baud serial line will make the break-glass path unusable during a port storm. Keep the console at warnings.- UDP syslog is fire and forget. A manager restart loses the messages sent during it. For a switch that is fine; for the archive log of configuration changes, the switch's own
show archive log config allis the backup record. - The source interface must have a route to the manager and must be allowed through the firewall from VLAN 1 to VLAN 10 on UDP 514. The firewall's own log is the quickest place to see the first packet being dropped.
- Wazuh's
allowed-ipsis per listener. A second site's stack, on10.2.1.0/24, needs its own entry or its own listener, and silently gets nothing until it has one. %SYS-5-CONFIG_Ifires once perconfigure terminalsession, not per line. The per-line detail is%PARSER-5-CFGLOG_LOGGEDCMDfrom the archive feature, which is why step 4 is not optional.
FAQ
Why not SNMP traps instead of polling? Traps are a reasonable middle ground and the 3850 can send them. They need the MIB, a community or SNMPv3 user, and a trap receiver that understands them. Syslog needs none of that and carries the same link and login events as text the SIEM already decodes.
Should I send to two managers?
logging host can be repeated for a second destination and the switch sends to both. If the second site's manager is reachable through the mesh, sending each stack to both managers means a site outage does not take its own logs with it.
What level should the alerts be? Level 7 for port state at a site where nothing should move, level 9 for LACP, and 10 for failed logins, map onto Wazuh's default notification threshold of 10 so that only the login failures page someone. Port flaps go in the daily summary. Tune after a month of seeing what is normal, not before.