Skip to content

Commit db4ae93

Browse files
committed
fix: align Cisco Meraki filter and rule contracts
1 parent 6c3af7e commit db4ae93

4 files changed

Lines changed: 320 additions & 85 deletions

File tree

‎filters/audits/cisco-meraki.md‎

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Cisco Meraki normalization and rule review
2+
3+
Avoid fabricated success for observations/pending VPN; preserve rule input fields and handle retrospective AMP events without invented source IP.
4+
5+
This draft targets UTMStack `v11`. It contains 1 filter changes
6+
and 1 rule changes for this technology only. Review covered
7+
1 filter configurations and 7 matching shipped rule files.
8+
Unchanged rules are listed in the regression manifest; they are not duplicated in the diff.
9+
10+
## Contract and validation
11+
12+
- Compared exact standard names/types with go-sdk v1.1.31 and the supplied UTMStack dictionaries.
13+
- Checked documented pipeline ordering, rename/move behavior, open vendor log fields,
14+
event-side versus alert-side fields, and surviving fields used by affected rule predicates/history/grouping.
15+
- Strict SDK configuration decoding and actual CEL compilation pass for this scope.
16+
- 6 synthetic normalization cases pass, including SDK Event conversion and any
17+
trigger predicate assertions recorded in the manifest.
18+
- The scoped alerts module tests and `git diff --check` pass with the shared contract runner applied.
19+
20+
The shared alert-contract PR supplies the reusable Go runner for the manifest in
21+
`plugins/alerts/testdata/filter-contracts/cisco-meraki.json`. Apply that support before running `go test ./...` in `plugins/alerts`.
22+
23+
The changed rules also require the shared alert-grouping fix to resolve `lastEvent.*` values correctly at runtime.
24+
25+
The model starts from synthetic extraction results. It does not run complex grok,
26+
JSON/KV/XML/CSV extraction, time conversion, dynamic plugins, historical OpenSearch
27+
queries, or the closed EventProcessor. Raw vendor logs and resulting alerts must
28+
still be checked in staging before rollout. No customer false-positive reduction
29+
has been measured and no production rollout is included.
30+
31+
Current Meraki security events retain their group/message for the AMP consumer. Retrospective malicious dispositions do not report a source IP, so the rule does not invent or require one; grouping includes dataSource. Source: https://documentation.meraki.com/General_Administration/Monitoring_and_Reporting/Syslog_Event_Types_and_Log_Samples
32+
33+
## References
34+
35+
- [SDK schema](https://github.com/threatwinds/go-sdk/blob/v1.1.31/plugins/plugins.proto)
36+
- [Filter steps](https://github.com/threatwinds/go-sdk/wiki/Filter-Steps-Reference)
37+
- [Standard event schema](https://github.com/threatwinds/go-sdk/wiki/Standard-Event-Schema)
38+
- [Rule implementation](https://github.com/threatwinds/go-sdk/wiki/Implementing-Rules)
39+
40+
`afterEvents`, empty noncapturing grok names, supported numeric strings, and custom
41+
`log.*` fields are accepted. Existing textual protocol casing and vendor action names
42+
are preserved unless a concrete consumer mismatch requires correction.

‎filters/cisco/meraki.yml‎

Lines changed: 146 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,10 @@ pipeline:
103103
where: '!equals("log.controlFlag", "Init")'
104104
# Adding action result
105105
- add:
106-
function: 'string'
106+
function: string
107107
params:
108108
key: actionResult
109-
value: 'accepted'
109+
value: success
110110
where: equals("log.connectivity", "true")
111111
# ........................................
112112
# event uplink connectivity change
@@ -169,10 +169,10 @@ pipeline:
169169
where: '!equals("log.controlFlag", "Init") && contains("action", "Cellular connection")'
170170
# Adding action result
171171
- add:
172-
function: 'string'
172+
function: string
173173
params:
174174
key: actionResult
175-
value: 'accepted'
175+
value: success
176176
where: equalsIgnoreCase("log.connectionState", "up")
177177
# ........................................
178178
# Event: event, dhcp no offers
@@ -236,10 +236,10 @@ pipeline:
236236
where: '!equals("log.controlFlag", "Init") && contains("log.genericEvent", "dhcp lease")'
237237
# Adding action result
238238
- add:
239-
function: 'string'
239+
function: string
240240
params:
241241
key: actionResult
242-
value: 'accepted'
242+
value: success
243243
where: contains("log.genericEvent", "dhcp lease")
244244
# ........................................
245245
# Event: event, HTTP GET requests in Meraki MX Security Appliance
@@ -270,12 +270,6 @@ pipeline:
270270
source: log.genericEvent
271271
where: '!equals("log.controlFlag", "Init") && startsWith("log.genericEvent", "src") && log.merakiGroup=="urls"'
272272
# Adding action result
273-
- add:
274-
function: 'string'
275-
params:
276-
key: actionResult
277-
value: 'accepted'
278-
where: '!equals("log.controlFlag", "Init") && startsWith("log.genericEvent", "src") && log.merakiGroup=="urls"'
279273
# ........................................
280274
# Event: flows, IP session initiated in Meraki MX Security Appliance
281275
# First variant
@@ -342,10 +336,10 @@ pipeline:
342336
where: '!equals("log.controlFlag", "Init") && startsWith("log.genericEvent", "src") && equals("log.merakiGroup", "flows")'
343337
# Adding action result
344338
- add:
345-
function: 'string'
339+
function: string
346340
params:
347341
key: actionResult
348-
value: 'accepted'
342+
value: success
349343
where: '!equals("log.controlFlag", "Init") && startsWith("log.genericEvent", "src") && equals("log.merakiGroup", "flows") && (startsWith("log.pattern", "0") || startsWith("log.pattern", "allow") || startsWith("log.pattern", "Allow"))'
350344
- add:
351345
function: 'string'
@@ -501,31 +495,31 @@ pipeline:
501495
where: '!equals("log.controlFlag", "Init") && startsWith("log.genericEvent", "Site") && equals("log.merakiGroup", "events")'
502496
# Adding action result
503497
- add:
504-
function: 'string'
498+
function: string
505499
params:
506500
key: actionResult
507-
value: 'accepted'
508-
where: '!equals("log.controlFlag", "Init") && startsWith("log.genericEvent", "Site") && equals("log.merakiGroup", "events") && (contains("log.genericEvent", "queued due to no phase 1") || contains("log.genericEvent", "queued due to no phase1") || contains("log.genericEvent", "established"))'
501+
value: success
502+
where: '!equals("log.controlFlag", "Init") && startsWith("log.genericEvent", "Site") && equals("log.merakiGroup", "events") && contains("log.genericEvent", "established")'
509503
# ........................................
510504
# Event: event spanning-tree guard state change in Meraki MS Switches
511505
- grok:
512506
patterns:
513-
- fieldName: log.irrelevant
514-
pattern: 'Port'
515-
- fieldName: origin.port
516-
pattern: '{{.integer}}'
517-
- fieldName: log.irrelevant
518-
pattern: 'received an'
519-
- fieldName: protocol
520-
pattern: '{{.data}}BPDU'
521-
- fieldName: log.irrelevant
522-
pattern: 'from'
523-
- fieldName: origin.mac
524-
pattern: '{{.data}}so'
525-
- fieldName: log.irrelevant
526-
pattern: 'the port was'
527-
- fieldName: actionResult
528-
pattern: '{{.greedy}}'
507+
- fieldName: log.irrelevant
508+
pattern: Port
509+
- fieldName: origin.port
510+
pattern: '{{.integer}}'
511+
- fieldName: log.irrelevant
512+
pattern: received an
513+
- fieldName: protocol
514+
pattern: '{{.data}}BPDU'
515+
- fieldName: log.irrelevant
516+
pattern: from
517+
- fieldName: origin.mac
518+
pattern: '{{.data}}so'
519+
- fieldName: log.irrelevant
520+
pattern: the port was
521+
- fieldName: log.merakiResult
522+
pattern: '{{.greedy}}'
529523
source: log.genericEvent
530524
where: '!equals("log.controlFlag", "Init") && startsWith("log.genericEvent", "Port") && equals("log.merakiGroup", "events")'
531525
- trim:
@@ -853,10 +847,10 @@ pipeline:
853847
where: '!equals("log.controlFlag", "Init") && startsWith("log.genericEvent", "type") && equals("log.merakiGroup", "events") && contains("log.genericEvent", "radio") && contains("log.genericEvent", "channel") && contains("log.genericEvent", "auth_neg_dur") && contains("log.genericEvent", "last_auth_ago")'
854848
# Adding action result
855849
- add:
856-
function: 'string'
850+
function: string
857851
params:
858852
key: actionResult
859-
value: 'accepted'
853+
value: success
860854
where: '!equals("log.controlFlag", "Init") && startsWith("log.genericEvent", "type") && equals("log.merakiGroup", "events") && contains("log.genericEvent", "radio") && contains("log.genericEvent", "channel") && contains("log.genericEvent", "auth_neg_dur") && contains("log.genericEvent", "last_auth_ago")'
861855
# ........................................
862856
# Event: event 802.1X (all events) in Meraki MR Access Points
@@ -1008,10 +1002,10 @@ pipeline:
10081002
where: '!equals("log.controlFlag", "Init") && startsWith("log.genericEvent", "type") && equals("log.merakiGroup", "events") && contains("log.genericEvent", "ip") && contains("log.genericEvent", "vap") && contains("log.genericEvent", "duration") && contains("log.genericEvent", "download") && contains("log.genericEvent", "upload")'
10091003
# Adding action result
10101004
- add:
1011-
function: 'string'
1005+
function: string
10121006
params:
10131007
key: actionResult
1014-
value: 'accepted'
1008+
value: success
10151009
where: '!equals("log.controlFlag", "Init") && startsWith("log.genericEvent", "type") && equals("log.merakiGroup", "events") && contains("log.genericEvent", "ip") && contains("log.genericEvent", "vap") && contains("log.genericEvent", "duration") && contains("log.genericEvent", "download") && contains("log.genericEvent", "upload")'
10161010
# ........................................
10171011
# Event: event wireless packet flood detected in Meraki MR Access Points
@@ -1123,32 +1117,32 @@ pipeline:
11231117
# Event: flows, flow allowed by Layer 3 firewall in Meraki MR Access Points
11241118
- grok:
11251119
patterns:
1126-
- fieldName: actionResult
1127-
pattern: '{{.word}}'
1128-
- fieldName: log.irrelevant
1129-
pattern: 'src(\s)?='
1130-
- fieldName: origin.ip
1131-
pattern: '{{.ipv4}}|{{.ipv6}}'
1132-
- fieldName: log.irrelevant
1133-
pattern: 'dst(\s)?='
1134-
- fieldName: target.ip
1135-
pattern: '{{.ipv4}}|{{.ipv6}}'
1136-
- fieldName: log.irrelevant
1137-
pattern: 'mac(\s)?='
1138-
- fieldName: origin.mac
1139-
pattern: '{{.data}}protocol'
1140-
- fieldName: log.irrelevant
1141-
pattern: '='
1142-
- fieldName: protocol
1143-
pattern: '{{.data}}sport'
1144-
- fieldName: log.irrelevant
1145-
pattern: '='
1146-
- fieldName: origin.port
1147-
pattern: '{{.integer}}'
1148-
- fieldName: log.irrelevant
1149-
pattern: 'dport(\s)?='
1150-
- fieldName: target.port
1151-
pattern: '{{.integer}}'
1120+
- fieldName: log.merakiResult
1121+
pattern: '{{.word}}'
1122+
- fieldName: log.irrelevant
1123+
pattern: src(\s)?=
1124+
- fieldName: origin.ip
1125+
pattern: '{{.ipv4}}|{{.ipv6}}'
1126+
- fieldName: log.irrelevant
1127+
pattern: dst(\s)?=
1128+
- fieldName: target.ip
1129+
pattern: '{{.ipv4}}|{{.ipv6}}'
1130+
- fieldName: log.irrelevant
1131+
pattern: mac(\s)?=
1132+
- fieldName: origin.mac
1133+
pattern: '{{.data}}protocol'
1134+
- fieldName: log.irrelevant
1135+
pattern: '='
1136+
- fieldName: protocol
1137+
pattern: '{{.data}}sport'
1138+
- fieldName: log.irrelevant
1139+
pattern: '='
1140+
- fieldName: origin.port
1141+
pattern: '{{.integer}}'
1142+
- fieldName: log.irrelevant
1143+
pattern: dport(\s)?=
1144+
- fieldName: target.port
1145+
pattern: '{{.integer}}'
11521146
source: log.genericEvent
11531147
where: '!equals("log.controlFlag", "Init") && (startsWith("log.genericEvent", "allow") || startsWith("log.genericEvent", "deny")) && equals("log.merakiGroup", "flows") && contains("log.genericEvent", "src") && contains("log.genericEvent", "dst") && contains("log.genericEvent", "sport") && contains("log.genericEvent", "dport") && contains("log.genericEvent", "mac")'
11541148
- trim:
@@ -1165,10 +1159,10 @@ pipeline:
11651159
where: '!equals("log.controlFlag", "Init") && (startsWith("log.genericEvent", "allow") || startsWith("log.genericEvent", "deny")) && equals("log.merakiGroup", "flows") && contains("log.genericEvent", "src") && contains("log.genericEvent", "dst") && contains("log.genericEvent", "sport") && contains("log.genericEvent", "dport") && contains("log.genericEvent", "mac")'
11661160
# Adding action result
11671161
- add:
1168-
function: 'string'
1162+
function: string
11691163
params:
11701164
key: actionResult
1171-
value: 'accepted'
1165+
value: success
11721166
where: '!equals("log.controlFlag", "Init") && startsWith("log.genericEvent", "allow") && equals("log.merakiGroup", "flows") && contains("log.genericEvent", "src") && contains("log.genericEvent", "dst") && contains("log.genericEvent", "sport") && contains("log.genericEvent", "dport") && contains("log.genericEvent", "mac")'
11731167
# Adding action result
11741168
- add:
@@ -1388,12 +1382,6 @@ pipeline:
13881382
- log.vap
13891383
where: startsWith("log.genericEvent", "airmarshal_events") && contains("log.genericEvent", "type") && contains("log.genericEvent", "ssid") && contains("log.genericEvent", "bssid")
13901384
# Adding action result
1391-
- add:
1392-
function: 'string'
1393-
params:
1394-
key: actionResult
1395-
value: 'accepted'
1396-
where: startsWith("log.genericEvent", "airmarshal_events") && contains("log.genericEvent", "type") && contains("log.genericEvent", "ssid") && contains("log.genericEvent", "bssid")
13971385
# ........................................
13981386
# Event: security_event ids_alerted, ids signature matched in Meraki MX Security Appliance
13991387
- grok:
@@ -1469,12 +1457,6 @@ pipeline:
14691457
- protocol
14701458
where: startsWith("log.genericEvent", "signature") && contains("log.genericEvent", "priority") && contains("log.genericEvent", "timestamp") && contains("log.genericEvent", "direction") && contains("log.genericEvent", "dhost")
14711459
# Adding action result
1472-
- add:
1473-
function: 'string'
1474-
params:
1475-
key: actionResult
1476-
value: 'accepted'
1477-
where: startsWith("log.genericEvent", "signature") && contains("log.genericEvent", "priority") && contains("log.genericEvent", "timestamp") && contains("log.genericEvent", "direction") && contains("log.genericEvent", "dhost")
14781460
# ............................................................................
14791461
# Cleaning common fields
14801462
- trim:
@@ -1541,9 +1523,95 @@ pipeline:
15411523
- target.port
15421524
to: int
15431525
#......................................................................#
1526+
# Keep the vendor event group and message available to correlation rules.
1527+
- grok:
1528+
source: log.merakiGroup
1529+
patterns:
1530+
- fieldName: log.eventType
1531+
pattern: '{{.greedy}}'
1532+
where: exists("log.merakiGroup")
1533+
- grok:
1534+
source: log.genericEvent
1535+
patterns:
1536+
- fieldName: log.message
1537+
pattern: '{{.greedy}}'
1538+
where: exists("log.genericEvent")
15441539
# Removing unused fields
15451540
- delete:
15461541
fields:
15471542
- log.controlFlag
15481543
- log.irrelevant
1549-
- log.genericEvent
1544+
- log.genericEvent
1545+
1546+
# Normalize explicit outcomes; an unknown outcome remains unset.
1547+
- add:
1548+
function: string
1549+
params:
1550+
key: actionResult
1551+
value: denied
1552+
where: equalsIgnoreCase("log.merakiResult","blocked")
1553+
1554+
# Keep addresses in IP fields and retain other source values under log.
1555+
- rename:
1556+
from:
1557+
- origin.ip
1558+
to: log.unparsedOriginIp
1559+
where: exists("origin.ip") && (!(inCIDR("origin.ip","0.0.0.0/0") || inCIDR("origin.ip","::/0")) || oneOf("origin.ip",["0.0.0.0","::"]))
1560+
- rename:
1561+
from:
1562+
- target.ip
1563+
to: log.unparsedTargetIp
1564+
where: exists("target.ip") && (!(inCIDR("target.ip","0.0.0.0/0") || inCIDR("target.ip","::/0")) || oneOf("target.ip",["0.0.0.0","::"]))
1565+
- add:
1566+
function: string
1567+
params:
1568+
key: protocol
1569+
value: ICMP
1570+
where: equals("protocol",1)
1571+
- add:
1572+
function: string
1573+
params:
1574+
key: protocol
1575+
value: TCP
1576+
where: equals("protocol",6)
1577+
- add:
1578+
function: string
1579+
params:
1580+
key: protocol
1581+
value: UDP
1582+
where: equals("protocol",17)
1583+
- add:
1584+
function: string
1585+
params:
1586+
key: protocol
1587+
value: GRE
1588+
where: equals("protocol",47)
1589+
- add:
1590+
function: string
1591+
params:
1592+
key: protocol
1593+
value: ESP
1594+
where: equals("protocol",50)
1595+
- add:
1596+
function: string
1597+
params:
1598+
key: protocol
1599+
value: AH
1600+
where: equals("protocol",51)
1601+
- add:
1602+
function: string
1603+
params:
1604+
key: protocol
1605+
value: ICMPV6
1606+
where: equals("protocol",58)
1607+
- add:
1608+
function: string
1609+
params:
1610+
key: protocol
1611+
value: SCTP
1612+
where: equals("protocol",132)
1613+
- rename:
1614+
from:
1615+
- protocol
1616+
to: log.ipProtocolNumber
1617+
where: exists("protocol") && greaterOrEqual("protocol",0)

0 commit comments

Comments
 (0)