diff --git a/docs/sources/vendor/Cisco/cisco_meraki.md b/docs/sources/vendor/Cisco/cisco_meraki.md
index 3a37bf9701..f34f843461 100644
--- a/docs/sources/vendor/Cisco/cisco_meraki.md
+++ b/docs/sources/vendor/Cisco/cisco_meraki.md
@@ -1,29 +1,49 @@
-## Meraki (MR, MS, MX, MV)
+## Meraki (MR, MS, MX)
## Key facts
+* In most cases, Cisco Meraki logs are general and require vendor product by source configuration.
+* For distinctive log messages, filters are based on the appliance name and program value.
-* MSG Format based filter (Partial)
-* Requires vendor product by source configuration
-* None conformant legacy BSD Format default port 514
+## Distinctive log messages
+See samples in the [vendor documentation](https://documentation.meraki.com/General_Administration/Monitoring_and_Reporting/Syslog_Event_Types_and_Log_Samples).
+
+The two conjuncted conditions are required:
+
+1. Program: `(events|urls|firewall|cellular_firewall|vpn_firewall|ids-alerts|flows)`
+
+2. Appliance name:
+
+| Sourcetype | Distinct element |
+| --------- | -------------- |
+| meraki:accesspoints | `host('MR' type(string) flags(ignore-case,prefix))` |
+| meraki:securityappliances | `host('MX' type(string) flags(ignore-case,prefix))` |
+| meraki:switches | `host('MS' type(string) flags(ignore-case,prefix))` |
+
## Links
| Ref | Link |
|----------------|---------------------------------------------------------------------------------------------------------|
-| Splunk Add-on | |
-| Product Manual | |
+| Splunk Add-on | |
+| Product Manual | |
## Sourcetypes
| sourcetype | notes |
|----------------|---------------------------------------------------------------------------------------------------------|
-| meraki | None |
+| meraki:accesspoints | MR |
+| meraki:securityappliances | MX |
+| meraki:switches | MS |
+| meraki | vendor product by source configuration |
## Sourcetype and Index Configuration
| key | sourcetype | index | notes |
|----------------|----------------|----------------|----------------|
-| cisco_meraki | meraki | netfw | The current TA does not sub sourcetype or utilize source preventing segmentation into more appropriate indexes |
+| cisco_meraki_accesspoints | meraki:accesspoints | netfw | Filtered on the message format |
+| cisco_meraki_securityappliances | meraki:securityappliances | netfw | Filtered on the message format |
+| cisco_meraki_switches | meraki:switches | netfw | Filtered on the message format |
+| cisco_meraki | meraki | netfw | Filtered on vendor product by source configuration |
## Parser Configuration
diff --git a/package/etc/conf.d/conflib/almost-syslog/app-almost-syslog-cisco_meraki.conf b/package/etc/conf.d/conflib/almost-syslog/app-almost-syslog-cisco_meraki.conf
new file mode 100644
index 0000000000..69ee78f9e8
--- /dev/null
+++ b/package/etc/conf.d/conflib/almost-syslog/app-almost-syslog-cisco_meraki.conf
@@ -0,0 +1,28 @@
+rewrite set_rfc3164_cisco_meraki{
+ set-tag("wireformat:rfc3164_cisco_meraki");
+};
+
+block parser app-almost-syslog-cisco_meraki() {
+ channel {
+ parser {
+ regexp-parser(
+ prefix(".tmp.")
+ patterns('^(?\<\d+\>) ?(?[A-Z][a-z]{2} *\d{1,2} \d\d:\d\d:\d\d) (?\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) (?\d) (?\d{10}\.\d{9}) (?.*)')
+ );
+ date-parser(
+ format('%s.%f', '%s')
+ template("${.tmp.ts2}")
+ );
+ syslog-parser(
+ flags(assume-utf8, guess-timezone)
+ template("${.tmp.pri} $S_ISODATE ${.tmp.message}")
+ );
+ };
+ rewrite(set_rfc);
+ rewrite(set_rfc3164);
+ rewrite(set_rfc3164_cisco_meraki);
+ };
+};
+application app-almost-syslog-cisco_meraki[sc4s-almost-syslog] {
+ parser { app-almost-syslog-cisco_meraki(); };
+};
diff --git a/package/etc/conf.d/conflib/syslog/app-syslog-cisco_meraki.conf b/package/etc/conf.d/conflib/syslog/app-syslog-cisco_meraki.conf
new file mode 100644
index 0000000000..73e1d7dff6
--- /dev/null
+++ b/package/etc/conf.d/conflib/syslog/app-syslog-cisco_meraki.conf
@@ -0,0 +1,45 @@
+block parser app-syslog-cisco_meraki() {
+ channel {
+
+ rewrite {
+ set("securityappliances", value(".tmp.device") condition( host('MX' type(string) flags(ignore-case,prefix))));
+ set("switches", value(".tmp.device") condition( host('MS' type(string) flags(ignore-case,prefix))));
+ set("accesspoints", value(".tmp.device") condition( host('MR' type(string) flags(ignore-case,prefix))));
+ };
+
+ rewrite {
+ r_set_splunk_dest_default(
+ index('netfw')
+ source('cisco:meraki:${.tmp.device}')
+ sourcetype('meraki:${.tmp.device}')
+ vendor("cisco")
+ product("meraki")
+ class("${.tmp.device}")
+ );
+ };
+ };
+};
+
+application app-syslog-cisco_meraki[sc4s-syslog] {
+ filter {
+ (
+ (
+ host('MX' type(string) flags(ignore-case,prefix))
+ or host('MS' type(string) flags(ignore-case,prefix))
+ or host('MR' type(string) flags(ignore-case,prefix))
+ )
+ )
+ and (
+ (
+ program('events' type(string))
+ or program('urls' type(string))
+ or program('firewall' type(string))
+ or program('cellular_firewall' type(string))
+ or program('vpn_firewall' type(string))
+ or program('ids-alerts' type(string))
+ or program('flows' type(string))
+ )
+ )
+ };
+ parser { app-syslog-cisco_meraki() };
+};
\ No newline at end of file
diff --git a/tests/test_cisco_meraki.py b/tests/test_cisco_meraki.py
index c7f73f3c1a..3481da9f5c 100644
--- a/tests/test_cisco_meraki.py
+++ b/tests/test_cisco_meraki.py
@@ -11,10 +11,145 @@
from .splunkutils import *
from .timeutils import *
+import pytest
+
env = Environment()
+# Log samples from https://documentation.meraki.com/General_Administration/Monitoring_and_Reporting/Syslog_Event_Types_and_Log_Samples
+mx_test_data = [
+ # MX events: vpn connectivity change
+ {
+ "template": "{{ mark }} {{ epoch }} {{ host }} events type=vpn_connectivity_change vpn_type='site-to-site' peer_contact='1.1.1.1:51856' peer_ident='XXXXX' connectivity='false'",
+ "host_prefix": "MX",
+ "sourcetype": "meraki:securityappliances"
+ },
+ # urls: HTTP GET requests
+ {
+ "template": "{{ mark }} {{ epoch }} {{ host }} urls src=1.1.1.1:63735 dst=1.1.1.1:80 mac=XX:XX:XX:XX:XX:XX request: GET https://...",
+ "host_prefix": "MX",
+ "sourcetype": "meraki:securityappliances"
+ },
+ # MX flows
+ {
+ "template": "{{ mark }} {{ epoch }} {{ host }} flows src=1.1.1.186 dst=8.8.8.8 mac=XX:XX:XX:XX:XX:XX protocol=udp sport=55719 dport=53 pattern: allow all",
+ "host_prefix": "MX",
+ "sourcetype": "meraki:securityappliances"
+ },
+ # MX firewall
+ {
+ "template": "{{ mark }} {{ epoch }} {{ host }} firewall src=1.1.1.186 dst=8.8.8.8 mac=XX:XX:XX:XX:XX:XX protocol=udp sport=55719 dport=53 pattern: allow all",
+ "host_prefix": "MX",
+ "sourcetype": "meraki:securityappliances"
+ },
+ # MX ids-alerts: ids signature matched
+ {
+ "template": "{{ mark }} {{ epoch }} {{ host }} ids-alerts signature=129:4:1 priority=3 timestamp=1377449842.512569 direction=ingress protocol=tcp/ip src=1.1.1.1:80",
+ "host_prefix": "MX",
+ "sourcetype": "meraki:securityappliances"
+ }
+]
+
+ms_test_data = [
+ # MS events: port status change
+ {
+ "template": "{{ mark }} {{ epoch }} {{ host }} events port 3 status changed from 100fdx to down",
+ "host_prefix": "MS",
+ "sourcetype": "meraki:switches"
+ },
+ # MS events: blocked DHCP server response
+ {
+ "template": "{{ mark }} {{ epoch }} {{ host }} events Blocked DHCP server response from XX:XX:XX:XX:XX:XX on VLAN 100",
+ "host_prefix": "MS",
+ "sourcetype": "meraki:switches"
+ }
+]
+
+mr_test_data = [
+ # MR events: 802.11 association
+ {
+ "template": "{{ mark }} {{ epoch }} {{ host }} events type=association radio='0' vap='1' channel='6' rssi='23' aid='XXXXXX'",
+ "host_prefix": "MR",
+ "sourcetype": "meraki:accesspoints"
+ },
+ # MR events: WPA authentication
+ {
+ "template": "{{ mark }} {{ epoch }} {{ host }} events type=wpa_auth radio='0' vap='1' aid='XXXXXXX'",
+ "host_prefix": "MR",
+ "sourcetype": "meraki:accesspoints"
+ },
+ # MR events: splash authentication
+ {
+ "template": "{{ mark }} {{ epoch }} {{ host }} events type=splash_auth ip='1.1.1.1 [More Information] ' duration='3600' vap='2' download='5242880bps' upload='5242880bps'",
+ "host_prefix": "MR",
+ "sourcetype": "meraki:accesspoints"
+ },
+ # MR flows: flow denied by Layer 3 firewall
+ {
+ "template": "{{ mark }} {{ epoch }} {{ host }} flows deny src=1.1.1.1 dst=1.1.1.1 mac=XX:XX:XX:XX:XX:XX protocol=tcp sport=52421 dport=80",
+ "host_prefix": "MR",
+ "sourcetype": "meraki:accesspoints"
+ }
+]
+
+mx_almost_syslog_test_data = [
+ # MX events: uplink connectivity change
+ {
+ "template": "{{ mark }} Dec 6 08:46:12 1.1.1.1 1 {{ epoch }} {{ host }} events Cellular connection down",
+ "host_prefix": "MX",
+ "sourcetype": "meraki:securityappliances"
+ },
+ # MX events: dhcp no offers
+ {
+ "template": "{{ mark }} Sep 11 16:12:41 1.1.1.1 1 {{ epoch }} {{ host }} events dhcp no offers for mac XX:XX:XX:XX:XX:XX host = 1.1.1.1",
+ "host_prefix": "MX",
+ "sourcetype": "meraki:securityappliances"
+ },
+ # MX events: dhcp lease
+ {
+ "template": "{{ mark }} Sep 11 16:05:15 1.1.1.1 1 {{ epoch }} {{ host }} events dhcp lease of ip 1.1.1.1 from server mac XX:XX:XX:XX:XX:XX for client mac XX:XX:XX:XX:XX:XX from router 1.1.1.1 on subnet 255.255.255.0 with dns 8.8.8.8, 8.8.4.4",
+ "host_prefix": "MX",
+ "sourcetype": "meraki:securityappliances"
+ }
+]
+
+test_data = mx_test_data + ms_test_data + mr_test_data + mx_almost_syslog_test_data
+
+
+@pytest.mark.parametrize("test_case", test_data)
+def test_cisco_meraki_syslog_app(
+ record_property, setup_wordlist, get_host_key, setup_splunk, setup_sc4s, test_case
+):
+ model_number = random.randint(60, 200)
+ model_suffix = random.choice(["", "C", "CW", "W", "-HW", "W-HW"])
+ host = f'{test_case["host_prefix"]}{model_number}{model_suffix}'
+
+ dt = datetime.datetime.now(datetime.timezone.utc)
+ iso, bsd, time, date, tzoffset, tzname, epoch = time_operations(dt)
+
+ meraki_format_epoch = epoch + "000" # "1691740392.147501" -> "1691740392.147501000"
+
+ mt = env.from_string(test_case["template"] + "\n")
+ message = mt.render(mark="<134>", epoch=meraki_format_epoch, host=host)
+
+ sendsingle(message, setup_sc4s[0], setup_sc4s[1][514])
+
+ epoch = dt.astimezone().strftime("%s.%f")[:-3] # -> "1691740392.147501" -> "1691740392.147"
+ st = env.from_string(
+ 'search index=netfw _time={{ epoch }} sourcetype={{ sourcetype }} host={{ host }}'
+ )
+ search = st.render( epoch=epoch, sourcetype=test_case["sourcetype"], host=host)
+
+ resultCount, eventCount = splunk_single(setup_splunk, search)
+
+ record_property("host", host)
+ record_property("resultCount", resultCount)
+ record_property("message", message)
+
+ assert resultCount == 1
+
+
# <134>1 1563249630.774247467 devicename security_event ids_alerted signature=1:28423:1 priority=1 timestamp=1468531589.810079 dhost=98:5A:EB:E1:81:2F direction=ingress protocol=tcp/ip src=151.101.52.238:80 dst=192.168.128.2:53023 message: EXPLOIT-KIT Multiple exploit kit single digit exe detection
-def test_cisco_meraki_security_event(
+def test_cisco_meraki_vps_app(
record_property, setup_wordlist, setup_splunk, setup_sc4s
):
host = "testcm-{}-{}".format(
@@ -45,7 +180,4 @@ def test_cisco_meraki_security_event(
record_property("resultCount", resultCount)
record_property("message", message)
- assert resultCount == 1
-
-
-# <134>1 Dec 6 08:41:44 192.168.1.1 1 1386337316.207232138 MX84 events Cellular connection up
+ assert resultCount == 1
\ No newline at end of file