From 14019664cbb6b5d236088940b8cbd48b0e1730d7 Mon Sep 17 00:00:00 2001 From: Ricardo Valdes Date: Thu, 24 Sep 2026 16:25:51 -0400 Subject: [PATCH 1/6] fix(cisco-firepower): parse real FTD events and fix two rule conditions Filter version 3.1.0: - Add a third header pattern for the real '%FTD--: ' shape; it runs only when the two existing header patterns set nothing. - Split the key-value text of 430001, 430002, 430003 and 430007 into log. (go-sdk v1.1.36 keeps underscores, so DNS_TTL is stored as log.DNS_TTL). Map only SrcIP, DstIP, SrcPort, DstPort and Protocol to origin.ip, target.ip, origin.port, target.port and protocol, each from the first copy of its key. Read UserAgent whole. Do not split a text that holds a second %FTD- header (two messages joined by the collector). - Rewrite the 98 direct log.* comparisons with equals, greaterOrEqual and lessOrEqual, and replace the undeclared lgreaterOrEqual. - Write the 16 log.* geolocation results to log.Geolocation. - Apply the six LINA step fixes shared with the Cisco ASA filter: 302013 direction, 302304 protocol, 305011/305012 action, 302017 users, the 106102/106103 add order (values unchanged) and the 113009/113011 guard. Rules version v1.0.1: - Intrusion rule: read message 430001 and the device's own Priority and Classification; drop the event-type, impact and syslog-level branches. - Non-standard-port rule: read ApplicationProtocol and InitiatorPackets, require a destination port and a destination outside the private ranges; the history search is unchanged. Names, impact, grouping and MITRE labels are unchanged. The malware, indicator-of-compromise and threat-intelligence rules are unchanged. Co-Authored-By: Claude Opus 5.5 --- filters/cisco/firepower.yml | 398 ++++++++++++------ rules/cisco/firepower/c2_nonstandard_port.yml | 16 +- ...rusion_prevention_high_priority_events.yml | 16 +- 3 files changed, 287 insertions(+), 143 deletions(-) diff --git a/filters/cisco/firepower.yml b/filters/cisco/firepower.yml index 2c37bf00e..eb66bbbdc 100644 --- a/filters/cisco/firepower.yml +++ b/filters/cisco/firepower.yml @@ -1,4 +1,4 @@ -# CISCO Firepower filter, version 3.0.3 +# CISCO Firepower filter, version 3.1.0 # Based on examples provided and https://www.cisco.com/c/en/us/td/docs/security/firepower/Syslogs/b_fptd_syslog_guide.html # Filter Input requirements -> Syslog # @@ -44,13 +44,31 @@ pipeline: - fieldName: log.msg pattern: '{{.greedy}}' source: raw + # Real Firepower Threat Defense shape: '%FTD--: ', with no + # timestamp and no device name. Runs only when the two groks above set nothing. + - grok: + patterns: + - fieldName: log.syslogPri + pattern: '(\<{{.integer}}\>)' + - fieldName: log.firePowerHeader + pattern: '%FTD-' + - fieldName: log.severity + pattern: '{{.integer}}' + - fieldName: log.messageId + pattern: '-{{.integer}}' + - fieldName: log.ciscoSeparator + pattern: '\:{{.space}}' + - fieldName: log.msg + pattern: '{{.greedy}}' + source: raw + where: '!exists("log.messageId")' #......................................................................# # Adding log.localIp geolocation - dynamic: plugin: com.utmstack.geolocation params: source: log.localIp - destination: log.localIp.geolocation + destination: log.localIpGeolocation where: exists("log.localIp") # Removing unwanted prefixes - trim: @@ -63,6 +81,128 @@ pipeline: fields: - log.messageId to: int + #......................................................................# + # FTD-1-4300xx security events seen in real records (430001 intrusion, 430002 and + # 430003 connection start and end, 430007): the text is 'Key: Value, Key: Value, ...'. + # Other 4300xx IDs (for example 430004/430005 file and malware events) are not parsed + # until a real record or Cisco's field list confirms their layout. + # Every key goes to log. (go-sdk v1.1.36 keeps letters, digits, dots and + # underscores, so 'Prefilter Policy' becomes log.PrefilterPolicy and 'DNS_TTL' + # stays log.DNS_TTL). + # A record whose text holds a second '%FTD-' header (two syslog messages joined + # by the collector) is not parsed, so no value can come from the second message. + - kv: + fieldSplit: ', ' + valueSplit: ': ' + source: log.msg + where: 'exists("log.msg") && oneOf("log.messageId", [430001, 430002, 430003, 430007]) && !contains("log.msg", "%FTD-")' + # A UserAgent value can hold ', Key: value' text, and the kv split keeps the last + # copy of a key. Split the text before UserAgent again, so every key that comes + # before UserAgent keeps the device's own value. + - grok: + patterns: + - fieldName: log.ftdHead + pattern: '{{.data}}, UserAgent: ' + source: log.msg + where: 'oneOf("log.messageId", [430001, 430002, 430003, 430007]) && !contains("log.msg", "%FTD-") && contains("log.msg", ", UserAgent: ")' + - kv: + fieldSplit: ', ' + valueSplit: ': ' + source: log.ftdHead + where: 'exists("log.ftdHead")' + - delete: + fields: + - log.ftdHead + where: 'exists("log.ftdHead")' + # UserAgent values can contain ', ' (for example 'KHTML, like Gecko'); read the + # whole value up to the key that follows it (Client, or AuthenticationSource) + - grok: + patterns: + - fieldName: log.irrelevant + pattern: '{{.data}}, UserAgent: ' + - fieldName: log.UserAgent + pattern: '{{.data}}, (Client|AuthenticationSource): ' + source: log.msg + where: 'oneOf("log.messageId", [430001, 430002, 430003, 430007]) && !contains("log.msg", "%FTD-") && contains("log.msg", ", UserAgent: ")' + - trim: + function: regex + substring: ',\s(Client|AuthenticationSource):\s*$' + fields: + - log.UserAgent + where: 'oneOf("log.messageId", [430001, 430002, 430003, 430007]) && !contains("log.msg", "%FTD-") && contains("log.msg", ", UserAgent: ")' + # Standard fields, each from the first occurrence of its key: a later copy of the + # key inside a free-text value (for example a crafted UserAgent) cannot replace them + - grok: + patterns: + - fieldName: log.irrelevant + pattern: '{{.data}}, SrcIP: ' + - fieldName: origin.ip + pattern: '({{.ipv4}}|{{.ipv6}})' + - fieldName: log.irrelevant + pattern: ',' + source: log.msg + where: 'oneOf("log.messageId", [430001, 430002, 430003, 430007]) && !contains("log.msg", "%FTD-")' + - grok: + patterns: + - fieldName: log.irrelevant + pattern: '{{.data}}, DstIP: ' + - fieldName: target.ip + pattern: '({{.ipv4}}|{{.ipv6}})' + - fieldName: log.irrelevant + pattern: ',' + source: log.msg + where: 'oneOf("log.messageId", [430001, 430002, 430003, 430007]) && !contains("log.msg", "%FTD-")' + - grok: + patterns: + - fieldName: log.irrelevant + pattern: '{{.data}}, SrcPort: ' + - fieldName: origin.port + pattern: '{{.integer}}' + - fieldName: log.irrelevant + pattern: ',' + source: log.msg + where: 'oneOf("log.messageId", [430001, 430002, 430003, 430007]) && !contains("log.msg", "%FTD-")' + - grok: + patterns: + - fieldName: log.irrelevant + pattern: '{{.data}}, DstPort: ' + - fieldName: target.port + pattern: '{{.integer}}' + - fieldName: log.irrelevant + pattern: ',' + source: log.msg + where: 'oneOf("log.messageId", [430001, 430002, 430003, 430007]) && !contains("log.msg", "%FTD-")' + - grok: + patterns: + - fieldName: log.irrelevant + pattern: '{{.data}}, Protocol: ' + - fieldName: protocol + pattern: '{{.word}}' + - fieldName: log.irrelevant + pattern: ',' + source: log.msg + where: 'oneOf("log.messageId", [430001, 430002, 430003, 430007]) && !contains("log.msg", "%FTD-")' + # Remove the kv copies of the five keys that now have a standard field + - delete: + fields: + - log.SrcIP + where: 'oneOf("log.messageId", [430001, 430002, 430003, 430007]) && exists("origin.ip")' + - delete: + fields: + - log.DstIP + where: 'oneOf("log.messageId", [430001, 430002, 430003, 430007]) && exists("target.ip")' + - delete: + fields: + - log.SrcPort + where: 'oneOf("log.messageId", [430001, 430002, 430003, 430007]) && exists("origin.port")' + - delete: + fields: + - log.DstPort + where: 'oneOf("log.messageId", [430001, 430002, 430003, 430007]) && exists("target.port")' + - delete: + fields: + - log.Protocol + where: 'oneOf("log.messageId", [430001, 430002, 430003, 430007]) && exists("protocol")' # Begining specific message parsing #......................................................................# # FTD-2-106001 @@ -311,15 +451,15 @@ pipeline: function: 'string' params: key: actionResult - value: 'accepted' - where: exists("actionResult") && (equals("log.messageId", 106102) || equals("log.messageId", 106103)) && equalsIgnoreCase("actionResult", "Permitted") + value: 'denied' + where: exists("actionResult") && (equals("log.messageId", 106102) || equals("log.messageId", 106103)) && !equalsIgnoreCase("actionResult", "Permitted") # Adding action result - add: function: 'string' params: key: actionResult - value: 'denied' - where: exists("actionResult") && (equals("log.messageId", 106102) || equals("log.messageId", 106103)) && !equalsIgnoreCase("actionResult", "Permitted") + value: 'accepted' + where: exists("actionResult") && (equals("log.messageId", 106102) || equals("log.messageId", 106103)) && equalsIgnoreCase("actionResult", "Permitted") #......................................................................# # FTD-6-109101 - grok: @@ -456,21 +596,21 @@ pipeline: - fieldName: action pattern: '{{.greedy}}' source: log.msg - where: greaterOrEqual("log.messageId", 109201) && log.messageId<=109213 + where: greaterOrEqual("log.messageId", 109201) && lessOrEqual("log.messageId", 109213) - trim: function: suffix substring: ',' fields: - log.session - origin.user - where: lgreaterOrEqual("log.messageId", 109201) && log.messageId<=109213 + where: greaterOrEqual("log.messageId", 109201) && lessOrEqual("log.messageId", 109213) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: greaterOrEqual("log.messageId", 109201) && log.messageId<=109213 + where: greaterOrEqual("log.messageId", 109201) && lessOrEqual("log.messageId", 109213) #......................................................................# # FTD-6-113004 - grok: @@ -621,7 +761,7 @@ pipeline: - fieldName: origin.user pattern: '{{.greedy}}' source: log.msg - where: equals("log.messageId", 113009) + where: equals("log.messageId", 113009) && !exists("origin.user") - trim: function: suffix substring: 'for' @@ -692,7 +832,7 @@ pipeline: - fieldName: origin.user pattern: '{{.greedy}}' source: log.msg - where: equals("log.messageId", 113011) + where: equals("log.messageId", 113011) && !exists("origin.user") - trim: function: suffix substring: 'for' @@ -934,19 +1074,19 @@ pipeline: - fieldName: log.rest pattern: '{{.greedy}}' source: log.msg - where: equals("log.messageId", 113031) || log.messageId==113032 || log.messageId==113033 + where: equals("log.messageId", 113031) || equals("log.messageId", 113032) || equals("log.messageId", 113033) - trim: function: suffix substring: 'User' fields: - origin.group - where: equals("log.messageId", 113031) || log.messageId==113032 || log.messageId==113033 + where: equals("log.messageId", 113031) || equals("log.messageId", 113032) || equals("log.messageId", 113033) - trim: function: suffix substring: 'IP' fields: - origin.user - where: equals("log.messageId", 113031) || log.messageId==113032 || log.messageId==113033 + where: equals("log.messageId", 113031) || equals("log.messageId", 113032) || equals("log.messageId", 113033) # Specific fields - grok: patterns: @@ -957,7 +1097,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.rest - where: equals("log.messageId", 113031) || log.messageId==113032 || log.messageId==113033 + where: equals("log.messageId", 113031) || equals("log.messageId", 113032) || equals("log.messageId", 113033) - grok: patterns: - fieldName: action @@ -965,20 +1105,20 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.rest - where: equals("log.messageId", 113031) || log.messageId==113032 || log.messageId==113033 + where: equals("log.messageId", 113031) || equals("log.messageId", 113032) || equals("log.messageId", 113033) - trim: function: suffix substring: 'is' fields: - log.filter - where: equals("log.messageId", 113031) || log.messageId==113032 || log.messageId==113033 + where: equals("log.messageId", 113031) || equals("log.messageId", 113032) || equals("log.messageId", 113033) # Adding action result - add: function: 'string' params: key: actionResult value: 'denied' - where: equals("log.messageId", 113031) || log.messageId==113032 || log.messageId==113033 + where: equals("log.messageId", 113031) || equals("log.messageId", 113032) || equals("log.messageId", 113033) #......................................................................# # FTD-113034,113035,113036,113038,113039 - grok: @@ -994,20 +1134,20 @@ pipeline: - fieldName: log.rest pattern: '{{.greedy}}' source: log.msg - where: equals("log.messageId", 113034) || log.messageId==113035 || log.messageId==113036 || log.messageId==113038 || log.messageId==113039 + where: equals("log.messageId", 113034) || equals("log.messageId", 113035) || equals("log.messageId", 113036) || equals("log.messageId", 113038) || equals("log.messageId", 113039) # Cleaning fields - trim: function: suffix substring: 'User' fields: - origin.group - where: equals("log.messageId", 113034) || log.messageId==113035 || log.messageId==113036 || log.messageId==113038 || log.messageId==113039 + where: equals("log.messageId", 113034) || equals("log.messageId", 113035) || equals("log.messageId", 113036) || equals("log.messageId", 113038) || equals("log.messageId", 113039) - trim: function: suffix substring: 'IP' fields: - origin.user - where: equals("log.messageId", 113034) || log.messageId==113035 || log.messageId==113036 || log.messageId==113038 || log.messageId==113039 + where: equals("log.messageId", 113034) || equals("log.messageId", 113035) || equals("log.messageId", 113036) || equals("log.messageId", 113038) || equals("log.messageId", 113039) - trim: function: prefix substring: '<' @@ -1015,7 +1155,7 @@ pipeline: - origin.group - origin.user - origin.ip - where: equals("log.messageId", 113034) || log.messageId==113035 || log.messageId==113036 || log.messageId==113038 || log.messageId==113039 + where: equals("log.messageId", 113034) || equals("log.messageId", 113035) || equals("log.messageId", 113036) || equals("log.messageId", 113038) || equals("log.messageId", 113039) - trim: function: suffix substring: '>' @@ -1023,7 +1163,7 @@ pipeline: - origin.group - origin.user - origin.ip - where: equals("log.messageId", 113034) || log.messageId==113035 || log.messageId==113036 || log.messageId==113038 || log.messageId==113039 + where: equals("log.messageId", 113034) || equals("log.messageId", 113035) || equals("log.messageId", 113036) || equals("log.messageId", 113038) || equals("log.messageId", 113039) # Adding action result - add: function: 'string' @@ -1037,7 +1177,7 @@ pipeline: params: key: actionResult value: 'denied' - where: equals("log.messageId", 113035) || log.messageId==113038 + where: equals("log.messageId", 113035) || equals("log.messageId", 113038) #......................................................................# # FTD-4-113042 - grok: @@ -1292,7 +1432,7 @@ pipeline: - fieldName: log.tmpFromip pattern: '({{.ipv4}}|{{.ipv6}}|{{.hostname}})((\s)?/(\s)?{{.integer}}|(\s)?/)?' source: log.msg - where: equals("log.messageId", 302003) || log.messageId==302004 + where: equals("log.messageId", 302003) || equals("log.messageId", 302004) # Second variant - grok: patterns: @@ -1309,7 +1449,7 @@ pipeline: - fieldName: log.tmpFromip pattern: '({{.ipv4}}|{{.ipv6}})((\s)?/(\s)?{{.integer}}|(\s)?/)?' source: log.msg - where: equals("log.messageId", 302003) || log.messageId==302004 + where: equals("log.messageId", 302003) || equals("log.messageId", 302004) # Parsing from/to ip and port - grok: patterns: @@ -1318,13 +1458,13 @@ pipeline: - fieldName: target.port pattern: '{{.greedy}}' source: log.tmpToip - where: equals("log.messageId", 302003) || log.messageId==302004 + where: equals("log.messageId", 302003) || equals("log.messageId", 302004) - grok: patterns: - fieldName: target.ip pattern: '({{.ipv4}}|{{.ipv6}}|{{.hostname}})' source: log.tmpToip - where: equals("log.messageId", 302003) || log.messageId==302004 + where: equals("log.messageId", 302003) || equals("log.messageId", 302004) - grok: patterns: - fieldName: origin.ip @@ -1332,33 +1472,33 @@ pipeline: - fieldName: origin.port pattern: '{{.greedy}}' source: log.tmpFromip - where: equals("log.messageId", 302003) || log.messageId==302004 + where: equals("log.messageId", 302003) || equals("log.messageId", 302004) - grok: patterns: - fieldName: origin.ip pattern: '({{.ipv4}}|{{.ipv6}}|{{.hostname}})' source: log.tmpFromip - where: equals("log.messageId", 302003) || log.messageId==302004 + where: equals("log.messageId", 302003) || equals("log.messageId", 302004) - trim: function: prefix substring: '/' fields: - origin.port - target.port - where: equals("log.messageId", 302003) || log.messageId==302004 + where: equals("log.messageId", 302003) || equals("log.messageId", 302004) # Removing temp fields - delete: fields: - log.tmpFromip - log.tmpToip - where: equals("log.messageId", 302003) || log.messageId==302004 + where: equals("log.messageId", 302003) || equals("log.messageId", 302004) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: equals("log.messageId", 302003) || log.messageId==302004 + where: equals("log.messageId", 302003) || equals("log.messageId", 302004) #......................................................................# # FTD-6-302012 - grok: @@ -1439,8 +1579,6 @@ pipeline: pattern: 'Built' - fieldName: log.direction pattern: '{{.word}}' - - fieldName: log.irrelevant - pattern: '{{.greedy}}' source: log.tmpDirection where: equals("log.messageId", 302013) # Parsing firewall user from @@ -1981,8 +2119,6 @@ pipeline: pattern: '\(' - fieldName: log.firewallUserTo pattern: '{{.data}}\)' - - fieldName: log.irrelevant - pattern: '{{.greedy}}' source: log.tmpFwUserTo where: equals("log.messageId", 302017) - trim: @@ -2001,6 +2137,12 @@ pipeline: - log.realCid - target.user where: equals("log.messageId", 302017) + - trim: + function: prefix + substring: '(' + fields: + - target.user + where: equals("log.messageId", 302017) # Removing tmp fields - delete: fields: @@ -2226,7 +2368,7 @@ pipeline: - fieldName: log.tmpFwUserFrom pattern: '{{.greedy}}' source: log.tmpFaddrAndFwUser - where: equals("log.messageId", 302020) || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) - grok: patterns: - fieldName: origin.ip @@ -2234,7 +2376,7 @@ pipeline: - fieldName: log.tmpFwUserFrom pattern: '{{.greedy}}' source: log.tmpFaddrAndFwUser - where: equals("log.messageId", 302020) || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) # icmpSeqNum variants - grok: patterns: @@ -2245,7 +2387,7 @@ pipeline: - fieldName: log.irrelevant pattern: 'gaddr' source: log.tmpFaddrAndFwUser - where: equals("log.messageId", 302020) || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) - grok: patterns: - fieldName: log.icmpSeqNum @@ -2253,14 +2395,14 @@ pipeline: - fieldName: log.tmpFwUserFrom pattern: 'gaddr' source: log.tmpFaddrAndFwUser - where: equals("log.messageId", 302020) || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) # Cleaning icmpSeqNum - trim: function: suffix substring: 'gaddr' fields: - log.icmpSeqNum - where: equals("log.messageId", 302020) || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) # Continue with firewall user from - grok: patterns: @@ -2271,7 +2413,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserFrom - where: equals("log.messageId", 302020) || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) # For parsing gaddr and global fwuser (Begin with ip/port), or icmpType - grok: patterns: @@ -2282,7 +2424,7 @@ pipeline: - fieldName: log.tmpFwUserGlobal pattern: '{{.greedy}}' source: log.tmpGaddrAndFwUser - where: equals("log.messageId", 302020) || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) - grok: patterns: - fieldName: log.globalIp @@ -2290,7 +2432,7 @@ pipeline: - fieldName: log.tmpFwUserGlobal pattern: '{{.greedy}}' source: log.tmpGaddrAndFwUser - where: equals("log.messageId", 302020) || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) # icmpType variants - grok: patterns: @@ -2301,7 +2443,7 @@ pipeline: - fieldName: log.irrelevant pattern: 'laddr' source: log.tmpGaddrAndFwUser - where: equals("log.messageId", 302020) || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) - grok: patterns: - fieldName: log.icmpType @@ -2309,20 +2451,20 @@ pipeline: - fieldName: log.tmpFwUserGlobal pattern: 'laddr' source: log.tmpGaddrAndFwUser - where: equals("log.messageId", 302020) || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) # Cleaning icmpType - trim: function: suffix substring: 'laddr' fields: - log.icmpType - where: equals("log.messageId", 302020) || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) - trim: function: suffix substring: 'code' fields: - log.icmpType - where: equals("log.messageId", 302020) || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) # Continue with global firewall - grok: patterns: @@ -2333,7 +2475,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserGlobal - where: equals("log.messageId", 302020) || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) # For parsing laddr and fwuser to (Begin with ip/port) - grok: patterns: @@ -2344,7 +2486,7 @@ pipeline: - fieldName: log.tmpFwUserTo pattern: '{{.greedy}}' source: log.tmpLaddrAndFwUser - where: equals("log.messageId", 302020) || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) - grok: patterns: - fieldName: target.ip @@ -2352,7 +2494,7 @@ pipeline: - fieldName: log.tmpFwUserTo pattern: '{{.greedy}}' source: log.tmpLaddrAndFwUser - where: equals("log.messageId", 302020) || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) # Continue with firewall user to - grok: patterns: @@ -2363,7 +2505,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserTo - where: equals("log.messageId", 302020) || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) # Cleaning fields - trim: function: suffix @@ -2371,7 +2513,7 @@ pipeline: fields: - target.ip - origin.ip - where: equals("log.messageId", 302020) || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) - trim: function: suffix substring: ')' @@ -2379,7 +2521,7 @@ pipeline: - log.firewallUserFrom - log.firewallUserTo - log.globalFirewallUser - where: equals("log.messageId", 302020) || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) # Removing tmp fields - delete: fields: @@ -2389,14 +2531,14 @@ pipeline: - log.tmpFaddrAndFwUser - log.tmpGaddrAndFwUser - log.tmpLaddrAndFwUser - where: equals("log.messageId", 302020) || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: equals("log.messageId", 302020) || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) #......................................................................# # FTD-6-302022, 302024, 302026 - grok: @@ -2428,7 +2570,7 @@ pipeline: - fieldName: log.tmpMappedIpPortTo pattern: '\({{.data}}\)' source: log.msg - where: equals("log.messageId", 302022) || log.messageId==302024 || log.messageId==302026 + where: equals("log.messageId", 302022) || equals("log.messageId", 302024) || equals("log.messageId", 302026) # Parsing mapped ip,port from tmp fields # With port - grok: @@ -2442,7 +2584,7 @@ pipeline: - fieldName: log.irrelevant pattern: '\)' source: log.tmpMappedIpPortFrom - where: equals("log.messageId", 302022) || log.messageId==302024 || log.messageId==302026 + where: equals("log.messageId", 302022) || equals("log.messageId", 302024) || equals("log.messageId", 302026) # Without port - grok: patterns: @@ -2453,7 +2595,7 @@ pipeline: - fieldName: log.irrelevant pattern: '\)' source: log.tmpMappedIpPortFrom - where: equals("log.messageId", 302022) || log.messageId==302024 || log.messageId==302026 + where: equals("log.messageId", 302022) || equals("log.messageId", 302024) || equals("log.messageId", 302026) # Parsing mapped ip,port to, from tmp fields # With port - grok: @@ -2467,7 +2609,7 @@ pipeline: - fieldName: log.irrelevant pattern: '\)' source: log.tmpMappedIpPortTo - where: equals("log.messageId", 302022) || log.messageId==302024 || log.messageId==302026 + where: equals("log.messageId", 302022) || equals("log.messageId", 302024) || equals("log.messageId", 302026) # Without port - grok: patterns: @@ -2478,21 +2620,21 @@ pipeline: - fieldName: log.irrelevant pattern: '\)' source: log.tmpMappedIpPortTo - where: equals("log.messageId", 302022) || log.messageId==302024 || log.messageId==302026 + where: equals("log.messageId", 302022) || equals("log.messageId", 302024) || equals("log.messageId", 302026) # Cleaning fields - trim: function: suffix substring: 'stub' fields: - log.role - where: equals("log.messageId", 302022) || log.messageId==302024 || log.messageId==302026 + where: equals("log.messageId", 302022) || equals("log.messageId", 302024) || equals("log.messageId", 302026) - trim: function: suffix substring: ':' fields: - log.srcInterface - log.dstInterface - where: equals("log.messageId", 302022) || log.messageId==302024 || log.messageId==302026 + where: equals("log.messageId", 302022) || equals("log.messageId", 302024) || equals("log.messageId", 302026) - trim: function: suffix substring: '/' @@ -2501,20 +2643,20 @@ pipeline: - target.ip - log.mappedIpFrom - log.mappedIpTo - where: equals("log.messageId", 302022) || log.messageId==302024 || log.messageId==302026 + where: equals("log.messageId", 302022) || equals("log.messageId", 302024) || equals("log.messageId", 302026) # Removing tmp fields - delete: fields: - log.tmpMappedIpPortFrom - log.tmpMappedIpPortTo - where: equals("log.messageId", 302022) || log.messageId==302024 || log.messageId==302026 + where: equals("log.messageId", 302022) || equals("log.messageId", 302024) || equals("log.messageId", 302026) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: equals("log.messageId", 302022) || log.messageId==302024 || log.messageId==302026 + where: equals("log.messageId", 302022) || equals("log.messageId", 302024) || equals("log.messageId", 302026) #......................................................................# # FTD-6-302023, 302025, 302027 - grok: @@ -2550,7 +2692,7 @@ pipeline: - fieldName: log.reason pattern: '{{.greedy}}' source: log.msg - where: equals("log.messageId", 302023) || log.messageId==302025 || log.messageId==302027 + where: equals("log.messageId", 302023) || equals("log.messageId", 302025) || equals("log.messageId", 302027) # Cleaning fields - trim: function: suffix @@ -2558,21 +2700,21 @@ pipeline: fields: - log.srcInterface - log.dstInterface - where: equals("log.messageId", 302023) || log.messageId==302025 || log.messageId==302027 + where: equals("log.messageId", 302023) || equals("log.messageId", 302025) || equals("log.messageId", 302027) - trim: function: suffix substring: '/' fields: - origin.ip - target.ip - where: equals("log.messageId", 302023) || log.messageId==302025 || log.messageId==302027 + where: equals("log.messageId", 302023) || equals("log.messageId", 302025) || equals("log.messageId", 302027) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: equals("log.messageId", 302023) || log.messageId==302025 || log.messageId==302027 + where: equals("log.messageId", 302023) || equals("log.messageId", 302025) || equals("log.messageId", 302027) #......................................................................# # FTD-302033,302034 - grok: @@ -2592,7 +2734,7 @@ pipeline: - fieldName: log.tmpIpPortTo pattern: '{{.greedy}}' source: log.msg - where: equals("log.messageId", 302033) || log.messageId==302034 + where: equals("log.messageId", 302033) || equals("log.messageId", 302034) # Parsing optional ports from y to - grok: patterns: @@ -2601,7 +2743,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpPortFrom - where: equals("log.messageId", 302033) || log.messageId==302034 + where: equals("log.messageId", 302033) || equals("log.messageId", 302034) - grok: patterns: - fieldName: target.ip @@ -2609,13 +2751,13 @@ pipeline: - fieldName: target.port pattern: '{{.greedy}}' source: log.tmpIpPortTo - where: equals("log.messageId", 302033) || log.messageId==302034 + where: equals("log.messageId", 302033) || equals("log.messageId", 302034) - grok: patterns: - fieldName: target.ip pattern: '({{.ipv4}}|{{.ipv6}}|{{.hostname}})(\s)?' source: log.tmpIpPortTo - where: equals("log.messageId", 302033) || log.messageId==302034 + where: equals("log.messageId", 302033) || equals("log.messageId", 302034) # Cleaning fields - trim: function: suffix @@ -2623,20 +2765,20 @@ pipeline: fields: - log.srcInterface - log.dstInterface - where: equals("log.messageId", 302033) || log.messageId==302034 + where: equals("log.messageId", 302033) || equals("log.messageId", 302034) - trim: function: suffix substring: '/' fields: - origin.ip - target.ip - where: equals("log.messageId", 302033) || log.messageId==302034 + where: equals("log.messageId", 302033) || equals("log.messageId", 302034) # Remove tmp fields - delete: fields: - log.tmpPortFrom - log.tmpIpPortTo - where: equals("log.messageId", 302033) || log.messageId==302034 + where: equals("log.messageId", 302033) || equals("log.messageId", 302034) # Adding action result - add: function: 'string' @@ -2769,7 +2911,7 @@ pipeline: - grok: patterns: - fieldName: log.irrelevant - pattern: 'Built' + pattern: 'Teardown' - fieldName: protocol pattern: '{{.word}}' - fieldName: log.irrelevant @@ -2971,7 +3113,7 @@ pipeline: # Parsing protocol - grok: patterns: - - fieldName: action + - fieldName: log.irrelevant pattern: 'Built\s(dynamic|static)' - fieldName: protocol pattern: '{{.word}}' @@ -3049,7 +3191,7 @@ pipeline: # Parsing protocol - grok: patterns: - - fieldName: action + - fieldName: log.irrelevant pattern: 'Teardown\s(dynamic|static)' - fieldName: protocol pattern: '{{.word}}' @@ -3631,7 +3773,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.msg - where: equals("log.messageId", 402119) || log.messageId==402120 + where: equals("log.messageId", 402119) || equals("log.messageId", 402120) # Protocol - grok: patterns: @@ -3642,7 +3784,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: equals("log.messageId", 402119) || log.messageId==402120 + where: equals("log.messageId", 402119) || equals("log.messageId", 402120) # Cleaning fields - trim: function: suffix @@ -3650,20 +3792,20 @@ pipeline: fields: - log.seqNum - origin.user - where: equals("log.messageId", 402119) || log.messageId==402120 + where: equals("log.messageId", 402119) || equals("log.messageId", 402120) - trim: function: suffix substring: ',' fields: - log.spi - where: equals("log.messageId", 402119) || log.messageId==402120 + where: equals("log.messageId", 402119) || equals("log.messageId", 402120) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: equals("log.messageId", 402119) || log.messageId==402120 + where: equals("log.messageId", 402119) || equals("log.messageId", 402120) #......................................................................# # FTD-5-402128 - grok: @@ -4210,7 +4352,7 @@ pipeline: - fieldName: target.ip pattern: '({{.ipv4}}|{{.ipv6}}|{{.hostname}})' source: log.msg - where: equals("log.messageId", 611310) || log.messageId==611311 + where: equals("log.messageId", 611310) || equals("log.messageId", 611311) # Adding action result - add: function: 'string' @@ -4425,20 +4567,20 @@ pipeline: - fieldName: action pattern: '{{.greedy}}' source: log.msg - where: equals("log.messageId", 716001) || log.messageId==716002 || log.messageId==716003 + where: equals("log.messageId", 716001) || equals("log.messageId", 716002) || equals("log.messageId", 716003) # Cleaning fields - trim: function: suffix substring: 'User' fields: - origin.group - where: equals("log.messageId", 716001) || log.messageId==716002 || log.messageId==716003 + where: equals("log.messageId", 716001) || equals("log.messageId", 716002) || equals("log.messageId", 716003) - trim: function: suffix substring: 'IP' fields: - origin.user - where: equals("log.messageId", 716001) || log.messageId==716002 || log.messageId==716003 + where: equals("log.messageId", 716001) || equals("log.messageId", 716002) || equals("log.messageId", 716003) - trim: function: prefix substring: '<' @@ -4446,7 +4588,7 @@ pipeline: - origin.group - origin.user - origin.ip - where: equals("log.messageId", 716001) || log.messageId==716002 || log.messageId==716003 + where: equals("log.messageId", 716001) || equals("log.messageId", 716002) || equals("log.messageId", 716003) - trim: function: suffix substring: '>' @@ -4454,26 +4596,26 @@ pipeline: - origin.group - origin.user - origin.ip - where: equals("log.messageId", 716001) || log.messageId==716002 || log.messageId==716003 + where: equals("log.messageId", 716001) || equals("log.messageId", 716002) || equals("log.messageId", 716003) - trim: function: prefix substring: '''' fields: - origin.user - where: equals("log.messageId", 716001) || log.messageId==716002 || log.messageId==716003 + where: equals("log.messageId", 716001) || equals("log.messageId", 716002) || equals("log.messageId", 716003) - trim: function: suffix substring: '''' fields: - origin.user - where: equals("log.messageId", 716001) || log.messageId==716002 || log.messageId==716003 + where: equals("log.messageId", 716001) || equals("log.messageId", 716002) || equals("log.messageId", 716003) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: equals("log.messageId", 716001) || log.messageId==716002 + where: equals("log.messageId", 716001) || equals("log.messageId", 716002) #......................................................................# # FTD-6-716004,716005,716006,716007,716009 - grok: @@ -4487,47 +4629,47 @@ pipeline: - fieldName: action pattern: '{{.greedy}}' source: log.msg - where: equals("log.messageId", 716004) || log.messageId==716005 || log.messageId==716006 || log.messageId==716007 || log.messageId==716009 + where: equals("log.messageId", 716004) || equals("log.messageId", 716005) || equals("log.messageId", 716006) || equals("log.messageId", 716007) || equals("log.messageId", 716009) # Cleaning fields - trim: function: suffix substring: 'User' fields: - origin.group - where: equals("log.messageId", 716004) || log.messageId==716005 || log.messageId==716006 || log.messageId==716007 || log.messageId==716009 + where: equals("log.messageId", 716004) || equals("log.messageId", 716005) || equals("log.messageId", 716006) || equals("log.messageId", 716007) || equals("log.messageId", 716009) - trim: function: prefix substring: '<' fields: - origin.group - origin.user - where: equals("log.messageId", 716004) || log.messageId==716005 || log.messageId==716006 || log.messageId==716007 || log.messageId==716009 + where: equals("log.messageId", 716004) || equals("log.messageId", 716005) || equals("log.messageId", 716006) || equals("log.messageId", 716007) || equals("log.messageId", 716009) - trim: function: suffix substring: '>' fields: - origin.group - origin.user - where: equals("log.messageId", 716004) || log.messageId==716005 || log.messageId==716006 || log.messageId==716007 || log.messageId==716009 + where: equals("log.messageId", 716004) || equals("log.messageId", 716005) || equals("log.messageId", 716006) || equals("log.messageId", 716007) || equals("log.messageId", 716009) - trim: function: prefix substring: '''' fields: - origin.user - where: equals("log.messageId", 716004) || log.messageId==716005 || log.messageId==716006 || log.messageId==716007 || log.messageId==716009 + where: equals("log.messageId", 716004) || equals("log.messageId", 716005) || equals("log.messageId", 716006) || equals("log.messageId", 716007) || equals("log.messageId", 716009) - trim: function: suffix substring: '''' fields: - origin.user - where: equals("log.messageId", 716004) || log.messageId==716005 || log.messageId==716006 || log.messageId==716007 || log.messageId==716009 + where: equals("log.messageId", 716004) || equals("log.messageId", 716005) || equals("log.messageId", 716006) || equals("log.messageId", 716007) || equals("log.messageId", 716009) # Adding action result - add: function: 'string' params: key: actionResult value: 'denied' - where: equals("log.messageId", 716004) || log.messageId==716007 || log.messageId==716009 + where: equals("log.messageId", 716004) || equals("log.messageId", 716007) || equals("log.messageId", 716009) - add: function: 'string' params: @@ -4656,7 +4798,7 @@ pipeline: - fieldName: action pattern: '{{.greedy}}' source: log.msg - where: equals("log.messageId", 719017) || (log.messageId>=719019 && log.messageId<=719023) + where: equals("log.messageId", 719017) || (greaterOrEqual("log.messageId", 719019) && lessOrEqual("log.messageId", 719023)) # Adding log.ciscoResult and modifying some actions # Adding action result - add: @@ -4664,13 +4806,13 @@ pipeline: params: key: actionResult value: 'denied' - where: equals("log.messageId", 719019) || log.messageId==719023 + where: equals("log.messageId", 719019) || equals("log.messageId", 719023) - add: function: 'string' params: key: actionResult value: 'accepted' - where: equals("log.messageId", 719020) || log.messageId==719022 + where: equals("log.messageId", 719020) || equals("log.messageId", 719022) - add: function: 'string' params: @@ -4695,13 +4837,13 @@ pipeline: substring: '''' fields: - origin.user - where: equals("log.messageId", 719017) || (log.messageId>=719019 && log.messageId<=719023) + where: equals("log.messageId", 719017) || (greaterOrEqual("log.messageId", 719019) && lessOrEqual("log.messageId", 719023)) - trim: function: suffix substring: '''' fields: - origin.user - where: equals("log.messageId", 719017) || (log.messageId>=719019 && log.messageId<=719023) + where: equals("log.messageId", 719017) || (greaterOrEqual("log.messageId", 719019) && lessOrEqual("log.messageId", 719023)) #......................................................................# # FTD-6-719018 - grok: @@ -4971,20 +5113,20 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.msg - where: equals("log.messageId", 733102) || log.messageId==733103 + where: equals("log.messageId", 733102) || equals("log.messageId", 733103) # Adding action - add: function: 'string' params: key: action value: 'Threat-detection add host to shun list' - where: (equals("log.messageId", 733102) || log.messageId==733103) && contains("log.msg", "add") + where: (equals("log.messageId", 733102) || equals("log.messageId", 733103)) && contains("log.msg", "add") - add: function: 'string' params: key: action value: 'Threat-detection removes host to shun list' - where: (equals("log.messageId", 733102) || log.messageId==733103) && contains("log.msg", "removes") + where: (equals("log.messageId", 733102) || equals("log.messageId", 733103)) && contains("log.msg", "removes") #......................................................................# # Decoding severity - add: @@ -4992,19 +5134,19 @@ pipeline: params: key: severity value: 'high' - where: log.severity=="1" || log.severity=="2" || log.severity=="3" + where: equals("log.severity", "1") || equals("log.severity", "2") || equals("log.severity", "3") - add: function: 'string' params: key: severity value: 'medium' - where: log.severity=="4" + where: equals("log.severity", "4") - add: function: 'string' params: key: severity value: 'low' - where: log.severity=="5" || log.severity=="6" || log.severity=="7" + where: equals("log.severity", "5") || equals("log.severity", "6") || equals("log.severity", "7") #......................................................................# # Adding common geolocation - dynamic: @@ -5024,105 +5166,105 @@ pipeline: plugin: com.utmstack.geolocation params: source: log.mappedIpFrom - destination: log.mappedIpFrom.geolocation + destination: log.mappedIpFromGeolocation where: exists("log.mappedIpFrom") # Adding log.mappedIpTo geolocation - dynamic: plugin: com.utmstack.geolocation params: source: log.mappedIpTo - destination: log.mappedIpTo.geolocation + destination: log.mappedIpToGeolocation where: exists("log.mappedIpTo") # Adding log.translatedIpFrom geolocation - dynamic: plugin: com.utmstack.geolocation params: source: log.translatedIpFrom - destination: log.translatedIpFrom.geolocation + destination: log.translatedIpFromGeolocation where: exists("log.translatedIpFrom") # Adding log.translatedIpTo geolocation - dynamic: plugin: com.utmstack.geolocation params: source: log.translatedIpTo - destination: log.translatedIpTo.geolocation + destination: log.translatedIpToGeolocation where: exists("log.translatedIpTo") # Adding log.globalIp geolocation - dynamic: plugin: com.utmstack.geolocation params: source: log.globalIp - destination: log.globalIp.geolocation + destination: log.globalIpGeolocation where: exists("log.globalIp") # Adding log.remoteIp geolocation - dynamic: plugin: com.utmstack.geolocation params: source: log.remoteIp - destination: log.remoteIp.geolocation + destination: log.remoteIpGeolocation where: exists("log.remoteIp") # Adding log.mappedIp geolocation - dynamic: plugin: com.utmstack.geolocation params: source: log.mappedIp - destination: log.mappedIp.geolocation + destination: log.mappedIpGeolocation where: exists("log.mappedIp") # Adding log.decapsulatedPktDestAddr geolocation - dynamic: plugin: com.utmstack.geolocation params: source: log.decapsulatedPktDestAddr - destination: log.decapsulatedPktDestAddr.geolocation + destination: log.decapsulatedPktDestAddrGeolocation where: exists("log.decapsulatedPktDestAddr") # Adding log.decapsulatedPktSrcAddr geolocation - dynamic: plugin: com.utmstack.geolocation params: source: log.decapsulatedPktSrcAddr - destination: log.decapsulatedPktSrcAddr.geolocation + destination: log.decapsulatedPktSrcAddrGeolocation where: exists("log.decapsulatedPktSrcAddr") # Adding log.localProxyIpAddr geolocation - dynamic: plugin: com.utmstack.geolocation params: source: log.localProxyIpAddr - destination: log.localProxyIpAddr.geolocation + destination: log.localProxyIpAddrGeolocation where: exists("log.localProxyIpAddr") # Adding log.remoteProxyIpAddr geolocation - dynamic: plugin: com.utmstack.geolocation params: source: log.remoteProxyIpAddr - destination: log.remoteProxyIpAddr.geolocation + destination: log.remoteProxyIpAddrGeolocation where: exists("log.remoteProxyIpAddr") # Adding log.primaryDns geolocation - dynamic: plugin: com.utmstack.geolocation params: source: log.primaryDns - destination: log.primaryDns.geolocation + destination: log.primaryDnsGeolocation where: exists("log.primaryDns") # Adding log.secondaryDns geolocation - dynamic: plugin: com.utmstack.geolocation params: source: log.secondaryDns - destination: log.secondaryDns.geolocation + destination: log.secondaryDnsGeolocation where: exists("log.secondaryDns") # Adding log.primaryWins geolocation - dynamic: plugin: com.utmstack.geolocation params: source: log.primaryWins - destination: log.primaryWins.geolocation + destination: log.primaryWinsGeolocation where: exists("log.primaryWins") # Adding log.secondaryWins geolocation - dynamic: plugin: com.utmstack.geolocation params: source: log.secondaryWins - destination: log.secondaryWins.geolocation + destination: log.secondaryWinsGeolocation where: exists("log.secondaryWins") #......................................................................# # Common field conversions diff --git a/rules/cisco/firepower/c2_nonstandard_port.yml b/rules/cisco/firepower/c2_nonstandard_port.yml index c2a311f29..f2432151d 100644 --- a/rules/cisco/firepower/c2_nonstandard_port.yml +++ b/rules/cisco/firepower/c2_nonstandard_port.yml @@ -1,4 +1,4 @@ -# Rule version v1.0.0 +# Rule version v1.0.1 dataTypes: - firewall-cisco-firepower @@ -24,13 +24,17 @@ description: | 5. Examine the connection duration and data transfer patterns 6. Consider blocking the destination IP and scanning the internal host where: | - exists("log.appProto") && + exists("log.ApplicationProtocol") && exists("origin.ip") && exists("target.ip") && - ((contains("log.appProto", "HTTP") && !oneOf("target.port", [80, 443, 8080, 8443, 8000, 8888])) || - (contains("log.appProto", "SSL") && !oneOf("target.port", [443, 8443, 993, 995, 465, 636])) || - equals("log.appProto", "unknown-tcp")) && - equals("log.initiatorPackets", true) + exists("target.port") && + !inCIDR("target.ip", "10.0.0.0/8") && + !inCIDR("target.ip", "172.16.0.0/12") && + !inCIDR("target.ip", "192.168.0.0/16") && + !inCIDR("target.ip", "fc00::/7") && + ((contains("log.ApplicationProtocol", "HTTP") && !oneOf("target.port", [80, 443, 8080, 8443, 8000, 8888])) || + (contains("log.ApplicationProtocol", "SSL") && !oneOf("target.port", [443, 8443, 993, 995, 465, 636]))) && + greaterThan("log.InitiatorPackets", 0) afterEvents: - indexPattern: v11-log-firewall-cisco-firepower-* with: diff --git a/rules/cisco/firepower/intrusion_prevention_high_priority_events.yml b/rules/cisco/firepower/intrusion_prevention_high_priority_events.yml index 22aff3cd3..6acc04f54 100644 --- a/rules/cisco/firepower/intrusion_prevention_high_priority_events.yml +++ b/rules/cisco/firepower/intrusion_prevention_high_priority_events.yml @@ -1,4 +1,4 @@ -# Rule version v1.0.0 +# Rule version v1.0.1 dataTypes: - firewall-cisco-firepower @@ -26,14 +26,12 @@ description: | 7. Consider blocking the source IP if it shows persistent malicious behavior 8. Document the incident and update security controls based on findings where: | - equals("log.eventType", "IPS_EVENT") && - (equals("log.priority", 1) || - lessOrEqual("log.severity", 2) || - equals("log.impact", "HIGH") || - contains("log.classification", "attempted-admin") || - contains("log.classification", "attempted-user") || - contains("log.classification", "web-application-attack") || - contains("log.classification", "exploit-kit")) + equals("log.messageId", 430001) && + (equals("log.Priority", 1) || + contains("log.Classification", "Attempted Administrator Privilege Gain") || + contains("log.Classification", "Attempted User Privilege Gain") || + contains("log.Classification", "Web Application Attack") || + contains("log.Classification", "Exploit Kit Activity Detected")) groupBy: - adversary.ip - target.ip From b55e481854037f20aac2ff3a0c993c139735c60f Mon Sep 17 00:00:00 2001 From: Ricardo Valdes Date: Thu, 24 Sep 2026 16:26:00 -0400 Subject: [PATCH 2/6] test(cisco-firepower): add fabricated raw fixtures and regression checks plugins/alerts/testdata/cisco-firepower holds 58 fabricated raw lines (documentation addresses, example names, a made-up device UUID), the fields and alerts recorded from EventProcessor 8a3ade7 (go-sdk v1.1.36), the shared grok definitions, fabricated geolocation data and replay.py, which runs the lines through the playground and checks every event and alert. The non-standard-port rule is staged there as a condition-only test copy because no OpenSearch runs; its history search is not executed. cisco_firepower_filter_test.go checks the helper where clauses (truth tables, no error without a log object), the geolocation destinations, a model of the engine's step plugins against the recorded fields (header, key-value split with underscore names, joined messages, unparsed IDs, the shared LINA fixes and the typo fix), the new and guarded step conditions, the rule contract, both changed rule conditions as go-sdk predicates (positives and near misses) and the history placeholders. Co-Authored-By: Claude Opus 5.5 --- plugins/alerts/cisco_firepower_filter_test.go | 1188 +++++++++ .../testdata/cisco-firepower/expected.json | 2271 +++++++++++++++++ .../geolocation-data/asn-blocks-v4.csv | 3 + .../geolocation-data/asn-blocks-v6.csv | 2 + .../geolocation-data/blocks-v4.csv | 3 + .../geolocation-data/blocks-v6.csv | 2 + .../geolocation-data/locations-en.csv | 4 + .../testdata/cisco-firepower/patterns.yaml | 16 + .../alerts/testdata/cisco-firepower/raw.json | 63 + .../alerts/testdata/cisco-firepower/replay.py | 221 ++ 10 files changed, 3773 insertions(+) create mode 100644 plugins/alerts/cisco_firepower_filter_test.go create mode 100644 plugins/alerts/testdata/cisco-firepower/expected.json create mode 100644 plugins/alerts/testdata/cisco-firepower/geolocation-data/asn-blocks-v4.csv create mode 100644 plugins/alerts/testdata/cisco-firepower/geolocation-data/asn-blocks-v6.csv create mode 100644 plugins/alerts/testdata/cisco-firepower/geolocation-data/blocks-v4.csv create mode 100644 plugins/alerts/testdata/cisco-firepower/geolocation-data/blocks-v6.csv create mode 100644 plugins/alerts/testdata/cisco-firepower/geolocation-data/locations-en.csv create mode 100644 plugins/alerts/testdata/cisco-firepower/patterns.yaml create mode 100644 plugins/alerts/testdata/cisco-firepower/raw.json create mode 100644 plugins/alerts/testdata/cisco-firepower/replay.py diff --git a/plugins/alerts/cisco_firepower_filter_test.go b/plugins/alerts/cisco_firepower_filter_test.go new file mode 100644 index 000000000..5b23a0ac8 --- /dev/null +++ b/plugins/alerts/cisco_firepower_filter_test.go @@ -0,0 +1,1188 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "path/filepath" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + "testing" + "text/template" + "unicode/utf8" + + "github.com/threatwinds/go-sdk/plugins" + "github.com/threatwinds/go-sdk/utils" + "github.com/tidwall/gjson" + "google.golang.org/protobuf/encoding/protojson" + "google.golang.org/protobuf/reflect/protoreflect" +) + +// Cisco Firepower Threat Defense regression checks. Every raw input is FABRICATED +// (testdata/cisco-firepower). The 4300xx lines follow the key layout of genuine device records +// that were reviewed privately and are not part of this repository; the LINA lines follow the +// filter's own patterns. They use RFC 5737 and RFC 3849 documentation addresses (RFC 1918 and +// RFC 4193 private addresses only for the private-destination near misses of the +// non-standard-port rule), example names and a made-up device UUID. These tests use the pinned +// go-sdk (v1.1.36) for YAML decoding, key sanitizing, CEL and Event conversion. They do not run +// the EventProcessor: fpModel mirrors its step plugins, and testdata/cisco-firepower/replay.py +// runs the same lines through the public playground. See filters/audits/cisco-firepower.md. + +const ( + fpFilter = "../../filters/cisco/firepower.yml" + fpRulesDir = "../../rules/cisco/firepower" + fpData = "testdata/cisco-firepower" + fpTenant = "00000000-0000-4000-8000-000000000001" + fpAbsent = "" + fpIPS = "intrusion_prevention_high_priority_events" + fpC2 = "c2_nonstandard_port" +) + +// The 4300xx message IDs seen in genuine records; only these are split into log. fields. +const fpObserved = `oneOf("log.messageId", [430001, 430002, 430003, 430007])` + +var fpEnvelope = map[string]bool{"id": true, "timestamp": true, "deviceTime": true, "dataType": true, + "dataSource": true, "tenantId": true, "tenantName": true, "raw": true, "errors": true} + +func fpPipeline(t *testing.T) *plugins.Pipeline { + t.Helper() + encoded, err := utils.ReadPbYaml(fpFilter) + if err != nil { + t.Fatal(err) + } + config := new(plugins.Config) + if err := protojson.Unmarshal(encoded, config); err != nil { + t.Fatal(err) + } + if len(config.Pipeline) != 1 || len(config.Pipeline[0].DataTypes) != 1 || + config.Pipeline[0].DataTypes[0] != "firewall-cisco-firepower" { + t.Fatalf("unexpected pipeline layout: %v", config.Pipeline) + } + return config.Pipeline[0] +} + +// fpWhere returns the where clause of whichever step kind is set. +func fpWhere(step *plugins.Step) string { + where := "" + step.ProtoReflect().Range(func(_ protoreflect.FieldDescriptor, v protoreflect.Value) bool { + msg := v.Message() + if fd := msg.Descriptor().Fields().ByName("where"); fd != nil { + where = msg.Get(fd).String() + } + return false + }) + return where +} + +// Before this revision, 98 clauses compared log.* directly (for example log.messageId==113032), +// and one called the undeclared function lgreaterOrEqual. These rewrite a helper clause back to +// the direct form, so both can be compared; the observed-ID list becomes a chain of ==. +var ( + fpRawLog = regexp.MustCompile(`(^|[^"\w.])log\.[A-Za-z0-9_.]+\s*(==|!=|>=|<=|<|>)`) + fpHelperToRaw = []struct { + helper *regexp.Regexp + raw string + }{ + {regexp.MustCompile(`equals\("log\.messageId", (\d+)\)`), `log.messageId==$1`}, + {regexp.MustCompile(`greaterOrEqual\("log\.messageId", (\d+)\)`), `log.messageId>=$1`}, + {regexp.MustCompile(`lessOrEqual\("log\.messageId", (\d+)\)`), `log.messageId<=$1`}, + {regexp.MustCompile(`equals\("log\.severity", "(\d)"\)`), `log.severity=="$1"`}, + {regexp.MustCompile(regexp.QuoteMeta(fpObserved)), + `(log.messageId==430001 || log.messageId==430002 || log.messageId==430003 || log.messageId==430007)`}, + } + fpHelperCall = regexp.MustCompile(`(\w+)\("([A-Za-z0-9_.]+)"(?:, ("[^"]*"|\d+|\[[\d, ]+\]))?\)`) + fpNumber = regexp.MustCompile(`\d+`) +) + +func fpRawForm(where string) string { + for _, r := range fpHelperToRaw { + where = r.helper.ReplaceAllString(where, r.raw) + } + return where +} + +// fpTruthTable builds events that all carry a log object: log.messageId as the JSON number the +// filter's cast produces (around every literal in the clause), log.severity as the one-digit +// levels 0 to 7, and every other field the clause reads absent, equal to its literal, in other +// case, containing it, or unrelated. +func fpTruthTable(where string) []string { + ids := map[float64]bool{100000: true} + severities := []string{"6"} + others := map[string][]any{} + for _, m := range fpHelperCall.FindAllStringSubmatch(where, -1) { + field, arg := m[2], m[3] + switch field { + case "log.messageId": + for _, lit := range fpNumber.FindAllString(arg, -1) { + n, _ := strconv.Atoi(lit) + for d := -1; d <= 1; d++ { + ids[float64(n+d)] = true + } + } + case "log.severity": + severities = []string{"0", "1", "2", "3", "4", "5", "6", "7"} + default: + if others[field] == nil { + others[field] = []any{nil, "unrelated"} + } + if lit, err := strconv.Unquote(arg); err == nil && lit != "" { + others[field] = append(others[field], lit, strings.ToLower(lit), strings.ToUpper(lit), "x "+lit+" y") + } + } + } + names := make([]string, 0, len(others)) + for f := range others { + names = append(names, f) + } + sort.Strings(names) + combos := []map[string]any{{}} + for _, f := range names { + var next []map[string]any + for _, c := range combos { + for _, v := range others[f] { + n := map[string]any{f: v} + for k, x := range c { + n[k] = x + } + next = append(next, n) + } + } + combos = next + } + var docs []string + for id := range ids { + for _, sev := range severities { + for _, c := range combos { + doc := map[string]any{"id": "x", "dataType": "firewall-cisco-firepower", "raw": "x", + "log": map[string]any{"messageId": id, "severity": sev}} + for f, v := range c { + if v != nil { + fpSet(doc, f, v) + } + } + b, _ := json.Marshal(doc) + docs = append(docs, string(b)) + } + } + } + return docs +} + +// No where clause compares log.* directly, and every clause compiles and runs without an error +// on a line that no header pattern accepted (no log object). Every clause that uses equals, +// greaterOrEqual or lessOrEqual on log.messageId, equals on log.severity or the observed-ID list +// has the same truth table as the direct comparison on events with a log object, and is false +// when there is none. +func TestCiscoFirepowerWhereHelpers(t *testing.T) { + steps := fpPipeline(t).Steps + cache := plugins.NewCELCache("cisco-firepower-where") + noLog := `{"id":"x","dataType":"firewall-cisco-firepower","dataSource":"fixture-ftd","tenantId":"` + fpTenant + `","raw":"x"}` + cast := -1 + for i, step := range steps { + if c := step.Cast; c != nil && c.To == "int" && c.Where == "" && len(c.Fields) == 1 && c.Fields[0] == "log.messageId" { + cast = i + break + } + } + if cast < 0 { + t.Fatal("no unconditional int cast of log.messageId") + } + var raw []string + clauses, checked, rows, observed := 0, 0, 0, 0 + for i, step := range steps { + where := fpWhere(step) + if where == "" { + continue + } + clauses++ + if _, err := cache.Eval(where, noLog); err != nil { + t.Errorf("step %d fails without a log object: %q: %v", i, where, err) + } + if fpRawLog.MatchString(where) { + raw = append(raw, where) + continue + } + observed += strings.Count(where, fpObserved) + pre := fpRawForm(where) + if pre == where { + continue + } + checked++ + if i < cast { + t.Errorf("step %d reads log.messageId before it is cast to a number: %q", i, where) + } + if got, err := cache.Eval(where, noLog); err != nil || got { + t.Errorf("step %d without a log object: %q returned %t, error %v", i, where, got, err) + } + for _, doc := range fpTruthTable(where) { + rows++ + got, err := cache.Eval(where, doc) + want, errPre := cache.Eval(pre, doc) + if err != nil || errPre != nil || got != want { + t.Errorf("step %d: %q=%t (%v), %q=%t (%v) on %s", i, where, got, err, pre, want, errPre, doc) + break + } + } + } + if len(raw) > 0 { + t.Errorf("%d where clauses compare log.* directly and fail without a log object, for example %q", len(raw), raw[0]) + } + if observed != 14 { + t.Errorf("%d clauses restrict the key-value split to the observed IDs, want 14", observed) + } + if checked < 468 { + t.Errorf("%d helper clauses read log.messageId or log.severity, want at least 468", checked) + } + t.Logf("%d where clauses, %d helper clauses, %d truth-table rows", clauses, checked, rows) +} + +// The geolocation plugin writes its result at the destination path, so a destination below +// an address field would replace the address with an object. +func TestCiscoFirepowerGeolocationDestinations(t *testing.T) { + steps := fpPipeline(t).Steps + scalars := map[string]bool{} + for _, step := range steps { + if g := step.Grok; g != nil { + for _, p := range g.Patterns { + if p.FieldName != "" { + scalars[p.FieldName] = true + } + } + } + if a := step.Add; a != nil { + scalars[a.Params["key"].GetStringValue()] = true + } + if r := step.Rename; r != nil { + scalars[r.To] = true + } + if c := step.Cast; c != nil { + for _, f := range c.Fields { + scalars[f] = true + } + } + if tr := step.Trim; tr != nil { + for _, f := range tr.Fields { + scalars[f] = true + } + } + } + count := 0 + for i, step := range steps { + d := step.Dynamic + if d == nil || d.Plugin != "com.utmstack.geolocation" { + continue + } + count++ + src, dst := d.Params["source"].GetStringValue(), d.Params["destination"].GetStringValue() + parts := strings.Split(dst, ".") + for n := 1; n <= len(parts); n++ { + prefix := strings.Join(parts[:n], ".") + if scalars[prefix] || prefix == src { + t.Errorf("step %d writes %s under %s, which holds a scalar", i, dst, prefix) + } + } + want := src + "Geolocation" + if src == "origin.ip" || src == "target.ip" { + want = strings.TrimSuffix(src, ".ip") + ".geolocation" + } else if !strings.HasPrefix(src, "log.") { + t.Errorf("step %d: unexpected geolocation source %s", i, src) + } + if dst != want { + t.Errorf("step %d: geolocation of %s goes to %s, want %s", i, src, dst, want) + } + if d.Where != `exists("`+src+`")` { + t.Errorf("step %d: where %q, want exists(%q)", i, d.Where, src) + } + } + if count != 18 { + t.Errorf("%d geolocation steps, want 18", count) + } + // The sibling key survives finalization next to the address. + draft := `{"id":"x","dataType":"firewall-cisco-firepower","raw":"x","log":{"localIp":"192.0.2.1","localIpGeolocation":{"asn":64501,"country":"Fabricated Country B"}}}` + event := new(plugins.Event) + if err := utils.StringToProtoMessage(&draft, event); err != nil { + t.Fatal(err) + } + if event.Log["localIp"].GetStringValue() != "192.0.2.1" || + event.Log["localIpGeolocation"].GetStructValue().GetFields()["asn"].GetNumberValue() != 64501 { + t.Errorf("finalized log: %v", event.Log) + } +} + +// fpModel mirrors the ordered step execution of the public EventProcessor at commit +// 8a3ade72bd9d12db21f6b273200588fb49540f14 (pkg/parsing/parsing.go and plugins/{grok,kv,trim, +// add,rename,cast,delete}/main.go): each where clause is evaluated with the SDK on the whole +// draft, a failing clause is recorded as an error and skips the step, every field name a step +// writes goes through utils.SanitizeField, and every write follows sjson.Set. The geolocation +// steps are not executed here. It is a model used to guard the filter in CI, not a substitute +// for replay.py. +type fpModel struct { + steps []*plugins.Step + defs map[string]string + cache *plugins.CELCache + regex map[string]*regexp.Regexp +} + +func fpNewModel(t *testing.T) *fpModel { + t.Helper() + encoded, err := utils.ReadPbYaml(filepath.Join(fpData, "patterns.yaml")) + if err != nil { + t.Fatal(err) + } + var file struct { + Patterns map[string]string `json:"patterns"` + } + if err := json.Unmarshal(encoded, &file); err != nil { + t.Fatal(err) + } + return &fpModel{steps: fpPipeline(t).Steps, defs: file.Patterns, + cache: plugins.NewCELCache("cisco-firepower-model"), regex: map[string]*regexp.Regexp{}} +} + +// compile expands {{.name}} like the SDK regexp cache and compiles the result. +func (m *fpModel) compile(t *testing.T, pattern string) *regexp.Regexp { + t.Helper() + if re, ok := m.regex[pattern]; ok { + return re + } + final := pattern + for i := 0; i < 10 && strings.Contains(final, "{{"); i++ { + parsed, err := template.New("pattern").Option("missingkey=error").Parse(final) + if err != nil { + t.Fatalf("pattern %q: %v", pattern, err) + } + var out bytes.Buffer + if err := parsed.Execute(&out, m.defs); err != nil { + t.Fatalf("pattern %q: %v", pattern, err) + } + if out.String() == final { + break + } + final = out.String() + } + re, err := regexp.Compile(final) + if err != nil { + t.Fatalf("pattern %q: %v", pattern, err) + } + m.regex[pattern] = re + return re +} + +func fpGet(doc map[string]any, path string) (any, bool) { + var cur any = doc + for _, part := range strings.Split(path, ".") { + obj, ok := cur.(map[string]any) + if !ok { + return nil, false + } + if cur, ok = obj[part]; !ok { + return nil, false + } + } + return cur, true +} + +// fpSet follows sjson.Set for plain dotted paths: a missing or scalar parent becomes an object. +func fpSet(doc map[string]any, path string, value any) { + parts := strings.Split(path, ".") + cur := doc + for _, part := range parts[:len(parts)-1] { + next, ok := cur[part].(map[string]any) + if !ok { + next = map[string]any{} + cur[part] = next + } + cur = next + } + cur[parts[len(parts)-1]] = value +} + +func fpDelete(doc map[string]any, path string) { + parts := strings.Split(path, ".") + cur := doc + for _, part := range parts[:len(parts)-1] { + next, ok := cur[part].(map[string]any) + if !ok { + return + } + cur = next + } + delete(cur, parts[len(parts)-1]) +} + +// fpString follows gjson.Result.String for JSON-decoded values. +func fpString(v any) string { + switch x := v.(type) { + case string: + return x + case float64: + return strconv.FormatFloat(x, 'f', -1, 64) + case bool: + return strconv.FormatBool(x) + case nil: + return "" + default: + b, _ := json.Marshal(x) + return string(b) + } +} + +func (m *fpModel) run(t *testing.T, raw string) (map[string]any, []string) { + t.Helper() + doc := map[string]any{"id": "fixture", "dataType": "firewall-cisco-firepower", "dataSource": "fixture-ftd", + "@timestamp": "2026-09-23T14:00:00Z", "tenantId": fpTenant, "raw": raw} + var errs []string + for i, step := range m.steps { + if where := fpWhere(step); where != "" { + draft, _ := json.Marshal(doc) + ok, err := m.cache.Eval(where, string(draft)) + if err != nil { + errs = append(errs, err.Error()) + } + if !ok { + continue + } + } + var err error + switch { + case step.Grok != nil: + err = m.grok(t, doc, step.Grok) + case step.Kv != nil: + err = m.kv(doc, step.Kv) + case step.Trim != nil: + err = m.trim(t, doc, step.Trim) + case step.Add != nil: + key := step.Add.Params["key"].GetStringValue() + utils.SanitizeField(&key) + if err = utils.ValidateReservedField(key, false); err == nil && step.Add.Function == "string" { + fpSet(doc, key, step.Add.Params["value"].GetStringValue()) + } else if err == nil { + err = fmt.Errorf("add function %q not modelled", step.Add.Function) + } + case step.Rename != nil: + to := step.Rename.To + utils.SanitizeField(&to) + for _, from := range step.Rename.From { + if v, ok := fpGet(doc, from); ok { + switch v.(type) { // utils.GetValueOf keeps scalars and turns JSON into text + case map[string]any, []any: + v = fpString(v) + case nil: + v = "" + } + fpSet(doc, to, v) + fpDelete(doc, from) + } + } + case step.Cast != nil: + if step.Cast.To != "int" { + t.Fatalf("step %d: cast to %s not modelled", i, step.Cast.To) + } + for _, f := range step.Cast.Fields { + if v, ok := fpGet(doc, f); ok { + fpSet(doc, f, float64(utils.CastInt64(v))) + } + } + case step.Delete != nil: + for _, f := range step.Delete.Fields { + fpDelete(doc, f) + } + case step.Dynamic != nil: + // geolocation: see TestCiscoFirepowerGeolocationDestinations and replay.py + default: + t.Fatalf("step %d: kind not modelled", i) + } + if err != nil { + errs = append(errs, err.Error()) + } + } + return doc, errs +} + +func (m *fpModel) grok(t *testing.T, doc map[string]any, g *plugins.Grok) error { + source := "raw" + if g.Source != "" { + source = g.Source + } + v, ok := fpGet(doc, source) + if !ok { + return nil + } + value := fpString(v) + type capture struct{ field, value string } + var store []capture + size := 0 + for _, p := range g.Patterns { + value = strings.TrimSpace(value) + if utf8.RuneCountInString(value) == 0 { + break + } + match := m.compile(t, p.Pattern).FindString(value) + if match == "" || !strings.HasPrefix(value, match) { + break + } + field := p.FieldName + utils.SanitizeField(&field) + if err := utils.ValidateReservedField(field, true); err != nil { + return err + } + size++ + if field != "" { + store = append(store, capture{field, strings.TrimSpace(match)}) + } + value = strings.TrimPrefix(value, match) + } + if size == len(g.Patterns) { + for _, c := range store { + fpSet(doc, c.field, c.value) + } + } + return nil +} + +// kv follows plugins/kv/main.go: split on fieldSplit, cut each pair at the first valueSplit, +// sanitize the key and store the trimmed value as text under log.; a later copy of a key +// replaces an earlier one. +func (m *fpModel) kv(doc map[string]any, k *plugins.Kv) error { + source := "raw" + if k.Source != "" { + source = k.Source + } + v, ok := fpGet(doc, source) + if !ok { + return fmt.Errorf("kv source %s does not exist", source) + } + for _, pair := range strings.Split(strings.TrimSpace(fpString(v)), k.FieldSplit) { + if key, value, found := strings.Cut(pair, k.ValueSplit); found { + utils.SanitizeField(&key) + fpSet(doc, "log."+key, strings.TrimSpace(value)) + } + } + return nil +} + +func (m *fpModel) trim(t *testing.T, doc map[string]any, tr *plugins.Trim) error { + for _, f := range tr.Fields { + if err := utils.ValidateReservedField(f, false); err != nil { + return err + } + v, ok := fpGet(doc, f) + if !ok || fpString(v) == "" { + continue + } + s := strings.TrimSpace(fpString(v)) + switch tr.Function { + case "prefix": + s = strings.TrimPrefix(s, tr.Substring) + case "suffix": + s = strings.TrimSuffix(s, tr.Substring) + case "substring": + s = strings.ReplaceAll(s, tr.Substring, "") + case "regex": + found := m.compile(t, tr.Substring).FindAllString(s, -1) + if len(found) == 0 { + continue + } + for _, x := range found { + s = strings.ReplaceAll(s, x, "") + } + } + fpSet(doc, f, strings.TrimSpace(s)) + } + return nil +} + +// fpFinalize converts the draft to the SDK Event and back to JSON the way the playground's +// event writer stores it. +func fpFinalize(t *testing.T, doc map[string]any) (*plugins.Event, map[string]any) { + t.Helper() + b, _ := json.Marshal(doc) + draft := string(b) + event := new(plugins.Event) + if err := utils.StringToProtoMessage(&draft, event); err != nil { + t.Fatal(err) + } + out, err := json.Marshal(event) + if err != nil { + t.Fatal(err) + } + var stored map[string]any + if err := json.Unmarshal(out, &stored); err != nil { + t.Fatal(err) + } + return event, stored +} + +// fpFields flattens an event to dotted leaf paths, keeping empty objects as leaves. +func fpFields(event map[string]any) map[string]any { + out := map[string]any{} + var walk func(v any, prefix string) + walk = func(v any, prefix string) { + if obj, ok := v.(map[string]any); ok && (len(obj) > 0 || prefix == "") { + for k, x := range obj { + if prefix == "" && fpEnvelope[k] { + continue + } + p := k + if prefix != "" { + p = prefix + "." + k + } + walk(x, p) + } + return + } + out[prefix] = v + } + walk(event, "") + return out +} + +var fpGeoPath = regexp.MustCompile(`^(origin\.geolocation|target\.geolocation|log\.[A-Za-z0-9]+Geolocation)(\.|$)`) + +type fpCase struct { + LogObject bool `json:"logObject"` + Fields map[string]any `json:"fields"` + Alerts []string `json:"alerts"` +} + +func fpFixtures(t *testing.T) (map[string]string, map[string]fpCase) { + t.Helper() + var raw struct { + Cases map[string]string `json:"cases"` + } + var expected struct { + Cases map[string]fpCase `json:"cases"` + } + for name, target := range map[string]any{"raw.json": &raw, "expected.json": &expected} { + data, err := os.ReadFile(filepath.Join(fpData, name)) + if err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(data, target); err != nil { + t.Fatalf("%s: %v", name, err) + } + } + if len(raw.Cases) == 0 || len(raw.Cases) != len(expected.Cases) { + t.Fatalf("raw fixtures %d, expectations %d", len(raw.Cases), len(expected.Cases)) + } + return raw.Cases, expected.Cases +} + +// fpModelEvents runs every fabricated line through the model and finalizes it. +func fpModelEvents(t *testing.T) (map[string]*plugins.Event, map[string]map[string]any, map[string][]string) { + t.Helper() + model := fpNewModel(t) + raw, _ := fpFixtures(t) + events, stored, errs := map[string]*plugins.Event{}, map[string]map[string]any{}, map[string][]string{} + for name, line := range raw { + doc, e := model.run(t, line) + events[name], stored[name] = fpFinalize(t, doc) + errs[name] = e + } + return events, stored, errs +} + +// Each change, with positive and near-miss lines. A nil value means the path must be absent. +var fpChangeCases = []struct { + change, fixture, path string + want any +}{ + {"F-H1", "430003-https", "log.messageId", float64(430003)}, + {"F-H1", "430003-https", "log.severity", "1"}, + {"F-H1", "430003-https", "severity", "high"}, + {"F-H1", "430003-space-after-pri", "log.messageId", float64(430003)}, + {"F-H1", "302013-real-header", "log.direction", "inbound"}, + {"F-H1", "302013-real-header", "origin.ip", "198.51.100.7"}, + {"F-H1 near miss", "unparsed-no-pri", "log", nil}, + {"F-H1 near miss", "unparsed-no-space", "log", nil}, + {"F-H1 near miss", "unparsed-asa-prefix", "log", nil}, + {"F-H1 near miss", "unparsed-glued-header", "log", nil}, + {"F-H1 near miss", "unparsed-linux-sshd", "log", nil}, + {"F-H1 older shape", "302013-header-bsd", "log.localIp", "ftd01.example.com"}, + {"F-A3", "302013-header-no-pri", "log.direction", "inbound"}, + {"F-K1", "430003-https", "origin.ip", "192.0.2.10"}, + {"F-K1", "430003-https", "target.ip", "198.51.100.20"}, + {"F-K1", "430003-https", "origin.port", float64(51000)}, + {"F-K1", "430003-https", "target.port", float64(443)}, + {"F-K1", "430003-https", "protocol", "tcp"}, + {"F-K1", "430003-https", "log.SrcIP", nil}, + {"F-K1", "430003-https", "log.DstIP", nil}, + {"F-K1", "430003-https", "log.SrcPort", nil}, + {"F-K1", "430003-https", "log.DstPort", nil}, + {"F-K1", "430003-https", "log.Protocol", nil}, + {"F-K1", "430003-https", "log.PrefilterPolicy", "Example Prefilter Policy"}, + {"F-K1", "430003-https", "log.ApplicationProtocol", "HTTPS"}, + {"F-K1", "430003-https", "log.InitiatorPackets", "6"}, + {"F-K1 underscore", "430003-dns-ttl", "log.DNS_TTL", "300"}, + {"F-K1 underscore", "430003-dns-ttl", "log.DNSTTL", nil}, + {"F-K1", "430003-dns-ttl", "protocol", "udp"}, + {"F-K1", "430003-dns-ttl", "target.port", float64(53)}, + {"F-K1", "430003-useragent", "log.UserAgent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Example/1.0"}, + {"F-K1", "430003-useragent", "log.Client", "Example Client"}, + {"F-K1", "430003-useragent", "log.ftdHead", nil}, + {"F-K1 crafted text", "430003-useragent-spoof", "origin.ip", "192.0.2.10"}, + {"F-K1 crafted text", "430003-useragent-spoof", "target.port", float64(80)}, + {"F-K1 crafted text", "430003-useragent-spoof", "log.AccessControlRuleAction", "Allow"}, + {"F-K1 crafted text", "430003-useragent-spoof", "log.User", "alice"}, + {"F-K1", "430003-icmp", "origin.port", nil}, + {"F-K1", "430003-icmp", "protocol", "icmp"}, + {"F-K1", "430003-ipv6", "target.ip", "2001:db8::20"}, + {"F-K1 near miss", "430003-bad-address", "origin.ip", nil}, + {"F-K1 near miss", "430003-bad-address", "log.SrcIP", "not-an-address"}, + {"F-K1", "430001-p2-user", "log.Priority", "2"}, + {"F-K1", "430001-p2-user", "log.Classification", "Attempted User Privilege Gain"}, + {"F-K1", "430002-block", "log.AccessControlRuleAction", "Block with reset"}, + {"F-K1", "430007-elephant-inside", "log.AccessControlRuleReason", "Elephant Flow"}, + {"F-K1 joined", "glued-text", "log.messageId", float64(430003)}, + {"F-K1 joined", "glued-text", "origin", nil}, + {"F-K1 joined", "glued-text", "log.EventPriority", nil}, + {"F-K1 joined", "glued-text-second-intrusion", "log.Priority", nil}, + {"F-K1 scope", "scope-430005", "log.messageId", float64(430005)}, + {"F-K1 scope", "scope-430005", "log.eventType", nil}, + {"F-K1 scope", "scope-430005", "origin", nil}, + {"F-K1 scope", "scope-430008", "log.EventPriority", nil}, + {"F-A2", "302013-header-device-ipv4", "log.localIp", "192.0.2.1"}, + {"F-A3", "302013-outbound", "log.direction", "outbound"}, + {"F-A3", "302013-header-bsd", "log.direction", "inbound"}, + {"F-A4", "302304-teardown", "protocol", "TCP"}, + {"F-A5", "305011-built", "action", "Built dynamic TCP translation"}, + {"F-A5", "305011-built", "protocol", "TCP"}, + {"F-A5", "305012-teardown", "action", "Teardown dynamic TCP translation"}, + {"F-A6", "302017-gre", "target.user", "erin"}, + {"F-A6", "302017-gre", "log.firewallUserTo", "dave"}, + {"F-A6", "302017-gre", "log.firewallUserFrom", "carol"}, + {"F-A7", "106102-permitted", "actionResult", "accepted"}, + {"F-A7 near miss", "106102-denied", "actionResult", "denied"}, + {"F-A8", "113009-with-equals", "origin.user", "alice"}, + {"F-A8", "113009-with-equals", "log.policy", "DfltGrpPolicy"}, + {"F-A8", "113011-with-equals", "origin.user", "alice"}, + {"F-A8", "113011-with-equals", "log.policy", "GP1"}, + {"F-A8 near miss", "113009-without-equals", "origin.user", "alice"}, + {"F-W2", "109201-uauth", "origin.user", "alice"}, + {"F-W2", "109201-uauth", "log.session", "0x1a2b"}, +} + +// Every fabricated line through the model: the named change cases, no where errors, and every +// stored field except geolocation equal to the playground result recorded in expected.json. +func TestCiscoFirepowerExtractionModel(t *testing.T) { + // The key names below rely on go-sdk v1.1.36 keeping underscores and removing spaces. + for in, want := range map[string]string{"DNS_TTL": "DNS_TTL", "Prefilter Policy": "PrefilterPolicy"} { + got := in + utils.SanitizeField(&got) + if got != want { + t.Fatalf("utils.SanitizeField(%q) = %q, want %q", in, got, want) + } + } + _, stored, errs := fpModelEvents(t) + _, expected := fpFixtures(t) + for _, c := range fpChangeCases { + var got any = fpAbsent + if v, ok := fpGet(stored[c.fixture], c.path); ok { + got = v + } + want := c.want + if want == nil { + want = fpAbsent + } + if !reflect.DeepEqual(got, want) { + t.Errorf("%s %s: %s = %v, want %v", c.change, c.fixture, c.path, got, want) + } + } + for name, want := range expected { + if len(errs[name]) > 0 { + t.Errorf("%s: %d where errors, first: %.200s", name, len(errs[name]), errs[name][0]) + } + if _, ok := stored[name]["log"]; ok != want.LogObject { + t.Errorf("%s: log object present=%t", name, ok) + } + got := fpFields(stored[name]) + keys := map[string]bool{} + for k := range got { + keys[k] = true + } + for k := range want.Fields { + keys[k] = true + } + for k := range keys { + if fpGeoPath.MatchString(k) { + continue + } + g, gok := got[k] + w, wok := want.Fields[k] + if gok != wok || !reflect.DeepEqual(g, w) { + t.Errorf("%s: %s = %v (present %t), want %v (present %t)", name, k, g, gok, w, wok) + } + } + } +} + +// The where clauses of the new, reordered and guarded steps, evaluated with the SDK in filter order. +func TestCiscoFirepowerStepPredicates(t *testing.T) { + steps := fpPipeline(t).Steps + cache := plugins.NewCELCache("cisco-firepower-steps") + eval := func(where, doc string) bool { + t.Helper() + ok, err := cache.Eval(where, doc) + if err != nil { + t.Fatalf("%q: %v", where, err) + } + return ok + } + // F-H1: three header patterns read raw; the third runs only while the first two set nothing. + var headers []*plugins.Grok + for _, step := range steps { + if g := step.Grok; g != nil && (g.Source == "" || g.Source == "raw") { + for _, p := range g.Patterns { + if p.FieldName == "log.messageId" { + headers = append(headers, g) + break + } + } + } + } + if len(headers) != 3 || headers[0].Where != "" || headers[1].Where != "" { + t.Fatalf("F-H1: header groks %d, want two unconditional ones and a third", len(headers)) + } + if !eval(headers[2].Where, `{"raw":"x"}`) || eval(headers[2].Where, `{"raw":"x","log":{"messageId":"-302013"}}`) { + t.Errorf("F-H1: third header grok %q must run only when no messageId was set", headers[2].Where) + } + // F-K1: the key-value split reads log.msg of the observed IDs only, and never a joined text. + var split *plugins.Kv + for _, step := range steps { + if k := step.Kv; k != nil && k.Source == "log.msg" { + split = k + } + } + if split == nil || split.FieldSplit != ", " || split.ValueSplit != ": " { + t.Fatalf("F-K1: key-value split of log.msg: %v", split) + } + for id := 430000; id <= 430009; id++ { + observed := id == 430001 || id == 430002 || id == 430003 || id == 430007 + doc := fmt.Sprintf(`{"raw":"x","log":{"messageId":%d,"msg":"SrcIP: 192.0.2.10, DstIP: 198.51.100.20"}}`, id) + if eval(split.Where, doc) != observed { + t.Errorf("F-K1: %d split=%t, want %t", id, !observed, observed) + } + } + for _, doc := range []string{ + `{"raw":"x","log":{"messageId":430003,"msg":"SrcIP: 192.0.2.10, DstIP: 198.51.1<113>%FTD-1-430003: SrcIP: 192.0.2.11"}}`, + `{"raw":"x","log":{"messageId":430003}}`, + `{"raw":"x"}`, + } { + if eval(split.Where, doc) { + t.Errorf("F-K1: split must not run on %s", doc) + } + } + // F-A7: the two actionResult adds of 106102/106103, in filter order. + var adds []*plugins.Add + for _, step := range steps { + if a := step.Add; a != nil && a.Params["key"].GetStringValue() == "actionResult" && strings.Contains(a.Where, `"log.messageId", 106102`) { + adds = append(adds, a) + } + } + if len(adds) != 2 { + t.Fatalf("106102/106103 actionResult adds: %d", len(adds)) + } + for _, c := range []struct { + id int + captured, final string + }{{106102, "permitted", "accepted"}, {106103, "permitted", "accepted"}, {106102, "Permitted", "accepted"}, + {106102, "denied", "denied"}, {106103, "denied", "denied"}} { + value := c.captured + for _, a := range adds { + doc := fmt.Sprintf(`{"raw":"x","log":{"messageId":%d},"actionResult":%q}`, c.id, value) + if eval(a.Where, doc) { + value = a.Params["value"].GetStringValue() + } + } + if value != c.final { + t.Errorf("F-A7: %d with %q ends as %q, want %q", c.id, c.captured, value, c.final) + } + } + // F-A8: the second 113009/113011 variant runs only while origin.user is unset. + for _, id := range []int{113009, 113011} { + var writers []string + for _, step := range steps { + g := step.Grok + if g == nil || !strings.Contains(fpRawForm(g.Where), fmt.Sprintf("log.messageId==%d", id)) { + continue + } + for _, p := range g.Patterns { + if p.FieldName == "origin.user" { + writers = append(writers, g.Where) + } + } + } + if len(writers) != 2 { + t.Fatalf("F-A8: %d origin.user writers for %d, want 2", len(writers), id) + } + without := fmt.Sprintf(`{"raw":"x","log":{"messageId":%d}}`, id) + with := fmt.Sprintf(`{"raw":"x","log":{"messageId":%d},"origin":{"user":"alice"}}`, id) + if !eval(writers[0], without) || !eval(writers[1], without) { + t.Errorf("F-A8: %d variants must run while no user is set", id) + } + if eval(writers[1], with) { + t.Errorf("F-A8: %d second variant %q runs after the first set origin.user", id, writers[1]) + } + if eval(writers[1], fmt.Sprintf(`{"raw":"x","log":{"messageId":%d},"origin":{"user":"alice"}}`, id+1)) { + t.Errorf("F-A8: %d second variant matches another message", id) + } + } + // F-W2: the 109201-109213 steps call declared functions and cover exactly that range. + var uauth []string + for _, step := range steps { + if w := fpWhere(step); strings.Contains(w, "109201") { + uauth = append(uauth, w) + } + } + if len(uauth) != 3 { + t.Fatalf("F-W2: %d steps for 109201-109213, want 3", len(uauth)) + } + for _, w := range uauth { + for id, want := range map[int]bool{109200: false, 109201: true, 109207: true, 109213: true, 109214: false} { + if eval(w, fmt.Sprintf(`{"raw":"x","log":{"messageId":%d}}`, id)) != want { + t.Errorf("F-W2: %q on %d, want %t", w, id, want) + } + } + } +} + +func fpLoadRules(t *testing.T) map[string]*plugins.Rule { + t.Helper() + files, err := filepath.Glob(filepath.Join(fpRulesDir, "*.y*ml")) + if err != nil || len(files) != 5 { + t.Fatalf("Cisco Firepower rules: %d files, error %v", len(files), err) + } + out := map[string]*plugins.Rule{} + for _, path := range files { + encoded, err := utils.ReadPbYaml(path) + if err != nil { + t.Fatal(err) + } + rule := new(plugins.Rule) + if err := protojson.Unmarshal(encoded, rule); err != nil { + t.Fatalf("%s: %v", path, err) + } + rule.Normalize() + out[strings.TrimSuffix(filepath.Base(path), filepath.Ext(path))] = rule + } + return out +} + +func fpSearches(searches []*plugins.SearchRequest) string { + var parts []string + for _, s := range searches { + var with []string + for _, e := range s.With { + with = append(with, e.Field+" "+e.Operator+" "+e.Value.GetStringValue()) + } + p := fmt.Sprintf("%s[%s] within %s count %d", s.IndexPattern, strings.Join(with, "; "), s.Within, s.Count) + if len(s.Or) > 0 { + p += " or(" + fpSearches(s.Or) + ")" + } + parts = append(parts, p) + } + return strings.Join(parts, " | ") +} + +func fpPlaceholders(searches []*plugins.SearchRequest, out map[string]bool) { + for _, s := range searches { + for _, e := range s.With { + if v := e.Value.GetStringValue(); strings.HasPrefix(v, "{{.") && strings.HasSuffix(v, "}}") { + out[strings.TrimSuffix(strings.TrimPrefix(v, "{{."), "}}")] = true + } + } + fpPlaceholders(s.Or, out) + } +} + +// Names, metadata, impact, grouping, MITRE labels and the history search stay as they were; +// only the two conditions change, and they no longer read key names the filter never writes. +func TestCiscoFirepowerRuleContract(t *testing.T) { + const docs = "https://www.cisco.com/c/en/us/td/docs/security/" + want := map[string]string{ + "advanced_malware_protection_alerts": "Advanced Malware Protection (AMP) Alert Detection|Initial Access|T1566 - Phishing|origin|3/3/2|lastEvent.log.sha256,adversary.ip|" + + docs + "firepower/70/configuration/guide/fpmc-config-guide-v70/file_malware_events_and_network_file_trajectory.html,https://attack.mitre.org/techniques/T1566/|", + fpC2: "Command and Control on Non-Standard Ports|Command and Control|T1571 - Non-Standard Port|origin|3/2/1|adversary.ip,target.ip,target.port|" + + docs + "secure-firewall/management-center/device-config/710/management-center-device-config-71/connection-log-fields.html,https://attack.mitre.org/techniques/T1571/|" + + "v11-log-firewall-cisco-firepower-*[origin.ip filter_term {{.origin.ip}}; target.ip filter_term {{.target.ip}}] within 1h count 5", + fpIPS: "Intrusion Prevention System High Priority Events|Execution|T1203 - Exploitation for Client Execution|origin|3/3/3|adversary.ip,target.ip|" + + docs + "secure-firewall/management-center/device-config/710/management-center-device-config-71/intrusion-overview.html,https://attack.mitre.org/techniques/T1203/|", + "ioc_matches": "Firepower IOC (Indicator of Compromise) Detection|Initial Access|T1566 - Phishing|origin|3/3/2|adversary.ip|" + + docs + "firepower/70/configuration/guide/fpmc-config-guide-v70/file_malware_events_and_network_file_trajectory.html,https://attack.mitre.org/tactics/TA0040/,https://attack.mitre.org/techniques/T1566/|", + "threat_intelligence_director_alerts": "Threat Intelligence Director (TID) Alert Detection|Command and Control|T1071.001 - Application Layer Protocol: Web Protocols|origin|3/3/2|lastEvent.log.tidIndicator,adversary.ip|" + + docs + "firepower/70/configuration/guide/fpmc-config-guide-v70/tid_overview.html,https://attack.mitre.org/techniques/T1071/|", + } + rules := fpLoadRules(t) + for stem, rule := range rules { + got := fmt.Sprintf("%s|%s|%s|%s|%d/%d/%d|%s|%s|%s", rule.Name, rule.Category, rule.Technique, rule.Adversary, + rule.Impact.Confidentiality, rule.Impact.Integrity, rule.Impact.Availability, strings.Join(rule.GroupBy, ","), + strings.Join(rule.References, ","), fpSearches(rule.Correlation)) + if got != want[stem] { + t.Errorf("%s:\n got %s\nwant %s", stem, got, want[stem]) + } + if len(rule.DataTypes) != 1 || rule.DataTypes[0] != "firewall-cisco-firepower" || len(rule.DeduplicateBy) != 0 { + t.Errorf("%s: dataTypes %v deduplicateBy %v", stem, rule.DataTypes, rule.DeduplicateBy) + } + } + for stem, gone := range map[string][]string{ + fpIPS: {`"log.eventType"`, `"log.priority"`, `"log.severity"`, `"log.impact"`, `"log.classification"`}, + fpC2: {`"log.appProto"`, `"log.initiatorPackets"`, `unknown-tcp`}, + } { + for _, name := range gone { + if strings.Contains(rules[stem].Where, name) { + t.Errorf("%s still reads %s", stem, name) + } + } + } +} + +func fpEvent(t *testing.T, body string) *plugins.Event { + t.Helper() + input := `{"dataType":"firewall-cisco-firepower","dataSource":"fixture-ftd","tenantId":"` + fpTenant + `",` + body + `}` + event := new(plugins.Event) + if err := utils.StringToProtoMessage(&input, event); err != nil { + t.Fatalf("%s: %v", body, err) + } + return event +} + +// Synthetic normalized events shaped as the filter stores them: key-value fields are text, +// ports are numbers. +func fpConnection(app, dst string, port int, packets string) string { + return fmt.Sprintf(`"log":{"messageId":430003,"ApplicationProtocol":%q,"InitiatorPackets":%q},"origin":{"ip":"192.0.2.10","port":51000},"target":{"ip":%q,"port":%d},"protocol":"tcp"`, + app, packets, dst, port) +} + +func fpIntrusion(priority, classification string) string { + return fmt.Sprintf(`"log":{"messageId":430001,"Priority":%q,"Classification":%q,"severity":"1"},"origin":{"ip":"198.51.100.7","port":51000},"target":{"ip":"192.0.2.20","port":80},"protocol":"tcp"`, + priority, classification) +} + +var fpRuleCases = []struct { + rule, name, body string + want bool +}{ + {fpIPS, "priority 1", fpIntrusion("1", "Misc Attack"), true}, + {fpIPS, "priority 2, Attempted User Privilege Gain", fpIntrusion("2", "Attempted User Privilege Gain"), true}, + {fpIPS, "priority 2, Attempted Administrator Privilege Gain", fpIntrusion("2", "Attempted Administrator Privilege Gain"), true}, + {fpIPS, "priority 2, Web Application Attack", fpIntrusion("2", "Web Application Attack"), true}, + {fpIPS, "priority 3, Exploit Kit Activity Detected", fpIntrusion("3", "Exploit Kit Activity Detected"), true}, + {fpIPS, "priority 2, Misc Attack", fpIntrusion("2", "Misc Attack"), false}, + {fpIPS, "priority 3 at syslog level 1", fpIntrusion("3", "Potential Corporate Privacy Violation"), false}, + {fpIPS, "short class name attempted-user", fpIntrusion("2", "attempted-user"), false}, + {fpIPS, "connection event with Priority 1", `"log":{"messageId":430003,"Priority":"1","EventPriority":"High"},"origin":{"ip":"192.0.2.10"}`, false}, + {fpIPS, "old key names only", `"log":{"messageId":430001,"eventType":"IPS_EVENT","priority":1,"impact":"HIGH"},"origin":{"ip":"198.51.100.7"}`, false}, + {fpC2, "HTTP to an outside address on 8081", fpConnection("HTTP", "203.0.113.10", 8081, "6"), true}, + {fpC2, "SSL to an outside address on 9001", fpConnection("SSL", "198.51.100.30", 9001, "6"), true}, + {fpC2, "HTTPS to an outside address on 4443", fpConnection("HTTPS", "203.0.113.11", 4443, "6"), true}, + {fpC2, "HTTP to a global IPv6 address on 8081", fpConnection("HTTP", "2001:db8::20", 8081, "1"), true}, + {fpC2, "HTTP on listed port 8080", fpConnection("HTTP", "203.0.113.10", 8080, "6"), false}, + {fpC2, "HTTPS on 443", fpConnection("HTTPS", "203.0.113.10", 443, "6"), false}, + {fpC2, "SSL on listed port 993", fpConnection("SSL", "203.0.113.10", 993, "6"), false}, + {fpC2, "HTTP to 10.0.0.0/8", fpConnection("HTTP", "10.0.0.20", 8181, "6"), false}, + {fpC2, "SSL to 172.16.0.0/12", fpConnection("SSL", "172.31.255.254", 9001, "6"), false}, + {fpC2, "HTTP to 192.168.0.0/16", fpConnection("HTTP", "192.168.0.20", 8181, "6"), false}, + {fpC2, "HTTP to fc00::/7", fpConnection("HTTP", "fd00::20", 8081, "6"), false}, + {fpC2, "Unknown application", fpConnection("Unknown", "203.0.113.13", 4444, "6"), false}, + {fpC2, "no initiator packets", fpConnection("HTTP", "203.0.113.10", 8081, "0"), false}, + {fpC2, "no packet counter", `"log":{"messageId":430001,"ApplicationProtocol":"HTTP"},"origin":{"ip":"192.0.2.10"},"target":{"ip":"203.0.113.10","port":8081}`, false}, + {fpC2, "no destination port", `"log":{"messageId":430003,"ApplicationProtocol":"HTTP","InitiatorPackets":"6"},"origin":{"ip":"192.0.2.10"},"target":{"ip":"203.0.113.15"}`, false}, + {fpC2, "no source address", `"log":{"messageId":430003,"ApplicationProtocol":"HTTP","InitiatorPackets":"6","SrcIP":"not-an-address"},"target":{"ip":"203.0.113.10","port":8081}`, false}, + {fpC2, "old key names only", `"log":{"messageId":430003,"appProto":"HTTP","initiatorPackets":true},"origin":{"ip":"192.0.2.10"},"target":{"ip":"203.0.113.10","port":8081}`, false}, +} + +// go-sdk CEL on synthetic normalized events for the two changed conditions, and on the model +// output of every fabricated line against the alerts the playground raised (expected.json). +func TestCiscoFirepowerRulePredicates(t *testing.T) { + rules := fpLoadRules(t) + cache := plugins.NewCELCache("cisco-firepower-rules") + for _, c := range fpRuleCases { + t.Run(c.rule+"/"+c.name, func(t *testing.T) { + got, err := cache.Eval(rules[c.rule].Where, fpEvent(t, c.body)) + if err != nil { + t.Fatal(err) + } + if got != c.want { + t.Fatalf("match=%t want=%t", got, c.want) + } + }) + } + events, _, _ := fpModelEvents(t) + _, expected := fpFixtures(t) + stems := make([]string, 0, len(rules)) + for stem := range rules { + stems = append(stems, stem) + } + sort.Strings(stems) + fired := map[string]int{} + for name, event := range events { + var got []string + for _, stem := range stems { + match, err := cache.Eval(rules[stem].Where, event) + if err != nil { + t.Fatalf("%s on %s: %v", stem, name, err) + } + if match { + got = append(got, stem) + fired[stem]++ + } + } + if want := expected[name].Alerts; !reflect.DeepEqual(got, want) && !(len(got) == 0 && len(want) == 0) { + t.Errorf("%s: rules %v, playground alerts %v", name, got, want) + } + } + if fired[fpIPS] == 0 || fired[fpC2] == 0 { + t.Errorf("fabricated lines must include positives of both changed rules: %v", fired) + } +} + +// Whenever a rule with a history search matches, every {{.field}} placeholder resolves; an +// unresolved one fails the search, and five failures disable the rule with a Circuit Breaker +// alert. Checked on the model output of every fabricated line and on the synthetic events. +func TestCiscoFirepowerHistoryPlaceholders(t *testing.T) { + rules := fpLoadRules(t) + cache := plugins.NewCELCache("cisco-firepower-history") + events, _, _ := fpModelEvents(t) + for _, c := range fpRuleCases { + events["synthetic: "+c.name] = fpEvent(t, c.body) + } + names := make([]string, 0, len(events)) + for name := range events { + names = append(names, name) + } + sort.Strings(names) + checked := 0 + for stem, rule := range rules { + fields := map[string]bool{} + fpPlaceholders(rule.Correlation, fields) + if len(fields) == 0 { + continue + } + for _, name := range names { + match, err := cache.Eval(rule.Where, events[name]) + if err != nil { + t.Fatalf("%s on %s: %v", stem, name, err) + } + if !match { + continue + } + checked++ + doc, err := utils.ProtoMessageToString(events[name]) + if err != nil { + t.Fatal(err) + } + for field := range fields { + if gjson.Get(*doc, field).Value() == nil { + t.Errorf("%s matches %s without %s; its history search would fail", stem, name, field) + } + } + } + } + if checked == 0 { + t.Error("no positive case reached a history search") + } +} diff --git a/plugins/alerts/testdata/cisco-firepower/expected.json b/plugins/alerts/testdata/cisco-firepower/expected.json new file mode 100644 index 000000000..c47e99d54 --- /dev/null +++ b/plugins/alerts/testdata/cisco-firepower/expected.json @@ -0,0 +1,2271 @@ +{ + "provenance": "Expected results for the FABRICATED lines in raw.json, recorded from the public EventProcessor playground (commit 8a3ade72bd9d12db21f6b273200588fb49540f14, go-sdk v1.1.36; geolocation plugin built from this repository) with this filter, the five rules (the non-standard-port rule as a condition-only test copy), patterns.yaml and the fabricated geolocation data, after the declared per-change expectations were checked. fields excludes the envelope keys, deviceTime and tenantName.", + "envelopeTimestamp": "2026-09-23T14:00:00Z", + "cases": { + "106102-denied": { + "logObject": true, + "fields": { + "log.accessList": "outside_in", + "log.ciscoTime": "Wed Sep 23 2026 10:00:00", + "log.dstInterface": "inside", + "log.hitCount": "1", + "log.localIp": "ftd01.example.com", + "log.messageId": 106102, + "log.msg": "access-list outside_in denied tcp for user alice outside/198.51.100.7(51234) -> inside/192.0.2.10(443) hit-cnt 1 first hit [0x1a2b, 0x0]", + "log.severity": "6", + "log.srcInterface": "outside", + "target.ip": "192.0.2.10", + "target.port": 443, + "target.geolocation.country": "Fabricated Country B", + "target.geolocation.city": "Fabricated City B", + "target.geolocation.latitude": -30.125, + "target.geolocation.longitude": 40.75, + "target.geolocation.asn": 64501, + "target.geolocation.aso": "FABRICATED-AS-64501", + "target.geolocation.countryCode": "XB", + "origin.ip": "198.51.100.7", + "origin.user": "alice", + "origin.port": 51234, + "origin.geolocation.country": "Fabricated Country A", + "origin.geolocation.city": "Fabricated City A", + "origin.geolocation.latitude": 10.5, + "origin.geolocation.longitude": -20.25, + "origin.geolocation.asn": 64500, + "origin.geolocation.aso": "FABRICATED-AS-64500", + "origin.geolocation.countryCode": "XA", + "protocol": "tcp", + "actionResult": "denied", + "severity": "low" + }, + "alerts": [] + }, + "106102-permitted": { + "logObject": true, + "fields": { + "log.accessList": "outside_in", + "log.ciscoTime": "Wed Sep 23 2026 10:00:00", + "log.dstInterface": "inside", + "log.hitCount": "1", + "log.localIp": "ftd01.example.com", + "log.messageId": 106102, + "log.msg": "access-list outside_in permitted tcp for user alice outside/198.51.100.7 51234 inside/192.0.2.10 443 hit-cnt 1 first hit [0x1a2b, 0x0]", + "log.severity": "6", + "log.srcInterface": "outside", + "target.ip": "192.0.2.10", + "target.port": 443, + "target.geolocation.country": "Fabricated Country B", + "target.geolocation.city": "Fabricated City B", + "target.geolocation.latitude": -30.125, + "target.geolocation.longitude": 40.75, + "target.geolocation.asn": 64501, + "target.geolocation.aso": "FABRICATED-AS-64501", + "target.geolocation.countryCode": "XB", + "origin.ip": "198.51.100.7", + "origin.user": "alice", + "origin.port": 51234, + "origin.geolocation.country": "Fabricated Country A", + "origin.geolocation.city": "Fabricated City A", + "origin.geolocation.latitude": 10.5, + "origin.geolocation.longitude": -20.25, + "origin.geolocation.asn": 64500, + "origin.geolocation.aso": "FABRICATED-AS-64500", + "origin.geolocation.countryCode": "XA", + "protocol": "tcp", + "actionResult": "accepted", + "severity": "low" + }, + "alerts": [] + }, + "109201-uauth": { + "logObject": true, + "fields": { + "log.ciscoTime": "Wed Sep 23 2026 10:00:00", + "log.localIp": "ftd01.example.com", + "log.messageId": 109201, + "log.msg": "UAUTH: Session=0x1a2b, User=alice, Assigned IP=192.0.2.10, Succeeded adding entry.", + "log.session": "0x1a2b", + "log.severity": "5", + "origin.ip": "192.0.2.10", + "origin.user": "alice", + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "actionResult": "accepted", + "action": "Succeeded adding entry.", + "severity": "low" + }, + "alerts": [] + }, + "113009-with-equals": { + "logObject": true, + "fields": { + "log.ciscoTime": "Wed Sep 23 2026 10:00:00", + "log.localIp": "ftd01.example.com", + "log.messageId": 113009, + "log.msg": "AAA retrieved default group policy (DfltGrpPolicy) for user = alice", + "log.policy": "DfltGrpPolicy", + "log.severity": "6", + "origin.user": "alice", + "actionResult": "accepted", + "action": "AAA retrieved default group policy", + "severity": "low" + }, + "alerts": [] + }, + "113009-without-equals": { + "logObject": true, + "fields": { + "log.ciscoTime": "Wed Sep 23 2026 10:00:00", + "log.localIp": "ftd01.example.com", + "log.messageId": 113009, + "log.msg": "AAA retrieved default group policy (DfltGrpPolicy) for user alice", + "log.policy": "(DfltGrpPolicy)", + "log.severity": "6", + "origin.user": "alice", + "actionResult": "accepted", + "action": "AAA retrieved default group policy", + "severity": "low" + }, + "alerts": [] + }, + "113011-with-equals": { + "logObject": true, + "fields": { + "log.ciscoTime": "Wed Sep 23 2026 10:00:00", + "log.localIp": "ftd01.example.com", + "log.messageId": 113011, + "log.msg": "AAA retrieved user specific group policy (GP1) for user = alice", + "log.policy": "GP1", + "log.severity": "6", + "origin.user": "alice", + "action": "AAA retrieved user specific group policy", + "severity": "low" + }, + "alerts": [] + }, + "302013-header-bsd": { + "logObject": true, + "fields": { + "log.ciscoTime": "Wed Sep 23 2026 10:00:00", + "log.connectionId": "connection 1", + "log.direction": "inbound", + "log.dstInterface": "inside", + "log.localIp": "ftd01.example.com", + "log.mappedIpFrom": "198.51.100.7", + "log.mappedIpFromGeolocation.asn": 64500, + "log.mappedIpFromGeolocation.aso": "FABRICATED-AS-64500", + "log.mappedIpFromGeolocation.city": "Fabricated City A", + "log.mappedIpFromGeolocation.country": "Fabricated Country A", + "log.mappedIpFromGeolocation.countryCode": "XA", + "log.mappedIpFromGeolocation.latitude": 10.5, + "log.mappedIpFromGeolocation.longitude": -20.25, + "log.mappedIpTo": "203.0.113.5", + "log.mappedIpToGeolocation": {}, + "log.mappedPortFrom": "51234", + "log.mappedPortTo": "443", + "log.messageId": 302013, + "log.msg": "Built inbound TCP connection 1 for outside:198.51.100.7/51234 (198.51.100.7/51234) to inside:192.0.2.10/443 (203.0.113.5/443)", + "log.severity": "6", + "log.srcInterface": "outside", + "target.ip": "192.0.2.10", + "target.port": 443, + "target.geolocation.country": "Fabricated Country B", + "target.geolocation.city": "Fabricated City B", + "target.geolocation.latitude": -30.125, + "target.geolocation.longitude": 40.75, + "target.geolocation.asn": 64501, + "target.geolocation.aso": "FABRICATED-AS-64501", + "target.geolocation.countryCode": "XB", + "origin.ip": "198.51.100.7", + "origin.port": 51234, + "origin.geolocation.country": "Fabricated Country A", + "origin.geolocation.city": "Fabricated City A", + "origin.geolocation.latitude": 10.5, + "origin.geolocation.longitude": -20.25, + "origin.geolocation.asn": 64500, + "origin.geolocation.aso": "FABRICATED-AS-64500", + "origin.geolocation.countryCode": "XA", + "protocol": "TCP", + "actionResult": "accepted", + "severity": "low" + }, + "alerts": [] + }, + "302013-header-device-ipv4": { + "logObject": true, + "fields": { + "log.ciscoTime": "Wed Sep 23 2026 10:00:00", + "log.connectionId": "connection 1", + "log.direction": "inbound", + "log.dstInterface": "inside", + "log.localIp": "192.0.2.1", + "log.localIpGeolocation.asn": 64501, + "log.localIpGeolocation.aso": "FABRICATED-AS-64501", + "log.localIpGeolocation.city": "Fabricated City B", + "log.localIpGeolocation.country": "Fabricated Country B", + "log.localIpGeolocation.countryCode": "XB", + "log.localIpGeolocation.latitude": -30.125, + "log.localIpGeolocation.longitude": 40.75, + "log.mappedIpFrom": "198.51.100.7", + "log.mappedIpFromGeolocation.asn": 64500, + "log.mappedIpFromGeolocation.aso": "FABRICATED-AS-64500", + "log.mappedIpFromGeolocation.city": "Fabricated City A", + "log.mappedIpFromGeolocation.country": "Fabricated Country A", + "log.mappedIpFromGeolocation.countryCode": "XA", + "log.mappedIpFromGeolocation.latitude": 10.5, + "log.mappedIpFromGeolocation.longitude": -20.25, + "log.mappedIpTo": "203.0.113.5", + "log.mappedIpToGeolocation": {}, + "log.mappedPortFrom": "51234", + "log.mappedPortTo": "443", + "log.messageId": 302013, + "log.msg": "Built inbound TCP connection 1 for outside:198.51.100.7/51234 (198.51.100.7/51234) to inside:192.0.2.10/443 (203.0.113.5/443)", + "log.severity": "6", + "log.srcInterface": "outside", + "target.ip": "192.0.2.10", + "target.port": 443, + "target.geolocation.country": "Fabricated Country B", + "target.geolocation.city": "Fabricated City B", + "target.geolocation.latitude": -30.125, + "target.geolocation.longitude": 40.75, + "target.geolocation.asn": 64501, + "target.geolocation.aso": "FABRICATED-AS-64501", + "target.geolocation.countryCode": "XB", + "origin.ip": "198.51.100.7", + "origin.port": 51234, + "origin.geolocation.country": "Fabricated Country A", + "origin.geolocation.city": "Fabricated City A", + "origin.geolocation.latitude": 10.5, + "origin.geolocation.longitude": -20.25, + "origin.geolocation.asn": 64500, + "origin.geolocation.aso": "FABRICATED-AS-64500", + "origin.geolocation.countryCode": "XA", + "protocol": "TCP", + "actionResult": "accepted", + "severity": "low" + }, + "alerts": [] + }, + "302013-header-no-pri": { + "logObject": true, + "fields": { + "log.ciscoTime": "Wed Sep 23 2026 10:00:00", + "log.connectionId": "connection 1", + "log.direction": "inbound", + "log.dstInterface": "inside", + "log.localIp": "ftd01.example.com", + "log.mappedIpFrom": "198.51.100.7", + "log.mappedIpFromGeolocation.asn": 64500, + "log.mappedIpFromGeolocation.aso": "FABRICATED-AS-64500", + "log.mappedIpFromGeolocation.city": "Fabricated City A", + "log.mappedIpFromGeolocation.country": "Fabricated Country A", + "log.mappedIpFromGeolocation.countryCode": "XA", + "log.mappedIpFromGeolocation.latitude": 10.5, + "log.mappedIpFromGeolocation.longitude": -20.25, + "log.mappedIpTo": "203.0.113.5", + "log.mappedIpToGeolocation": {}, + "log.mappedPortFrom": "51234", + "log.mappedPortTo": "443", + "log.messageId": 302013, + "log.msg": "Built inbound TCP connection 1 for outside:198.51.100.7/51234 (198.51.100.7/51234) to inside:192.0.2.10/443 (203.0.113.5/443)", + "log.severity": "6", + "log.srcInterface": "outside", + "target.ip": "192.0.2.10", + "target.port": 443, + "target.geolocation.country": "Fabricated Country B", + "target.geolocation.city": "Fabricated City B", + "target.geolocation.latitude": -30.125, + "target.geolocation.longitude": 40.75, + "target.geolocation.asn": 64501, + "target.geolocation.aso": "FABRICATED-AS-64501", + "target.geolocation.countryCode": "XB", + "origin.ip": "198.51.100.7", + "origin.port": 51234, + "origin.geolocation.country": "Fabricated Country A", + "origin.geolocation.city": "Fabricated City A", + "origin.geolocation.latitude": 10.5, + "origin.geolocation.longitude": -20.25, + "origin.geolocation.asn": 64500, + "origin.geolocation.aso": "FABRICATED-AS-64500", + "origin.geolocation.countryCode": "XA", + "protocol": "TCP", + "actionResult": "accepted", + "severity": "low" + }, + "alerts": [] + }, + "302013-outbound": { + "logObject": true, + "fields": { + "log.ciscoTime": "Wed Sep 23 2026 10:00:00", + "log.connectionId": "connection 7", + "log.direction": "outbound", + "log.dstInterface": "inside", + "log.localIp": "ftd01.example.com", + "log.mappedIpFrom": "198.51.100.7", + "log.mappedIpFromGeolocation.asn": 64500, + "log.mappedIpFromGeolocation.aso": "FABRICATED-AS-64500", + "log.mappedIpFromGeolocation.city": "Fabricated City A", + "log.mappedIpFromGeolocation.country": "Fabricated Country A", + "log.mappedIpFromGeolocation.countryCode": "XA", + "log.mappedIpFromGeolocation.latitude": 10.5, + "log.mappedIpFromGeolocation.longitude": -20.25, + "log.mappedIpTo": "203.0.113.5", + "log.mappedIpToGeolocation": {}, + "log.mappedPortFrom": "443", + "log.mappedPortTo": "51234", + "log.messageId": 302013, + "log.msg": "Built outbound TCP connection 7 for outside:198.51.100.7/443 (198.51.100.7/443) to inside:192.0.2.10/51234 (203.0.113.5/51234)", + "log.severity": "6", + "log.srcInterface": "outside", + "target.ip": "192.0.2.10", + "target.port": 51234, + "target.geolocation.country": "Fabricated Country B", + "target.geolocation.city": "Fabricated City B", + "target.geolocation.latitude": -30.125, + "target.geolocation.longitude": 40.75, + "target.geolocation.asn": 64501, + "target.geolocation.aso": "FABRICATED-AS-64501", + "target.geolocation.countryCode": "XB", + "origin.ip": "198.51.100.7", + "origin.port": 443, + "origin.geolocation.country": "Fabricated Country A", + "origin.geolocation.city": "Fabricated City A", + "origin.geolocation.latitude": 10.5, + "origin.geolocation.longitude": -20.25, + "origin.geolocation.asn": 64500, + "origin.geolocation.aso": "FABRICATED-AS-64500", + "origin.geolocation.countryCode": "XA", + "protocol": "TCP", + "actionResult": "accepted", + "severity": "low" + }, + "alerts": [] + }, + "302013-real-header": { + "logObject": true, + "fields": { + "log.connectionId": "connection 1", + "log.direction": "inbound", + "log.dstInterface": "inside", + "log.mappedIpFrom": "198.51.100.7", + "log.mappedIpFromGeolocation.asn": 64500, + "log.mappedIpFromGeolocation.aso": "FABRICATED-AS-64500", + "log.mappedIpFromGeolocation.city": "Fabricated City A", + "log.mappedIpFromGeolocation.country": "Fabricated Country A", + "log.mappedIpFromGeolocation.countryCode": "XA", + "log.mappedIpFromGeolocation.latitude": 10.5, + "log.mappedIpFromGeolocation.longitude": -20.25, + "log.mappedIpTo": "203.0.113.5", + "log.mappedIpToGeolocation": {}, + "log.mappedPortFrom": "51234", + "log.mappedPortTo": "443", + "log.messageId": 302013, + "log.msg": "Built inbound TCP connection 1 for outside:198.51.100.7/51234 (198.51.100.7/51234) to inside:192.0.2.10/443 (203.0.113.5/443)", + "log.severity": "6", + "log.srcInterface": "outside", + "target.ip": "192.0.2.10", + "target.port": 443, + "target.geolocation.country": "Fabricated Country B", + "target.geolocation.city": "Fabricated City B", + "target.geolocation.latitude": -30.125, + "target.geolocation.longitude": 40.75, + "target.geolocation.asn": 64501, + "target.geolocation.aso": "FABRICATED-AS-64501", + "target.geolocation.countryCode": "XB", + "origin.ip": "198.51.100.7", + "origin.port": 51234, + "origin.geolocation.country": "Fabricated Country A", + "origin.geolocation.city": "Fabricated City A", + "origin.geolocation.latitude": 10.5, + "origin.geolocation.longitude": -20.25, + "origin.geolocation.asn": 64500, + "origin.geolocation.aso": "FABRICATED-AS-64500", + "origin.geolocation.countryCode": "XA", + "protocol": "TCP", + "actionResult": "accepted", + "severity": "low" + }, + "alerts": [] + }, + "302017-gre": { + "logObject": true, + "fields": { + "log.ciscoTime": "Wed Sep 23 2026 10:00:00", + "log.connectionId": "12", + "log.direction": "inbound", + "log.dstInterface": "inside", + "log.firewallUserFrom": "carol", + "log.firewallUserTo": "dave", + "log.localIp": "ftd01.example.com", + "log.messageId": 302017, + "log.msg": "Built inbound GRE connection 12 from outside:198.51.100.7 (198.51.100.7) (carol) to inside:192.0.2.10/0 (203.0.113.5/0) (dave) (erin)", + "log.realCid": "0", + "log.severity": "6", + "log.srcInterface": "outside", + "log.translatedCid": "0", + "log.translatedIpFrom": "198.51.100.7", + "log.translatedIpFromGeolocation.asn": 64500, + "log.translatedIpFromGeolocation.aso": "FABRICATED-AS-64500", + "log.translatedIpFromGeolocation.city": "Fabricated City A", + "log.translatedIpFromGeolocation.country": "Fabricated Country A", + "log.translatedIpFromGeolocation.countryCode": "XA", + "log.translatedIpFromGeolocation.latitude": 10.5, + "log.translatedIpFromGeolocation.longitude": -20.25, + "log.translatedIpTo": "203.0.113.5", + "log.translatedIpToGeolocation": {}, + "target.ip": "192.0.2.10", + "target.user": "erin", + "target.geolocation.country": "Fabricated Country B", + "target.geolocation.city": "Fabricated City B", + "target.geolocation.latitude": -30.125, + "target.geolocation.longitude": 40.75, + "target.geolocation.asn": 64501, + "target.geolocation.aso": "FABRICATED-AS-64501", + "target.geolocation.countryCode": "XB", + "origin.ip": "198.51.100.7", + "origin.geolocation.country": "Fabricated Country A", + "origin.geolocation.city": "Fabricated City A", + "origin.geolocation.latitude": 10.5, + "origin.geolocation.longitude": -20.25, + "origin.geolocation.asn": 64500, + "origin.geolocation.aso": "FABRICATED-AS-64500", + "origin.geolocation.countryCode": "XA", + "actionResult": "accepted", + "severity": "low" + }, + "alerts": [] + }, + "302020-global": { + "logObject": true, + "fields": { + "log.ciscoTime": "Wed Sep 23 2026 10:00:00", + "log.direction": "inbound", + "log.globalIp": "192.0.2.5", + "log.globalIpGeolocation.asn": 64501, + "log.globalIpGeolocation.aso": "FABRICATED-AS-64501", + "log.globalIpGeolocation.city": "Fabricated City B", + "log.globalIpGeolocation.country": "Fabricated Country B", + "log.globalIpGeolocation.countryCode": "XB", + "log.globalIpGeolocation.latitude": -30.125, + "log.globalIpGeolocation.longitude": 40.75, + "log.globalPort": "0", + "log.icmpCode": "0", + "log.icmpType": "8", + "log.localIp": "ftd01.example.com", + "log.messageId": 302020, + "log.msg": "Built inbound ICMP connection for faddr 198.51.100.7/0 gaddr 192.0.2.5/0 laddr 192.0.2.10/0 type 8 code 0", + "log.severity": "6", + "target.ip": "192.0.2.10", + "target.geolocation.country": "Fabricated Country B", + "target.geolocation.city": "Fabricated City B", + "target.geolocation.latitude": -30.125, + "target.geolocation.longitude": 40.75, + "target.geolocation.asn": 64501, + "target.geolocation.aso": "FABRICATED-AS-64501", + "target.geolocation.countryCode": "XB", + "origin.ip": "198.51.100.7", + "origin.geolocation.country": "Fabricated Country A", + "origin.geolocation.city": "Fabricated City A", + "origin.geolocation.latitude": 10.5, + "origin.geolocation.longitude": -20.25, + "origin.geolocation.asn": 64500, + "origin.geolocation.aso": "FABRICATED-AS-64500", + "origin.geolocation.countryCode": "XA", + "protocol": "ICMP", + "actionResult": "accepted", + "severity": "low" + }, + "alerts": [] + }, + "302304-teardown": { + "logObject": true, + "fields": { + "log.ciscoTime": "Wed Sep 23 2026 10:00:00", + "log.connectionId": "5", + "log.dstInterface": "inside", + "log.duration": "00:00:05", + "log.localIp": "ftd01.example.com", + "log.messageId": 302304, + "log.msg": "Teardown TCP state-bypass connection 5 from outside:198.51.100.7/51234 to inside:192.0.2.10/443 00:00:05, 1024, TCP FINs", + "log.reason": "TCP FINs", + "log.severity": "6", + "log.srcInterface": "outside", + "target.ip": "192.0.2.10", + "target.port": 443, + "target.geolocation.country": "Fabricated Country B", + "target.geolocation.city": "Fabricated City B", + "target.geolocation.latitude": -30.125, + "target.geolocation.longitude": 40.75, + "target.geolocation.asn": 64501, + "target.geolocation.aso": "FABRICATED-AS-64501", + "target.geolocation.countryCode": "XB", + "origin.bytesSent": 1024, + "origin.ip": "198.51.100.7", + "origin.port": 51234, + "origin.geolocation.country": "Fabricated Country A", + "origin.geolocation.city": "Fabricated City A", + "origin.geolocation.latitude": 10.5, + "origin.geolocation.longitude": -20.25, + "origin.geolocation.asn": 64500, + "origin.geolocation.aso": "FABRICATED-AS-64500", + "origin.geolocation.countryCode": "XA", + "protocol": "TCP", + "actionResult": "accepted", + "action": "Teardown TCP state-bypass connection", + "severity": "low" + }, + "alerts": [] + }, + "305011-built": { + "logObject": true, + "fields": { + "log.ciscoTime": "Wed Sep 23 2026 10:00:00", + "log.dstInterface": "outside", + "log.localIp": "ftd01.example.com", + "log.messageId": 305011, + "log.msg": "Built dynamic TCP translation from inside:192.0.2.10/51234 to outside:203.0.113.5/40001", + "log.severity": "6", + "log.srcInterface": "inside", + "target.ip": "203.0.113.5", + "target.port": 40001, + "target.geolocation": {}, + "origin.ip": "192.0.2.10", + "origin.port": 51234, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "TCP", + "action": "Built dynamic TCP translation", + "severity": "low" + }, + "alerts": [] + }, + "305012-teardown": { + "logObject": true, + "fields": { + "log.ciscoTime": "Wed Sep 23 2026 10:00:00", + "log.dstInterface": "outside", + "log.duration": "00:00:05", + "log.localIp": "ftd01.example.com", + "log.messageId": 305012, + "log.msg": "Teardown dynamic TCP translation from inside:192.0.2.10/51234 to outside:203.0.113.5/40001 duration 00:00:05", + "log.severity": "6", + "log.srcInterface": "inside", + "target.ip": "203.0.113.5", + "target.port": 40001, + "target.geolocation": {}, + "origin.ip": "192.0.2.10", + "origin.port": 51234, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "TCP", + "action": "Teardown dynamic TCP translation", + "severity": "low" + }, + "alerts": [] + }, + "430001-http-8081-out": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTP", + "log.Classification": "Web Application Attack", + "log.Client": "Web browser", + "log.ConnectionID": "2001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.GID": "1", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InlineResult": "Block", + "log.InstanceID": "1", + "log.IntrusionPolicy": "policy-ips", + "log.Message": "SERVER-OTHER fabricated example signature", + "log.MitreAttackGroups": "Example>Fabricated", + "log.NAPPolicy": "nap-a", + "log.Priority": "1", + "log.Revision": "1", + "log.SID": "1000001", + "log.SnortRuleGroups": "Example>Fabricated", + "log.messageId": 430001, + "log.msg": "DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 2001, SrcIP: 192.0.2.10, DstIP: 203.0.113.10, SrcPort: 51000, DstPort: 8081, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, Priority: 1, GID: 1, SID: 1000001, Revision: 1, Message: SERVER-OTHER fabricated example signature, Classification: Web Application Attack, Client: Web browser, ApplicationProtocol: HTTP, IntrusionPolicy: policy-ips, ACPolicy: policy-a, AccessControlRuleName: rule-a, NAPPolicy: nap-a, InlineResult: Block, SnortRuleGroups: Example>Fabricated, MitreAttackGroups: Example>Fabricated", + "log.severity": "1", + "target.ip": "203.0.113.10", + "target.port": 8081, + "target.geolocation": {}, + "origin.ip": "192.0.2.10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [ + "intrusion_prevention_high_priority_events" + ] + }, + "430001-near-p2-misc": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTP", + "log.Classification": "Misc Attack", + "log.Client": "Web browser", + "log.ConnectionID": "2001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.GID": "1", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InlineResult": "Block", + "log.InstanceID": "1", + "log.IntrusionPolicy": "policy-ips", + "log.Message": "SERVER-OTHER fabricated example signature", + "log.MitreAttackGroups": "Example>Fabricated", + "log.NAPPolicy": "nap-a", + "log.Priority": "2", + "log.Revision": "1", + "log.SID": "1000001", + "log.SnortRuleGroups": "Example>Fabricated", + "log.messageId": 430001, + "log.msg": "DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 2001, SrcIP: 198.51.100.7, DstIP: 192.0.2.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, Priority: 2, GID: 1, SID: 1000001, Revision: 1, Message: SERVER-OTHER fabricated example signature, Classification: Misc Attack, Client: Web browser, ApplicationProtocol: HTTP, IntrusionPolicy: policy-ips, ACPolicy: policy-a, AccessControlRuleName: rule-a, NAPPolicy: nap-a, InlineResult: Block, SnortRuleGroups: Example>Fabricated, MitreAttackGroups: Example>Fabricated", + "log.severity": "1", + "target.ip": "192.0.2.20", + "target.port": 80, + "target.geolocation.country": "Fabricated Country B", + "target.geolocation.city": "Fabricated City B", + "target.geolocation.latitude": -30.125, + "target.geolocation.longitude": 40.75, + "target.geolocation.asn": 64501, + "target.geolocation.aso": "FABRICATED-AS-64501", + "target.geolocation.countryCode": "XB", + "origin.ip": "198.51.100.7", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country A", + "origin.geolocation.city": "Fabricated City A", + "origin.geolocation.latitude": 10.5, + "origin.geolocation.longitude": -20.25, + "origin.geolocation.asn": 64500, + "origin.geolocation.aso": "FABRICATED-AS-64500", + "origin.geolocation.countryCode": "XA", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430001-near-p3": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTP", + "log.Classification": "Potential Corporate Privacy Violation", + "log.Client": "Web browser", + "log.ConnectionID": "2001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.GID": "1", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InlineResult": "Block", + "log.InstanceID": "1", + "log.IntrusionPolicy": "policy-ips", + "log.Message": "SERVER-OTHER fabricated example signature", + "log.MitreAttackGroups": "Example>Fabricated", + "log.NAPPolicy": "nap-a", + "log.Priority": "3", + "log.Revision": "1", + "log.SID": "1000001", + "log.SnortRuleGroups": "Example>Fabricated", + "log.messageId": 430001, + "log.msg": "DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 2001, SrcIP: 198.51.100.7, DstIP: 192.0.2.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, Priority: 3, GID: 1, SID: 1000001, Revision: 1, Message: SERVER-OTHER fabricated example signature, Classification: Potential Corporate Privacy Violation, Client: Web browser, ApplicationProtocol: HTTP, IntrusionPolicy: policy-ips, ACPolicy: policy-a, AccessControlRuleName: rule-a, NAPPolicy: nap-a, InlineResult: Block, SnortRuleGroups: Example>Fabricated, MitreAttackGroups: Example>Fabricated", + "log.severity": "1", + "target.ip": "192.0.2.20", + "target.port": 80, + "target.geolocation.country": "Fabricated Country B", + "target.geolocation.city": "Fabricated City B", + "target.geolocation.latitude": -30.125, + "target.geolocation.longitude": 40.75, + "target.geolocation.asn": 64501, + "target.geolocation.aso": "FABRICATED-AS-64501", + "target.geolocation.countryCode": "XB", + "origin.ip": "198.51.100.7", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country A", + "origin.geolocation.city": "Fabricated City A", + "origin.geolocation.latitude": 10.5, + "origin.geolocation.longitude": -20.25, + "origin.geolocation.asn": 64500, + "origin.geolocation.aso": "FABRICATED-AS-64500", + "origin.geolocation.countryCode": "XA", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430001-near-short-class": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTP", + "log.Classification": "attempted-user", + "log.Client": "Web browser", + "log.ConnectionID": "2001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.GID": "1", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InlineResult": "Block", + "log.InstanceID": "1", + "log.IntrusionPolicy": "policy-ips", + "log.Message": "SERVER-OTHER fabricated example signature", + "log.MitreAttackGroups": "Example>Fabricated", + "log.NAPPolicy": "nap-a", + "log.Priority": "2", + "log.Revision": "1", + "log.SID": "1000001", + "log.SnortRuleGroups": "Example>Fabricated", + "log.messageId": 430001, + "log.msg": "DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 2001, SrcIP: 198.51.100.7, DstIP: 192.0.2.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, Priority: 2, GID: 1, SID: 1000001, Revision: 1, Message: SERVER-OTHER fabricated example signature, Classification: attempted-user, Client: Web browser, ApplicationProtocol: HTTP, IntrusionPolicy: policy-ips, ACPolicy: policy-a, AccessControlRuleName: rule-a, NAPPolicy: nap-a, InlineResult: Block, SnortRuleGroups: Example>Fabricated, MitreAttackGroups: Example>Fabricated", + "log.severity": "1", + "target.ip": "192.0.2.20", + "target.port": 80, + "target.geolocation.country": "Fabricated Country B", + "target.geolocation.city": "Fabricated City B", + "target.geolocation.latitude": -30.125, + "target.geolocation.longitude": 40.75, + "target.geolocation.asn": 64501, + "target.geolocation.aso": "FABRICATED-AS-64501", + "target.geolocation.countryCode": "XB", + "origin.ip": "198.51.100.7", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country A", + "origin.geolocation.city": "Fabricated City A", + "origin.geolocation.latitude": 10.5, + "origin.geolocation.longitude": -20.25, + "origin.geolocation.asn": 64500, + "origin.geolocation.aso": "FABRICATED-AS-64500", + "origin.geolocation.countryCode": "XA", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430001-p1-webapp": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTP", + "log.Classification": "Web Application Attack", + "log.Client": "Web browser", + "log.ConnectionID": "2001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.GID": "1", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InlineResult": "Block", + "log.InstanceID": "1", + "log.IntrusionPolicy": "policy-ips", + "log.Message": "SERVER-OTHER fabricated example signature", + "log.MitreAttackGroups": "Example>Fabricated", + "log.NAPPolicy": "nap-a", + "log.Priority": "1", + "log.Revision": "1", + "log.SID": "1000001", + "log.SnortRuleGroups": "Example>Fabricated", + "log.messageId": 430001, + "log.msg": "DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 2001, SrcIP: 198.51.100.7, DstIP: 192.0.2.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, Priority: 1, GID: 1, SID: 1000001, Revision: 1, Message: SERVER-OTHER fabricated example signature, Classification: Web Application Attack, Client: Web browser, ApplicationProtocol: HTTP, IntrusionPolicy: policy-ips, ACPolicy: policy-a, AccessControlRuleName: rule-a, NAPPolicy: nap-a, InlineResult: Block, SnortRuleGroups: Example>Fabricated, MitreAttackGroups: Example>Fabricated", + "log.severity": "1", + "target.ip": "192.0.2.20", + "target.port": 80, + "target.geolocation.country": "Fabricated Country B", + "target.geolocation.city": "Fabricated City B", + "target.geolocation.latitude": -30.125, + "target.geolocation.longitude": 40.75, + "target.geolocation.asn": 64501, + "target.geolocation.aso": "FABRICATED-AS-64501", + "target.geolocation.countryCode": "XB", + "origin.ip": "198.51.100.7", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country A", + "origin.geolocation.city": "Fabricated City A", + "origin.geolocation.latitude": 10.5, + "origin.geolocation.longitude": -20.25, + "origin.geolocation.asn": 64500, + "origin.geolocation.aso": "FABRICATED-AS-64500", + "origin.geolocation.countryCode": "XA", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [ + "intrusion_prevention_high_priority_events" + ] + }, + "430001-p2-admin": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTP", + "log.Classification": "Attempted Administrator Privilege Gain", + "log.Client": "Web browser", + "log.ConnectionID": "2001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.GID": "1", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InlineResult": "Block", + "log.InstanceID": "1", + "log.IntrusionPolicy": "policy-ips", + "log.Message": "SERVER-OTHER fabricated example signature", + "log.MitreAttackGroups": "Example>Fabricated", + "log.NAPPolicy": "nap-a", + "log.Priority": "2", + "log.Revision": "1", + "log.SID": "1000001", + "log.SnortRuleGroups": "Example>Fabricated", + "log.messageId": 430001, + "log.msg": "DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 2001, SrcIP: 198.51.100.7, DstIP: 192.0.2.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, Priority: 2, GID: 1, SID: 1000001, Revision: 1, Message: SERVER-OTHER fabricated example signature, Classification: Attempted Administrator Privilege Gain, Client: Web browser, ApplicationProtocol: HTTP, IntrusionPolicy: policy-ips, ACPolicy: policy-a, AccessControlRuleName: rule-a, NAPPolicy: nap-a, InlineResult: Block, SnortRuleGroups: Example>Fabricated, MitreAttackGroups: Example>Fabricated", + "log.severity": "1", + "target.ip": "192.0.2.20", + "target.port": 80, + "target.geolocation.country": "Fabricated Country B", + "target.geolocation.city": "Fabricated City B", + "target.geolocation.latitude": -30.125, + "target.geolocation.longitude": 40.75, + "target.geolocation.asn": 64501, + "target.geolocation.aso": "FABRICATED-AS-64501", + "target.geolocation.countryCode": "XB", + "origin.ip": "198.51.100.7", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country A", + "origin.geolocation.city": "Fabricated City A", + "origin.geolocation.latitude": 10.5, + "origin.geolocation.longitude": -20.25, + "origin.geolocation.asn": 64500, + "origin.geolocation.aso": "FABRICATED-AS-64500", + "origin.geolocation.countryCode": "XA", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [ + "intrusion_prevention_high_priority_events" + ] + }, + "430001-p2-exploitkit": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTP", + "log.Classification": "Exploit Kit Activity Detected", + "log.Client": "Web browser", + "log.ConnectionID": "2001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.GID": "1", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InlineResult": "Block", + "log.InstanceID": "1", + "log.IntrusionPolicy": "policy-ips", + "log.Message": "SERVER-OTHER fabricated example signature", + "log.MitreAttackGroups": "Example>Fabricated", + "log.NAPPolicy": "nap-a", + "log.Priority": "2", + "log.Revision": "1", + "log.SID": "1000001", + "log.SnortRuleGroups": "Example>Fabricated", + "log.messageId": 430001, + "log.msg": "DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 2001, SrcIP: 198.51.100.7, DstIP: 192.0.2.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, Priority: 2, GID: 1, SID: 1000001, Revision: 1, Message: SERVER-OTHER fabricated example signature, Classification: Exploit Kit Activity Detected, Client: Web browser, ApplicationProtocol: HTTP, IntrusionPolicy: policy-ips, ACPolicy: policy-a, AccessControlRuleName: rule-a, NAPPolicy: nap-a, InlineResult: Block, SnortRuleGroups: Example>Fabricated, MitreAttackGroups: Example>Fabricated", + "log.severity": "1", + "target.ip": "192.0.2.20", + "target.port": 80, + "target.geolocation.country": "Fabricated Country B", + "target.geolocation.city": "Fabricated City B", + "target.geolocation.latitude": -30.125, + "target.geolocation.longitude": 40.75, + "target.geolocation.asn": 64501, + "target.geolocation.aso": "FABRICATED-AS-64501", + "target.geolocation.countryCode": "XB", + "origin.ip": "198.51.100.7", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country A", + "origin.geolocation.city": "Fabricated City A", + "origin.geolocation.latitude": 10.5, + "origin.geolocation.longitude": -20.25, + "origin.geolocation.asn": 64500, + "origin.geolocation.aso": "FABRICATED-AS-64500", + "origin.geolocation.countryCode": "XA", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [ + "intrusion_prevention_high_priority_events" + ] + }, + "430001-p2-user": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTP", + "log.Classification": "Attempted User Privilege Gain", + "log.Client": "Web browser", + "log.ConnectionID": "2001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.GID": "1", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InlineResult": "Block", + "log.InstanceID": "1", + "log.IntrusionPolicy": "policy-ips", + "log.Message": "SERVER-OTHER fabricated example signature", + "log.MitreAttackGroups": "Example>Fabricated", + "log.NAPPolicy": "nap-a", + "log.Priority": "2", + "log.Revision": "1", + "log.SID": "1000001", + "log.SnortRuleGroups": "Example>Fabricated", + "log.messageId": 430001, + "log.msg": "DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 2001, SrcIP: 198.51.100.7, DstIP: 192.0.2.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, Priority: 2, GID: 1, SID: 1000001, Revision: 1, Message: SERVER-OTHER fabricated example signature, Classification: Attempted User Privilege Gain, Client: Web browser, ApplicationProtocol: HTTP, IntrusionPolicy: policy-ips, ACPolicy: policy-a, AccessControlRuleName: rule-a, NAPPolicy: nap-a, InlineResult: Block, SnortRuleGroups: Example>Fabricated, MitreAttackGroups: Example>Fabricated", + "log.severity": "1", + "target.ip": "192.0.2.20", + "target.port": 80, + "target.geolocation.country": "Fabricated Country B", + "target.geolocation.city": "Fabricated City B", + "target.geolocation.latitude": -30.125, + "target.geolocation.longitude": 40.75, + "target.geolocation.asn": 64501, + "target.geolocation.aso": "FABRICATED-AS-64501", + "target.geolocation.countryCode": "XB", + "origin.ip": "198.51.100.7", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country A", + "origin.geolocation.city": "Fabricated City A", + "origin.geolocation.latitude": 10.5, + "origin.geolocation.longitude": -20.25, + "origin.geolocation.asn": 64500, + "origin.geolocation.aso": "FABRICATED-AS-64500", + "origin.geolocation.countryCode": "XA", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [ + "intrusion_prevention_high_priority_events" + ] + }, + "430002-block": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Block with reset", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTPS", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "1", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430002, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Block with reset, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 1, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "198.51.100.20", + "target.port": 443, + "target.geolocation.country": "Fabricated Country A", + "target.geolocation.city": "Fabricated City A", + "target.geolocation.latitude": 10.5, + "target.geolocation.longitude": -20.25, + "target.geolocation.asn": 64500, + "target.geolocation.aso": "FABRICATED-AS-64500", + "target.geolocation.countryCode": "XA", + "origin.ip": "192.0.2.10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430002-ssl-start-out": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Block with reset", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "SSL", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "1", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430002, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Block with reset, SrcIP: 192.0.2.10, DstIP: 203.0.113.12, SrcPort: 51000, DstPort: 23123, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: SSL, ConnectionDuration: 12, InitiatorPackets: 1, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "203.0.113.12", + "target.port": 23123, + "target.geolocation": {}, + "origin.ip": "192.0.2.10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [ + "c2_nonstandard_port" + ] + }, + "430003-bad-address": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTPS", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.SrcIP": "not-an-address", + "log.User": "alice", + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: not-an-address, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "198.51.100.20", + "target.port": 443, + "target.geolocation.country": "Fabricated Country A", + "target.geolocation.city": "Fabricated City A", + "target.geolocation.latitude": 10.5, + "target.geolocation.longitude": -20.25, + "target.geolocation.asn": 64500, + "target.geolocation.aso": "FABRICATED-AS-64500", + "target.geolocation.countryCode": "XA", + "origin.port": 51000, + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430003-dns-ttl": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "DNS", + "log.Client": "DNS", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DNSQuery": "host.example.com", + "log.DNSRecordType": "A", + "log.DNSResponseType": "No Error", + "log.DNS_TTL": "300", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.53, SrcPort: 51001, DstPort: 53, Protocol: udp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: DNS, ApplicationProtocol: DNS, DNSQuery: host.example.com, DNSRecordType: A, DNSResponseType: No Error, DNS_TTL: 300, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "198.51.100.53", + "target.port": 53, + "target.geolocation.country": "Fabricated Country A", + "target.geolocation.city": "Fabricated City A", + "target.geolocation.latitude": 10.5, + "target.geolocation.longitude": -20.25, + "target.geolocation.asn": 64500, + "target.geolocation.aso": "FABRICATED-AS-64500", + "target.geolocation.countryCode": "XA", + "origin.ip": "192.0.2.10", + "origin.port": 51001, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "udp", + "severity": "high" + }, + "alerts": [] + }, + "430003-http-8081-out": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTP", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 203.0.113.10, SrcPort: 51000, DstPort: 8081, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTP, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "203.0.113.10", + "target.port": 8081, + "target.geolocation": {}, + "origin.ip": "192.0.2.10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [ + "c2_nonstandard_port" + ] + }, + "430003-http-ipv6-out": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTP", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 2001:db8::10, DstIP: 2001:db8::20, SrcPort: 51000, DstPort: 8081, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTP, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "2001:db8::20", + "target.port": 8081, + "target.geolocation.country": "Fabricated Country C", + "target.geolocation.city": "Fabricated City C", + "target.geolocation.latitude": 1.5, + "target.geolocation.longitude": 2.5, + "target.geolocation.asn": 64502, + "target.geolocation.aso": "FABRICATED-AS-64502", + "target.geolocation.countryCode": "XC", + "origin.ip": "2001:db8::10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country C", + "origin.geolocation.city": "Fabricated City C", + "origin.geolocation.latitude": 1.5, + "origin.geolocation.longitude": 2.5, + "origin.geolocation.asn": 64502, + "origin.geolocation.aso": "FABRICATED-AS-64502", + "origin.geolocation.countryCode": "XC", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [ + "c2_nonstandard_port" + ] + }, + "430003-https": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTPS", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "198.51.100.20", + "target.port": 443, + "target.geolocation.country": "Fabricated Country A", + "target.geolocation.city": "Fabricated City A", + "target.geolocation.latitude": 10.5, + "target.geolocation.longitude": -20.25, + "target.geolocation.asn": 64500, + "target.geolocation.aso": "FABRICATED-AS-64500", + "target.geolocation.countryCode": "XA", + "origin.ip": "192.0.2.10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430003-icmp": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "ICMP", + "log.Client": "ICMP client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.ICMPCode": "No Code", + "log.ICMPType": "Echo Request", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, Protocol: icmp, ICMPType: Echo Request, ICMPCode: No Code, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: ICMP client, ApplicationProtocol: ICMP, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "198.51.100.20", + "target.geolocation.country": "Fabricated Country A", + "target.geolocation.city": "Fabricated City A", + "target.geolocation.latitude": 10.5, + "target.geolocation.longitude": -20.25, + "target.geolocation.asn": 64500, + "target.geolocation.aso": "FABRICATED-AS-64500", + "target.geolocation.countryCode": "XA", + "origin.ip": "192.0.2.10", + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "icmp", + "severity": "high" + }, + "alerts": [] + }, + "430003-ipv6": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTPS", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 2001:db8::10, DstIP: 2001:db8::20, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "2001:db8::20", + "target.port": 443, + "target.geolocation.country": "Fabricated Country C", + "target.geolocation.city": "Fabricated City C", + "target.geolocation.latitude": 1.5, + "target.geolocation.longitude": 2.5, + "target.geolocation.asn": 64502, + "target.geolocation.aso": "FABRICATED-AS-64502", + "target.geolocation.countryCode": "XC", + "origin.ip": "2001:db8::10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country C", + "origin.geolocation.city": "Fabricated City C", + "origin.geolocation.latitude": 1.5, + "origin.geolocation.longitude": 2.5, + "origin.geolocation.asn": 64502, + "origin.geolocation.aso": "FABRICATED-AS-64502", + "origin.geolocation.countryCode": "XC", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430003-near-http-10": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTP", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 10.0.0.20, SrcPort: 51000, DstPort: 8181, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTP, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "10.0.0.20", + "target.port": 8181, + "origin.ip": "192.0.2.10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430003-near-http-192-168": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTP", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 192.168.0.20, SrcPort: 51000, DstPort: 8181, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTP, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "192.168.0.20", + "target.port": 8181, + "origin.ip": "192.0.2.10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430003-near-http-8080": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTP", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 203.0.113.10, SrcPort: 51000, DstPort: 8080, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTP, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "203.0.113.10", + "target.port": 8080, + "target.geolocation": {}, + "origin.ip": "192.0.2.10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430003-near-http-ula": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTP", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 2001:db8::10, DstIP: fd00::20, SrcPort: 51000, DstPort: 8081, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTP, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "fd00::20", + "target.port": 8081, + "target.geolocation": {}, + "origin.ip": "2001:db8::10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country C", + "origin.geolocation.city": "Fabricated City C", + "origin.geolocation.latitude": 1.5, + "origin.geolocation.longitude": 2.5, + "origin.geolocation.asn": 64502, + "origin.geolocation.aso": "FABRICATED-AS-64502", + "origin.geolocation.countryCode": "XC", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430003-near-intrusion-block": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Block", + "log.AccessControlRuleName": "rule-a", + "log.AccessControlRuleReason": "Intrusion Block", + "log.ApplicationProtocol": "HTTP", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "High", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430003, + "log.msg": "EventPriority: High, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Block, AccessControlRuleReason: Intrusion Block, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTP, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "198.51.100.20", + "target.port": 80, + "target.geolocation.country": "Fabricated Country A", + "target.geolocation.city": "Fabricated City A", + "target.geolocation.latitude": 10.5, + "target.geolocation.longitude": -20.25, + "target.geolocation.asn": 64500, + "target.geolocation.aso": "FABRICATED-AS-64500", + "target.geolocation.countryCode": "XA", + "origin.ip": "192.0.2.10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430003-near-no-ports": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTP", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 203.0.113.15, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTP, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "203.0.113.15", + "target.geolocation": {}, + "origin.ip": "192.0.2.10", + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430003-near-ssl-172": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "SSL", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 172.16.0.20, SrcPort: 51000, DstPort: 9001, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: SSL, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "172.16.0.20", + "target.port": 9001, + "origin.ip": "192.0.2.10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430003-near-ssl-993": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "SSL", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 203.0.113.10, SrcPort: 51000, DstPort: 993, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: SSL, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "203.0.113.10", + "target.port": 993, + "target.geolocation": {}, + "origin.ip": "192.0.2.10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430003-near-unknown-app": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "Unknown", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 203.0.113.13, SrcPort: 51000, DstPort: 4444, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: Unknown, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "203.0.113.13", + "target.port": 4444, + "target.geolocation": {}, + "origin.ip": "192.0.2.10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430003-near-zero-packets": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTP", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "0", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 203.0.113.10, SrcPort: 51000, DstPort: 8081, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTP, ConnectionDuration: 12, InitiatorPackets: 0, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "203.0.113.10", + "target.port": 8081, + "target.geolocation": {}, + "origin.ip": "192.0.2.10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430003-space-after-pri": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTPS", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "198.51.100.20", + "target.port": 443, + "target.geolocation.country": "Fabricated Country A", + "target.geolocation.city": "Fabricated City A", + "target.geolocation.latitude": 10.5, + "target.geolocation.longitude": -20.25, + "target.geolocation.asn": 64500, + "target.geolocation.aso": "FABRICATED-AS-64500", + "target.geolocation.countryCode": "XA", + "origin.ip": "192.0.2.10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430003-ssl-9001-out": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "SSL", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.30, SrcPort: 51000, DstPort: 9001, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: SSL, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "198.51.100.30", + "target.port": 9001, + "target.geolocation.country": "Fabricated Country A", + "target.geolocation.city": "Fabricated City A", + "target.geolocation.latitude": 10.5, + "target.geolocation.longitude": -20.25, + "target.geolocation.asn": 64500, + "target.geolocation.aso": "FABRICATED-AS-64500", + "target.geolocation.countryCode": "XA", + "origin.ip": "192.0.2.10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [ + "c2_nonstandard_port" + ] + }, + "430003-useragent": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTP", + "log.Client": "Example Client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.URL": "http://www.example.com/index.html", + "log.User": "alice", + "log.UserAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Example/1.0", + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, UserAgent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Example/1.0, Client: Example Client, ApplicationProtocol: HTTP, URL: http://www.example.com/index.html, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "198.51.100.20", + "target.port": 80, + "target.geolocation.country": "Fabricated Country A", + "target.geolocation.city": "Fabricated City A", + "target.geolocation.latitude": 10.5, + "target.geolocation.longitude": -20.25, + "target.geolocation.asn": 64500, + "target.geolocation.aso": "FABRICATED-AS-64500", + "target.geolocation.countryCode": "XA", + "origin.ip": "192.0.2.10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430003-useragent-spoof": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.ApplicationProtocol": "HTTP", + "log.Client": "Example Client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.UserAgent": "Mozilla/5.0 (X11, SrcIP: 203.0.113.66, DstPort: 4444, AccessControlRuleAction: Block, User: mallory)", + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, UserAgent: Mozilla/5.0 (X11, SrcIP: 203.0.113.66, DstPort: 4444, AccessControlRuleAction: Block, User: mallory), Client: Example Client, ApplicationProtocol: HTTP, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "198.51.100.20", + "target.port": 80, + "target.geolocation.country": "Fabricated Country A", + "target.geolocation.city": "Fabricated City A", + "target.geolocation.latitude": 10.5, + "target.geolocation.longitude": -20.25, + "target.geolocation.asn": 64500, + "target.geolocation.aso": "FABRICATED-AS-64500", + "target.geolocation.countryCode": "XA", + "origin.ip": "192.0.2.10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430007-elephant-inside": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.AccessControlRuleReason": "Elephant Flow", + "log.ApplicationProtocol": "SSL", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430007, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, AccessControlRuleReason: Elephant Flow, SrcIP: 192.0.2.10, DstIP: 10.0.0.30, SrcPort: 51000, DstPort: 9001, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: SSL, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "10.0.0.30", + "target.port": 9001, + "origin.ip": "192.0.2.10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [] + }, + "430007-ssl-elephant-out": { + "logObject": true, + "fields": { + "log.ACPolicy": "policy-a", + "log.AccessControlRuleAction": "Allow", + "log.AccessControlRuleName": "rule-a", + "log.AccessControlRuleReason": "Elephant Flow", + "log.ApplicationProtocol": "SSL", + "log.Client": "SSL client", + "log.ConnectionDuration": "12", + "log.ConnectionID": "1001", + "log.DeviceUUID": "00000000-0000-4000-8000-00000000f7d1", + "log.EgressInterface": "out1", + "log.EgressZone": "zone-b", + "log.EventPriority": "Low", + "log.FirstPacketSecond": "2026-09-23T10:00:00Z", + "log.IngressInterface": "in1", + "log.IngressZone": "zone-a", + "log.InitiatorBytes": "1200", + "log.InitiatorPackets": "6", + "log.InstanceID": "1", + "log.NAPPolicy": "nap-a", + "log.PrefilterPolicy": "Example Prefilter Policy", + "log.ResponderBytes": "5400", + "log.ResponderPackets": "12", + "log.User": "alice", + "log.messageId": 430007, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, AccessControlRuleReason: Elephant Flow, SrcIP: 192.0.2.10, DstIP: 203.0.113.16, SrcPort: 51000, DstPort: 9001, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: SSL, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "target.ip": "203.0.113.16", + "target.port": 9001, + "target.geolocation": {}, + "origin.ip": "192.0.2.10", + "origin.port": 51000, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "protocol": "tcp", + "severity": "high" + }, + "alerts": [ + "c2_nonstandard_port" + ] + }, + "glued-text": { + "logObject": true, + "fields": { + "log.messageId": 430003, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.1<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1002, AccessControlRuleAction: Allow, SrcIP: 192.0.2.11, DstIP: 198.51.100.21, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "severity": "high" + }, + "alerts": [] + }, + "glued-text-second-intrusion": { + "logObject": true, + "fields": { + "log.messageId": 430002, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, <113>%FTD-1-430001: DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 2001, SrcIP: 198.51.100.7, DstIP: 192.0.2.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, Priority: 1, GID: 1, SID: 1000001, Revision: 1, Message: SERVER-OTHER fabricated example signature, Classification: Web Application Attack, Client: Web browser, ApplicationProtocol: HTTP, IntrusionPolicy: policy-ips, ACPolicy: policy-a, AccessControlRuleName: rule-a, NAPPolicy: nap-a, InlineResult: Block, SnortRuleGroups: Example>Fabricated, MitreAttackGroups: Example>Fabricated", + "log.severity": "1", + "severity": "high" + }, + "alerts": [] + }, + "scope-430005": { + "logObject": true, + "fields": { + "log.messageId": 430005, + "log.msg": "DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, eventType: MALWARE_EVENT, disposition: MALWARE, SrcIP: 192.0.2.10, DstIP: 203.0.113.22, SrcPort: 51000, DstPort: 443, Protocol: tcp, FileName: sample.exe", + "log.severity": "1", + "severity": "high" + }, + "alerts": [] + }, + "scope-430008": { + "logObject": true, + "fields": { + "log.messageId": 430008, + "log.msg": "EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "log.severity": "1", + "severity": "high" + }, + "alerts": [] + }, + "unparsed-asa-prefix": { + "logObject": false, + "fields": {}, + "alerts": [] + }, + "unparsed-glued-header": { + "logObject": false, + "fields": {}, + "alerts": [] + }, + "unparsed-linux-sshd": { + "logObject": false, + "fields": {}, + "alerts": [] + }, + "unparsed-no-pri": { + "logObject": false, + "fields": {}, + "alerts": [] + }, + "unparsed-no-space": { + "logObject": false, + "fields": {}, + "alerts": [] + } + } +} diff --git a/plugins/alerts/testdata/cisco-firepower/geolocation-data/asn-blocks-v4.csv b/plugins/alerts/testdata/cisco-firepower/geolocation-data/asn-blocks-v4.csv new file mode 100644 index 000000000..8c9cc44d5 --- /dev/null +++ b/plugins/alerts/testdata/cisco-firepower/geolocation-data/asn-blocks-v4.csv @@ -0,0 +1,3 @@ +network,autonomous_system_number,autonomous_system_organization +198.51.100.0/24,64500,FABRICATED-AS-64500 +192.0.2.0/24,64501,FABRICATED-AS-64501 diff --git a/plugins/alerts/testdata/cisco-firepower/geolocation-data/asn-blocks-v6.csv b/plugins/alerts/testdata/cisco-firepower/geolocation-data/asn-blocks-v6.csv new file mode 100644 index 000000000..785fac510 --- /dev/null +++ b/plugins/alerts/testdata/cisco-firepower/geolocation-data/asn-blocks-v6.csv @@ -0,0 +1,2 @@ +network,autonomous_system_number,autonomous_system_organization +2001:db8::/32,64502,FABRICATED-AS-64502 diff --git a/plugins/alerts/testdata/cisco-firepower/geolocation-data/blocks-v4.csv b/plugins/alerts/testdata/cisco-firepower/geolocation-data/blocks-v4.csv new file mode 100644 index 000000000..f78499b6f --- /dev/null +++ b/plugins/alerts/testdata/cisco-firepower/geolocation-data/blocks-v4.csv @@ -0,0 +1,3 @@ +network,geoname_id,registered_country_geoname_id,represented_country_geoname_id,is_anonymous_proxy,is_satellite_provider,postal_code,latitude,longitude,accuracy_radius +198.51.100.0/24,9900001,9900001,,0,0,,10.5,-20.25,100 +192.0.2.0/24,9900002,9900002,,0,0,,-30.125,40.75,50 diff --git a/plugins/alerts/testdata/cisco-firepower/geolocation-data/blocks-v6.csv b/plugins/alerts/testdata/cisco-firepower/geolocation-data/blocks-v6.csv new file mode 100644 index 000000000..77cfed2c7 --- /dev/null +++ b/plugins/alerts/testdata/cisco-firepower/geolocation-data/blocks-v6.csv @@ -0,0 +1,2 @@ +network,geoname_id,registered_country_geoname_id,represented_country_geoname_id,is_anonymous_proxy,is_satellite_provider,postal_code,latitude,longitude,accuracy_radius +2001:db8::/32,9900003,9900003,,0,0,,1.5,2.5,1000 diff --git a/plugins/alerts/testdata/cisco-firepower/geolocation-data/locations-en.csv b/plugins/alerts/testdata/cisco-firepower/geolocation-data/locations-en.csv new file mode 100644 index 000000000..590e302c2 --- /dev/null +++ b/plugins/alerts/testdata/cisco-firepower/geolocation-data/locations-en.csv @@ -0,0 +1,4 @@ +geoname_id,locale_code,continent_code,continent_name,country_iso_code,country_name,subdivision_1_iso_code,subdivision_1_name,subdivision_2_iso_code,subdivision_2_name,city_name,metro_code,time_zone,is_in_european_union +9900001,en,XX,Fabricated,XA,Fabricated Country A,,,,,Fabricated City A,,Etc/UTC,0 +9900002,en,XX,Fabricated,XB,Fabricated Country B,,,,,Fabricated City B,,Etc/UTC,0 +9900003,en,XX,Fabricated,XC,Fabricated Country C,,,,,Fabricated City C,,Etc/UTC,0 diff --git a/plugins/alerts/testdata/cisco-firepower/patterns.yaml b/plugins/alerts/testdata/cisco-firepower/patterns.yaml new file mode 100644 index 000000000..eb37af3be --- /dev/null +++ b/plugins/alerts/testdata/cisco-firepower/patterns.yaml @@ -0,0 +1,16 @@ +# The shared grok definitions this filter uses, copied from +# backend/src/main/resources/config/liquibase/changelog/20250616001_insert_utm_regex_pattern.xml. +patterns: + data: '(.*?)' + day: '(?:Mon(?:day)?|Tue(?:sday)?|Wed(?:nesday)?|Thu(?:rsday)?|Fri(?:day)?|Sat(?:urday)?|Sun(?:day)?)' + greedy: '.*' + hostname: '(\b(?:[0-9A-Za-z][0-9A-Za-z-]{0,62})(?:\.(?:[0-9A-Za-z][0-9A-Za-z-]{0,62}))*(\.?|\b))' + integer: '(?:[+-]?(?:[0-9]+))' + ipv4: '(((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.)){3}((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)))' + ipv6: '([0-9a-fA-F]{1,4}(:[0-9a-fA-F]{0,4}){1,7}|::[0-1]?)' + monthDay: '(?:(?:0[1-9])|(?:[12][0-9])|(?:3[01])|[1-9])' + monthName: '\b(?:[Jj]an(?:uary|uar)?|[Ff]eb(?:ruary|ruar)?|[Mm](?:a|ä)?r(?:ch|z)?|[Aa]pr(?:il)?|[Mm]a(?:y|i)?|[Jj]un(?:e|i)?|[Jj]ul(?:y|i)?|[Aa]ug(?:ust)?|[Ss]ep(?:tember)?|[Oo](?:c|k)?t(?:ober)?|[Nn]ov(?:ember)?|[Dd]e(?:c|z)(?:ember)?)\b' + space: '\s+' + time: '((([01][0-9])|2[0-4]):(?:[0-5][0-9])(?::(?:(?:[0-5]?[0-9]|60)(?:[:.,][0-9]+)?)))' + word: '\b\w+\b' + year: '(([1-9])[0-9]{1,3})' diff --git a/plugins/alerts/testdata/cisco-firepower/raw.json b/plugins/alerts/testdata/cisco-firepower/raw.json new file mode 100644 index 000000000..cce278b90 --- /dev/null +++ b/plugins/alerts/testdata/cisco-firepower/raw.json @@ -0,0 +1,63 @@ +{ + "provenance": "FABRICATED. Cisco's documentation could not be read (the site refused automated access). The 4300xx lines follow the key layout of genuine Firepower Threat Defense records that were reviewed privately and are not part of this repository; no value is copied from them. The LINA lines follow the filter's own patterns. Addresses are RFC 5737 and RFC 3849 documentation addresses, except the RFC 1918 and RFC 4193 private addresses in the private-destination near misses of the non-standard-port rule; device, user, zone, rule and policy names are examples and the device UUID is made up.", + "cases": { + "106102-denied": "<166>Wed Sep 23 2026 10:00:00 ftd01.example.com : %FTD-6-106102: access-list outside_in denied tcp for user alice outside/198.51.100.7(51234) -> inside/192.0.2.10(443) hit-cnt 1 first hit [0x1a2b, 0x0]", + "106102-permitted": "<166>Wed Sep 23 2026 10:00:00 ftd01.example.com : %FTD-6-106102: access-list outside_in permitted tcp for user alice outside/198.51.100.7 51234 inside/192.0.2.10 443 hit-cnt 1 first hit [0x1a2b, 0x0]", + "109201-uauth": "<166>Wed Sep 23 2026 10:00:00 ftd01.example.com : %FTD-5-109201: UAUTH: Session=0x1a2b, User=alice, Assigned IP=192.0.2.10, Succeeded adding entry.", + "113009-with-equals": "<166>Wed Sep 23 2026 10:00:00 ftd01.example.com : %FTD-6-113009: AAA retrieved default group policy (DfltGrpPolicy) for user = alice", + "113009-without-equals": "<166>Wed Sep 23 2026 10:00:00 ftd01.example.com : %FTD-6-113009: AAA retrieved default group policy (DfltGrpPolicy) for user alice", + "113011-with-equals": "<166>Wed Sep 23 2026 10:00:00 ftd01.example.com : %FTD-6-113011: AAA retrieved user specific group policy (GP1) for user = alice", + "302013-header-bsd": "<166>Wed Sep 23 2026 10:00:00 ftd01.example.com : %FTD-6-302013: Built inbound TCP connection 1 for outside:198.51.100.7/51234 (198.51.100.7/51234) to inside:192.0.2.10/443 (203.0.113.5/443)", + "302013-header-device-ipv4": "<166>Wed Sep 23 2026 10:00:00 192.0.2.1 : %FTD-6-302013: Built inbound TCP connection 1 for outside:198.51.100.7/51234 (198.51.100.7/51234) to inside:192.0.2.10/443 (203.0.113.5/443)", + "302013-header-no-pri": "Wed Sep 23 2026 10:00:00 ftd01.example.com : %FTD-6-302013: Built inbound TCP connection 1 for outside:198.51.100.7/51234 (198.51.100.7/51234) to inside:192.0.2.10/443 (203.0.113.5/443)", + "302013-outbound": "<166>Wed Sep 23 2026 10:00:00 ftd01.example.com : %FTD-6-302013: Built outbound TCP connection 7 for outside:198.51.100.7/443 (198.51.100.7/443) to inside:192.0.2.10/51234 (203.0.113.5/51234)", + "302013-real-header": "<166>%FTD-6-302013: Built inbound TCP connection 1 for outside:198.51.100.7/51234 (198.51.100.7/51234) to inside:192.0.2.10/443 (203.0.113.5/443)", + "302017-gre": "<166>Wed Sep 23 2026 10:00:00 ftd01.example.com : %FTD-6-302017: Built inbound GRE connection 12 from outside:198.51.100.7 (198.51.100.7) (carol) to inside:192.0.2.10/0 (203.0.113.5/0) (dave) (erin)", + "302020-global": "<166>Wed Sep 23 2026 10:00:00 ftd01.example.com : %FTD-6-302020: Built inbound ICMP connection for faddr 198.51.100.7/0 gaddr 192.0.2.5/0 laddr 192.0.2.10/0 type 8 code 0", + "302304-teardown": "<166>Wed Sep 23 2026 10:00:00 ftd01.example.com : %FTD-6-302304: Teardown TCP state-bypass connection 5 from outside:198.51.100.7/51234 to inside:192.0.2.10/443 00:00:05, 1024, TCP FINs", + "305011-built": "<166>Wed Sep 23 2026 10:00:00 ftd01.example.com : %FTD-6-305011: Built dynamic TCP translation from inside:192.0.2.10/51234 to outside:203.0.113.5/40001", + "305012-teardown": "<166>Wed Sep 23 2026 10:00:00 ftd01.example.com : %FTD-6-305012: Teardown dynamic TCP translation from inside:192.0.2.10/51234 to outside:203.0.113.5/40001 duration 00:00:05", + "430001-http-8081-out": "<113>%FTD-1-430001: DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 2001, SrcIP: 192.0.2.10, DstIP: 203.0.113.10, SrcPort: 51000, DstPort: 8081, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, Priority: 1, GID: 1, SID: 1000001, Revision: 1, Message: SERVER-OTHER fabricated example signature, Classification: Web Application Attack, Client: Web browser, ApplicationProtocol: HTTP, IntrusionPolicy: policy-ips, ACPolicy: policy-a, AccessControlRuleName: rule-a, NAPPolicy: nap-a, InlineResult: Block, SnortRuleGroups: Example>Fabricated, MitreAttackGroups: Example>Fabricated", + "430001-near-p2-misc": "<113>%FTD-1-430001: DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 2001, SrcIP: 198.51.100.7, DstIP: 192.0.2.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, Priority: 2, GID: 1, SID: 1000001, Revision: 1, Message: SERVER-OTHER fabricated example signature, Classification: Misc Attack, Client: Web browser, ApplicationProtocol: HTTP, IntrusionPolicy: policy-ips, ACPolicy: policy-a, AccessControlRuleName: rule-a, NAPPolicy: nap-a, InlineResult: Block, SnortRuleGroups: Example>Fabricated, MitreAttackGroups: Example>Fabricated", + "430001-near-p3": "<113>%FTD-1-430001: DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 2001, SrcIP: 198.51.100.7, DstIP: 192.0.2.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, Priority: 3, GID: 1, SID: 1000001, Revision: 1, Message: SERVER-OTHER fabricated example signature, Classification: Potential Corporate Privacy Violation, Client: Web browser, ApplicationProtocol: HTTP, IntrusionPolicy: policy-ips, ACPolicy: policy-a, AccessControlRuleName: rule-a, NAPPolicy: nap-a, InlineResult: Block, SnortRuleGroups: Example>Fabricated, MitreAttackGroups: Example>Fabricated", + "430001-near-short-class": "<113>%FTD-1-430001: DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 2001, SrcIP: 198.51.100.7, DstIP: 192.0.2.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, Priority: 2, GID: 1, SID: 1000001, Revision: 1, Message: SERVER-OTHER fabricated example signature, Classification: attempted-user, Client: Web browser, ApplicationProtocol: HTTP, IntrusionPolicy: policy-ips, ACPolicy: policy-a, AccessControlRuleName: rule-a, NAPPolicy: nap-a, InlineResult: Block, SnortRuleGroups: Example>Fabricated, MitreAttackGroups: Example>Fabricated", + "430001-p1-webapp": "<113>%FTD-1-430001: DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 2001, SrcIP: 198.51.100.7, DstIP: 192.0.2.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, Priority: 1, GID: 1, SID: 1000001, Revision: 1, Message: SERVER-OTHER fabricated example signature, Classification: Web Application Attack, Client: Web browser, ApplicationProtocol: HTTP, IntrusionPolicy: policy-ips, ACPolicy: policy-a, AccessControlRuleName: rule-a, NAPPolicy: nap-a, InlineResult: Block, SnortRuleGroups: Example>Fabricated, MitreAttackGroups: Example>Fabricated", + "430001-p2-admin": "<113>%FTD-1-430001: DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 2001, SrcIP: 198.51.100.7, DstIP: 192.0.2.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, Priority: 2, GID: 1, SID: 1000001, Revision: 1, Message: SERVER-OTHER fabricated example signature, Classification: Attempted Administrator Privilege Gain, Client: Web browser, ApplicationProtocol: HTTP, IntrusionPolicy: policy-ips, ACPolicy: policy-a, AccessControlRuleName: rule-a, NAPPolicy: nap-a, InlineResult: Block, SnortRuleGroups: Example>Fabricated, MitreAttackGroups: Example>Fabricated", + "430001-p2-exploitkit": "<113>%FTD-1-430001: DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 2001, SrcIP: 198.51.100.7, DstIP: 192.0.2.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, Priority: 2, GID: 1, SID: 1000001, Revision: 1, Message: SERVER-OTHER fabricated example signature, Classification: Exploit Kit Activity Detected, Client: Web browser, ApplicationProtocol: HTTP, IntrusionPolicy: policy-ips, ACPolicy: policy-a, AccessControlRuleName: rule-a, NAPPolicy: nap-a, InlineResult: Block, SnortRuleGroups: Example>Fabricated, MitreAttackGroups: Example>Fabricated", + "430001-p2-user": "<113>%FTD-1-430001: DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 2001, SrcIP: 198.51.100.7, DstIP: 192.0.2.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, Priority: 2, GID: 1, SID: 1000001, Revision: 1, Message: SERVER-OTHER fabricated example signature, Classification: Attempted User Privilege Gain, Client: Web browser, ApplicationProtocol: HTTP, IntrusionPolicy: policy-ips, ACPolicy: policy-a, AccessControlRuleName: rule-a, NAPPolicy: nap-a, InlineResult: Block, SnortRuleGroups: Example>Fabricated, MitreAttackGroups: Example>Fabricated", + "430002-block": "<113>%FTD-1-430002: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Block with reset, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 1, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430002-ssl-start-out": "<113>%FTD-1-430002: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Block with reset, SrcIP: 192.0.2.10, DstIP: 203.0.113.12, SrcPort: 51000, DstPort: 23123, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: SSL, ConnectionDuration: 12, InitiatorPackets: 1, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-bad-address": "<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: not-an-address, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-dns-ttl": "<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.53, SrcPort: 51001, DstPort: 53, Protocol: udp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: DNS, ApplicationProtocol: DNS, DNSQuery: host.example.com, DNSRecordType: A, DNSResponseType: No Error, DNS_TTL: 300, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-http-8081-out": "<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 203.0.113.10, SrcPort: 51000, DstPort: 8081, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTP, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-http-ipv6-out": "<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 2001:db8::10, DstIP: 2001:db8::20, SrcPort: 51000, DstPort: 8081, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTP, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-https": "<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-icmp": "<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, Protocol: icmp, ICMPType: Echo Request, ICMPCode: No Code, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: ICMP client, ApplicationProtocol: ICMP, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-ipv6": "<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 2001:db8::10, DstIP: 2001:db8::20, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-near-http-10": "<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 10.0.0.20, SrcPort: 51000, DstPort: 8181, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTP, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-near-http-192-168": "<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 192.168.0.20, SrcPort: 51000, DstPort: 8181, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTP, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-near-http-8080": "<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 203.0.113.10, SrcPort: 51000, DstPort: 8080, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTP, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-near-http-ula": "<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 2001:db8::10, DstIP: fd00::20, SrcPort: 51000, DstPort: 8081, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTP, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-near-intrusion-block": "<113>%FTD-1-430003: EventPriority: High, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Block, AccessControlRuleReason: Intrusion Block, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTP, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-near-no-ports": "<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 203.0.113.15, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTP, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-near-ssl-172": "<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 172.16.0.20, SrcPort: 51000, DstPort: 9001, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: SSL, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-near-ssl-993": "<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 203.0.113.10, SrcPort: 51000, DstPort: 993, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: SSL, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-near-unknown-app": "<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 203.0.113.13, SrcPort: 51000, DstPort: 4444, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: Unknown, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-near-zero-packets": "<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 203.0.113.10, SrcPort: 51000, DstPort: 8081, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTP, ConnectionDuration: 12, InitiatorPackets: 0, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-space-after-pri": "<113> %FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-ssl-9001-out": "<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.30, SrcPort: 51000, DstPort: 9001, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: SSL, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-useragent": "<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, UserAgent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Example/1.0, Client: Example Client, ApplicationProtocol: HTTP, URL: http://www.example.com/index.html, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430003-useragent-spoof": "<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, UserAgent: Mozilla/5.0 (X11, SrcIP: 203.0.113.66, DstPort: 4444, AccessControlRuleAction: Block, User: mallory), Client: Example Client, ApplicationProtocol: HTTP, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430007-elephant-inside": "<113>%FTD-1-430007: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, AccessControlRuleReason: Elephant Flow, SrcIP: 192.0.2.10, DstIP: 10.0.0.30, SrcPort: 51000, DstPort: 9001, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: SSL, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "430007-ssl-elephant-out": "<113>%FTD-1-430007: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, AccessControlRuleReason: Elephant Flow, SrcIP: 192.0.2.10, DstIP: 203.0.113.16, SrcPort: 51000, DstPort: 9001, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: SSL, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "glued-text": "<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.1<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1002, AccessControlRuleAction: Allow, SrcIP: 192.0.2.11, DstIP: 198.51.100.21, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "glued-text-second-intrusion": "<113>%FTD-1-430002: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, <113>%FTD-1-430001: DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 2001, SrcIP: 198.51.100.7, DstIP: 192.0.2.20, SrcPort: 51000, DstPort: 80, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, Priority: 1, GID: 1, SID: 1000001, Revision: 1, Message: SERVER-OTHER fabricated example signature, Classification: Web Application Attack, Client: Web browser, ApplicationProtocol: HTTP, IntrusionPolicy: policy-ips, ACPolicy: policy-a, AccessControlRuleName: rule-a, NAPPolicy: nap-a, InlineResult: Block, SnortRuleGroups: Example>Fabricated, MitreAttackGroups: Example>Fabricated", + "scope-430005": "<113>%FTD-1-430005: DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, eventType: MALWARE_EVENT, disposition: MALWARE, SrcIP: 192.0.2.10, DstIP: 203.0.113.22, SrcPort: 51000, DstPort: 443, Protocol: tcp, FileName: sample.exe", + "scope-430008": "<113>%FTD-1-430008: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "unparsed-asa-prefix": "<113>%ASA-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "unparsed-glued-header": "<113>%FTD-1-430<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "unparsed-linux-sshd": "<86>Sep 23 10:00:00 host01.example.com sshd[1234]: Accepted publickey for alice from 192.0.2.10 port 51000 ssh2", + "unparsed-no-pri": "%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "unparsed-no-space": "<113>%FTD-1-430003:EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a" + } +} diff --git a/plugins/alerts/testdata/cisco-firepower/replay.py b/plugins/alerts/testdata/cisco-firepower/replay.py new file mode 100644 index 000000000..5b3c532da --- /dev/null +++ b/plugins/alerts/testdata/cisco-firepower/replay.py @@ -0,0 +1,221 @@ +"""Replay fabricated Cisco Firepower raw lines through separately built EventProcessor binaries. + +Requires PyYAML. This does not build or deploy anything and uses only local file writers. +It stages the current filter, the shared grok definitions (patterns.yaml), fabricated +geolocation data and the five Cisco Firepower rules, runs the playground once, and checks every +event against expected.json and every local alert against its expected rule. Every input is +fabricated. See filters/audits/cisco-firepower.md. + +The non-standard-port rule has a history search, which needs OpenSearch. None runs here: the +OpenSearch address is a closed local port. That rule is therefore staged as a test-only copy of +its condition without the history search, under a suffixed name, so the lines that meet the +condition raise a local alert instead of a failed search. The history search itself is not +executed. Any rule that still reaches a history search fails the run. + +Written for EventProcessor commit 8a3ade72bd9d12db21f6b273200588fb49540f14, whose CEL plugin +reads the OpenSearch host and port as separate settings. +""" +import argparse +import errno +import hashlib +import json +import os +from pathlib import Path +import shutil +import socket +import subprocess +import tempfile + +import yaml + +PLUGINS = ("add", "cast", "cel", "delete", "grok", "kv", "rename", "saw", "sew", "trim") +GEOLOCATION = "com.utmstack.geolocation.plugin" +TENANT = "00000000-0000-4000-8000-000000000001" +ENVELOPE_TIMESTAMP = "2026-09-23T14:00:00Z" +ENVELOPE = ("id", "timestamp", "deviceTime", "dataType", "dataSource", "tenantId", "tenantName", "raw", "errors") +CONDITION_ONLY = " (condition only, test copy)" +LOG_FAILURES = ("failed to unmarshal", "failed to evaluate rule", "failed to execute correlation search", + "plugin not found", "failed to compile", "failed to start plugin", + "failed to convert log to event", "failed to parse log", "all retries failed", "panic") + + +def records(path): + # The playground writers can append adjacent JSON objects before newlines. + content = path.read_text() if path.exists() else "" + decoder = json.JSONDecoder() + result, offset = [], 0 + while offset < len(content): + while offset < len(content) and content[offset].isspace(): + offset += 1 + if offset < len(content): + record, offset = decoder.raw_decode(content, offset) + result.append(record) + return result + + +def require(condition, message): + if not condition: + raise RuntimeError(message) + + +def flatten(value, prefix=""): + """Dotted paths of every leaf; an empty object is kept as a leaf.""" + out = {} + if isinstance(value, dict) and (value or not prefix): + for key, item in value.items(): + out.update(flatten(item, f"{prefix}.{key}" if prefix else key)) + else: + out[prefix] = value + return out + + +def fields(event): + return flatten({k: v for k, v in event.items() if k not in ENVELOPE}) + + +def closed_port(): + # A port nothing listens on: bind an ephemeral port, then release it. + with socket.socket() as probe: + probe.bind(("127.0.0.1", 0)) + return probe.getsockname()[1] + + +def place(source, target): + target.parent.mkdir(parents=True, exist_ok=True) + try: + os.link(source, target) + except OSError as error: + if error.errno != errno.EXDEV: + raise + shutil.copy2(source, target) + + +def run(playground, plugins, geolocation_plugin): + """Stage everything in a fresh private directory, run the playground, return its results.""" + fixture_dir = Path(__file__).resolve().parent + root = fixture_dir.parents[3] + os.umask(0o077) + work = Path(tempfile.mkdtemp(prefix="fp-pg-", dir="/tmp")) + print(f"Local evidence directory: {work}", flush=True) + for part in ("input", "output", "pipeline/filters", "rules", "plugins", "sockets", "geolocation"): + (work / part).mkdir(parents=True, exist_ok=True) + binaries = {"playground": playground.resolve()} + for name in PLUGINS: + source = (plugins / f"{name}.plugin").resolve() + require(source.is_file(), f"Missing binary: {source}") + place(source, work / "plugins" / source.name) + binaries[name] = source + source = geolocation_plugin.resolve() + require(source.is_file(), f"Missing binary: {source}") + place(source, work / "plugins" / "utmstack" / GEOLOCATION) + binaries["geolocation"] = source + port = closed_port() + config = { + "tenants": [{"id": TENANT, "name": "fixture"}], + "plugins": { + "analysis": {"order": ["sew", "cel"]}, + "correlation": {"order": ["saw"]}, + "notification": {"order": []}, + # CEL builds a client at start-up without connecting; nothing listens here. + "org.opensearch": {"opensearch": {"host": "127.0.0.1", "port": str(port), "user": "", "password": ""}}, + }, + } + (work / "pipeline/config.yaml").write_text(yaml.safe_dump(config)) + shutil.copy2(fixture_dir / "patterns.yaml", work / "pipeline/patterns.yaml") + filter_path = root / "filters/cisco/firepower.yml" + shutil.copy2(filter_path, work / "pipeline/filters/firepower.yaml") + # Not named geolocation/: the repository ignores directories with that name. + geo_paths = sorted((fixture_dir / "geolocation-data").glob("*.csv")) + require(len(geo_paths) == 5, f"Expected 5 geolocation files, found {len(geo_paths)}") + for path in geo_paths: + shutil.copy2(path, work / "geolocation" / path.name) + rule_paths = sorted((root / "rules/cisco/firepower").glob("*.y*ml")) + require(len(rule_paths) == 5, f"Expected 5 Cisco Firepower rules, found {len(rule_paths)}") + stems, condition_only = {}, [] + for offset, path in enumerate(rule_paths): + rule = yaml.safe_load(path.read_text()) + require(isinstance(rule, dict) and "id" not in rule, f"Unexpected rule shape: {path.name}") + rule["id"] = 9001 + offset # the playground loader needs unique non-zero ids + if rule.pop("afterEvents", None) is not None or rule.pop("correlation", None) is not None: + rule["name"] += CONDITION_ONLY + condition_only.append(path.stem) + stems[rule["name"]] = path.stem + (work / "rules" / f"{rule['id']}-{path.stem}.yaml").write_text(yaml.safe_dump([rule])) + cases = json.loads((fixture_dir / "raw.json").read_text())["cases"] + inputs = {} + for number, name in enumerate(sorted(cases)): + event_id = f"cisco-firepower-{name}" + inputs[event_id] = (name, cases[name]) + event = {"id": event_id, "dataType": "firewall-cisco-firepower", "dataSource": "fixture-ftd", + "@timestamp": ENVELOPE_TIMESTAMP, "tenantId": TENANT, "raw": cases[name]} + (work / "input" / f"{number:03d}.json").write_text(json.dumps(event)) + hashed = [filter_path, *rule_paths, *geo_paths] + [fixture_dir / n for n in ("patterns.yaml", "raw.json")] + manifest = { + "provenance": "fabricated raw inputs; no customer data", + "historyBackend": f"none: http://127.0.0.1:{port} is closed", + "conditionOnlyTestCopies": condition_only, + "sourceHashes": {str(p.relative_to(root)): hashlib.sha256(p.read_bytes()).hexdigest() for p in hashed}, + "binaries": {name: { + "sha256": hashlib.sha256(path.read_bytes()).hexdigest(), + "buildInfo": subprocess.check_output(["go", "version", "-m", str(path)], text=True), + } for name, path in binaries.items()}, + } + (work / "manifest.json").write_text(json.dumps(manifest, indent=2)) + env = dict(os.environ, WORK_DIR=str(work), MODE="playground") + with (work / "execution.log").open("w") as log: + subprocess.run([str(binaries["playground"])], env=env, stdout=log, + stderr=subprocess.STDOUT, check=True, timeout=900) + log_text = (work / "execution.log").read_text() + events = records(work / "output/resulting_log.json") + alerts = records(work / "output/resulting_alert.json") + return work, inputs, stems, events, alerts, log_text + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--playground", required=True, type=Path) + parser.add_argument("--plugins", required=True, type=Path) + parser.add_argument("--geolocation-plugin", required=True, type=Path, + help="com.utmstack.geolocation.plugin built from this checkout's plugins/geolocation") + args = parser.parse_args() + fixture_dir = Path(__file__).resolve().parent + expected = json.loads((fixture_dir / "expected.json").read_text())["cases"] + work, inputs, stems, events, alerts, log_text = run(args.playground, args.plugins, args.geolocation_plugin) + require(set(expected) == {name for name, _ in inputs.values()}, "raw.json and expected.json disagree") + for marker in LOG_FAILURES: + require(marker not in log_text, f"Execution log reports: {marker}") + + by_id = {e.get("id"): e for e in events} + require(len(events) == len(inputs) == len(by_id), f"Events {len(events)} for {len(inputs)} inputs") + for event_id, (name, raw) in inputs.items(): + event, want = by_id[event_id], expected[name] + require(event.get("raw") == raw, f"Raw input changed: {event_id}") + require(not event.get("errors"), f"Parser errors: {event_id}: {len(event.get('errors') or [])}") + require(("log" in event) == want["logObject"], f"{event_id}: log object present={'log' in event}") + got = fields(event) + differ = sorted(k for k in set(got) | set(want["fields"]) if got.get(k, "") != want["fields"].get(k, "")) + require(not differ, f"{event_id}: fields differ: " + + "; ".join(f"{k}={got.get(k, '')!r}, want {want['fields'].get(k, '')!r}" for k in differ[:5])) + + fired = {} + for alert in alerts: + require(not alert.get("errors") and not alert.get("name", "").startswith("Circuit Breaker"), + f"Rule evaluation failure: {alert.get('name')}") + require(alert.get("name") in stems, f"Unknown alert: {alert.get('name')}") + ids = [e.get("id") for e in alert.get("events", [])] + require(ids and ids[-1] in inputs, f"Unexpected alert events: {alert.get('name')} {ids}") + last = by_id[ids[-1]] + require(alert.get("adversary") == last.get("origin") and alert.get("target") == last.get("target"), + f"{ids[-1]}: alert sides differ from the event's origin and target") + fired.setdefault(ids[-1], []).append(stems[alert["name"]]) + for event_id, (name, _) in inputs.items(): + got = sorted(fired.get(event_id, [])) + require(got == sorted(expected[name]["alerts"]), f"{event_id}: alerts {got}, want {sorted(expected[name]['alerts'])}") + result = {"passed": True, "events": len(events), "alerts": len(alerts)} + (work / "assertions.json").write_text(json.dumps(result)) + print(f"PASS: {len(events)} raw events, zero parser errors, {len(alerts)} local alerts, each from its " + f"intended rule with the event's origin as adversary, no Circuit Breaker and no history search attempted") + + +if __name__ == "__main__": + main() From ed0c23ef75e8edd70c015b976f122a643cd46fe5 Mon Sep 17 00:00:00 2001 From: Ricardo Valdes Date: Thu, 24 Sep 2026 16:26:00 -0400 Subject: [PATCH 3/6] docs(cisco-firepower): add filter and rule audit Records the evidence basis (genuine device records reviewed privately, the empty Firepower index, Cisco documentation unavailable), each change and its proof, the rules' assumptions about Cisco's meaning, expected volumes, the deferred items, the routing note and the known limits. Co-Authored-By: Claude Opus 5.5 --- filters/audits/cisco-firepower.md | 225 ++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 filters/audits/cisco-firepower.md diff --git a/filters/audits/cisco-firepower.md b/filters/audits/cisco-firepower.md new file mode 100644 index 000000000..ad3d05cda --- /dev/null +++ b/filters/audits/cisco-firepower.md @@ -0,0 +1,225 @@ +# Cisco Firepower v11 filter and rule review + +The Cisco Firepower filter (`filters/cisco/firepower.yml`) could not read the records a +Firepower Threat Defense device actually sends. Its header patterns required a timestamp and a +device name that the real records do not carry, so every real record was stored without any +parsed field and with 98 CEL errors. It had no parsing at all for the Firepower security events +(430001 intrusion, 430002/430003 connection start and end, 430007), which are the only events the +five Firepower rules can use, and none of those rules could fire. It also shared six step defects, +the misplaced geolocation results and the direct `log.*` comparisons with the Cisco ASA filter. +This revision parses the observed security events, fixes the engine-visible defects, and +corrects two rule conditions. The schema is ThreatWinds go-sdk **v1.1.36**, as pinned by +`plugins/alerts/go.mod`. + +## Evidence basis + +- **Genuine device records, reviewed privately.** None of the 29 v11 instances that could be + searched holds a `firewall-cisco-firepower` record: the index pattern + `v11-log-firewall-cisco-firepower-*` was empty on all of them (two could not be searched). One + instance holds about 1.25 million genuine Firepower Threat Defense syslog records from one + device over about a month, stored under another Cisco integration's data type because the + device sends to that integration's listener (see the routing note below). They were read only, + never written. A bounded private sample of 83 of them (20 intrusion, 20 connection start, 19 + connection end, 4 elephant-flow, 20 with two syslog messages joined by the collector) was + replayed locally as `firewall-cisco-firepower`. None of these records, their addresses or + their names is in this repository: the committed fixtures are fabricated and only follow the + key layout. +- **Cisco documentation was not available.** Cisco's documentation site refused every + automated request (HTTP 403) from the allowed fetch tool. Nothing here depends on Cisco's + wording; changes that need Cisco's definition of a key, a value or a side are deferred. +- **What the corrections rest on.** Each change below names its basis: + - *real records*: the header shape, the key names, which keys occur on which message, the + separators, and values such as the intrusion events' `Priority` and `Classification`; + - *the filter's own patterns*: what a step can receive follows from the earlier steps; + - *the engine*: the public EventProcessor at commit + `8a3ade72bd9d12db21f6b273200588fb49540f14`. Its + [parser](https://github.com/utmstack/EventProcessor/blob/8a3ade72bd9d12db21f6b273200588fb49540f14/pkg/parsing/parsing.go) + stores an error and skips the step when a `where` clause fails; its + [grok plugin](https://github.com/utmstack/EventProcessor/blob/8a3ade72bd9d12db21f6b273200588fb49540f14/plugins/grok/main.go) + writes nothing unless every pattern matched; its + [kv plugin](https://github.com/utmstack/EventProcessor/blob/8a3ade72bd9d12db21f6b273200588fb49540f14/plugins/kv/main.go) + splits on the field separator, cuts each pair at the first value separator, stores every + value as text under `log.` and keeps the last copy of a repeated key; its + [CEL plugin](https://github.com/utmstack/EventProcessor/blob/8a3ade72bd9d12db21f6b273200588fb49540f14/plugins/cel/main.go) + counts rule errors and, at the fifth, disables the rule and raises a + `Circuit Breaker: ` alert. This repository's `plugins/geolocation/main.go` + writes its result at the destination path with `sjson.Set`, which turns a string at that + path, or above it, into an object; + - *the SDK*: go-sdk v1.1.36 + [`utils/fields.go`](https://github.com/threatwinds/go-sdk/blob/v1.1.36/utils/fields.go) + keeps letters, digits, dots and underscores in field names and removes everything else, so + the key `DNS_TTL` is stored as `log.DNS_TTL` and `Prefilter Policy` as `log.PrefilterPolicy`; + [`plugins/cel.go`](https://github.com/threatwinds/go-sdk/blob/v1.1.36/plugins/cel.go) + declares a CEL variable only for the top-level keys an event has, so `log.messageId==N` + fails to compile when there is no `log` object, while `equals`, `oneOf`, `greaterOrEqual` + and `lessOrEqual` return false for a missing path; + [`plugins/rules.go`](https://github.com/threatwinds/go-sdk/blob/v1.1.36/plugins/rules.go) + returns an error when a `{{.field}}` history placeholder cannot be resolved. + +## Filter changes (version 3.1.0) + +| Change | Why | Basis | Proof | +|---|---|---|---| +| F-H1: a third header pattern for the real shape `%FTD--: `, with no timestamp and no device name. It runs only when the two existing header patterns set nothing (`where: '!exists("log.messageId")'`). | The two existing patterns require a day, date, time and device name before `%FTD-`; the device sends none of them, so no real record was parsed. | Real records, engine | Playground: all 70 real records with an intact header are accepted; the 13 whose header was cut by the collector stay rejected, without errors. A LINA message sent in this shape is now parsed by the existing LINA steps (fabricated line only; no real one was seen). | +| F-K1: split the text of 430001, 430002, 430003 and 430007 (`oneOf("log.messageId", [430001, 430002, 430003, 430007])`) at `, ` and `: ` into `log.`. Map only `SrcIP`, `DstIP`, `SrcPort`, `DstPort` and `Protocol` to `origin.ip`, `target.ip`, `origin.port`, `target.port` and `protocol`, each from the first copy of its key, and delete their `log` copies. Read `UserAgent` whole, and split the text before it again. Skip a text that holds a second `%FTD-` header. | These are the device's security events and the only input of the Firepower rules; the filter ignored them. A `UserAgent` value can contain `, ` and even `, Key: value` text, which would cut the value or replace an earlier key. The collector sometimes joins two messages; their values must not be mixed. Other 4300xx IDs (for example 430005) have no real record to show their layout, so they keep only the header fields. | Real records, engine, SDK | Playground: all 63 real single-message records match an independent model of the text key for key; all 63 get `origin.ip`, `target.ip` and `protocol`, and all 59 TCP/UDP ones get both ports. Key names are stored as the SDK writes them, for example `log.DNS_TTL` and `log.PrefilterPolicy`. The 7 real records with a second message inside the text keep only `log.messageId`, `log.severity` and `log.msg`. A fabricated `UserAgent` carrying `, SrcIP: ...` text changes no standard field and no earlier key. | +| F-W1: rewrite the 98 `where` clauses that compared `log.messageId` or `log.severity` directly (for example `log.messageId==113032`) with `equals`, `greaterOrEqual` and `lessOrEqual`. | Without a `log` object every one of them failed to compile, so each unparsed record was stored with 98 errors (about 35 KB). | Engine and SDK | Playground: 83 real records went from 98 errors each to none. Go test: the 468 clauses that use these helpers on these fields keep the direct comparison's result on 2,903 events that have a `log` object, and are false without an error when there is none. | +| F-W2: `lgreaterOrEqual(...)` in the 109201-109213 trim becomes `greaterOrEqual(...) && lessOrEqual(...)`. | The function does not exist, so the clause failed on every event. | SDK | The trim now runs on 109201 (`origin.user` `alice`, `log.session` `0x1a2b` on the fabricated line). | +| F-A2: write the 16 `log.*` geolocation results to `log.Geolocation` (for example `log.localIpGeolocation`) instead of `log..geolocation`. `origin.geolocation` and `target.geolocation` are unchanged. | The result replaced the address with an object, as in the Cisco ASA filter. | Engine | Playground: the address stays text next to the sibling object. Go test: no geolocation destination lies under a field that holds a value. | +| F-A3 to F-A8: the six LINA step fixes of the Cisco ASA review: 302013 direction, 302304 protocol, 305011/305012 action, 302017 users, the 106102/106103 `denied`/`accepted` order (values unchanged) and the second 113009/113011 variant only when no user was set. | Same steps and same defects as the Cisco ASA filter. | Filter patterns and engine | Playground and Go test, fabricated lines: each fix changes its field as intended and its near miss is unchanged. | + +With the new header every 4300xx record gets `severity` `high`, because the device sends all of +them at syslog level 1. The same records are already stored as `high` today under the other +data type. Choosing another source for severity needs Cisco's definitions (D03). + +## Rule changes + +Names, impact, adversary side, grouping, history search and MITRE labels are unchanged. The +malware, indicator-of-compromise and threat-intelligence rules are unchanged (D02). + +| Rule | Change | Why | Basis | +|---|---|---|---| +| `intrusion_prevention_high_priority_events` (v1.0.1) | Read message 430001 and the device's own `log.Priority` (1) and `log.Classification` (four descriptions). Remove the `log.eventType`, `log.impact` and syslog-level branches. | The rule read keys and values that no Firepower syslog record carries (`IPS_EVENT`, short class names such as `attempted-admin`), and the syslog-level branch would have been true for every intrusion event, because the device sends them all at level 1. | Real records, filter | +| `c2_nonstandard_port` (v1.0.1) | Read `log.ApplicationProtocol` and `log.InitiatorPackets` (above zero). Require a destination port and a destination outside 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 and fc00::/7. Remove the `unknown-tcp` branch. The history search (5 events for the same source and destination within 1 hour) is unchanged. | The rule read keys that do not exist (`appProto`, `initiatorPackets` compared to `true`). With only the key names and the packet test fixed, internal web services on unlisted ports (for example 8181) would have raised about 53,000 to 58,000 alert documents a month on the one device. | Real records, filter | + +**Assumptions about Cisco's meaning.** These follow the real records but could not be checked +against Cisco's documentation: + +- intrusion rule: `Priority` is the device's rating of the signature; the three classification + descriptions that no real record carried (`Attempted Administrator Privilege Gain`, + `Web Application Attack`, `Exploit Kit Activity Detected`) are spelled as the observed one + (`Attempted User Privilege Gain`); `SrcIP` is the attacking side for every signature; +- non-standard-port rule: `ApplicationProtocol` is the device's content-based identification; + `DstIP`/`DstPort` is the service side; `InitiatorPackets` counts the initiator's packets (only + "above zero" is used). + +**Expected volume on the one device (30 days).** Intrusion rule: 32 alert documents, grouped +under 7 top-level alerts; all 32 events were already blocked by the device and look like one +internal scanner. Non-standard-port rule: 122 to 188 alert documents under 7 to 13 top-level +alerts, mostly SSL to outside hosts on port 9001. The second figure is a model of the history +threshold over the device's records, not an executed search. + +## Validation + +**Engine and versions.** EventProcessor `8a3ade72bd9d12db21f6b273200588fb49540f14`: every +parser, writer and CEL plugin links go-sdk v1.1.36; the geolocation plugin was built from this +repository (go-sdk v1.1.34). Rule predicates were also replayed with go-sdk v1.1.36. + +**Genuine records (private, not committed).** The unchanged and the corrected filter ran on the +same 148 lines: the 83 genuine records above and 65 fabricated lines. + +| Run | Result | +|---|---| +| Unchanged filter, 148 lines | 148 events, 13,049 errors. Every genuine record: no `log` object, 98 errors, no field. | +| Corrected filter, 148 lines | 148 events, no errors. 70 of 70 intact headers accepted, 13 of 13 cut headers rejected; standard fields only the five listed above; 30 of 30 fabricated checks pass (1 of 30 before). | +| The 144 of these lines that also ran on the previous engine (EventProcessor 497bf53, go-sdk v1.1.26) | The unchanged filter's events are identical. The corrected filter's events differ only in `log.DNSTTL` becoming `log.DNS_TTL` (two genuine records) and in two fabricated 430005 lines, which an earlier draft parsed and this revision leaves unparsed. | +| go-sdk v1.1.36 predicate replay of the rules on those events | Intrusion rule: 27 matches, all 20 genuine intrusion records and 7 fabricated positives; no near miss. Non-standard-port rule: the 8 fabricated positives, none of the 83 genuine records and no near miss; its placeholders resolve on every match. The three unchanged rules match only fabricated lines carrying crafted keys. The original two conditions match none of the genuine records. | +| Playground with all five rules and no OpenSearch, 140 intrusion, connection and near-miss lines | 30 local alerts: 27 intrusion alerts (the replay's matches) and one for each crafted-key line of the three unchanged rules; each alert's adversary and target are the event's origin and target. No Circuit Breaker, no compile or rule error, and no history search: no line met the non-standard-port condition. | +| Same, the 8 positives and 2 near misses of the non-standard-port rule | The rule's condition matched the 8 positives and each history search failed (connection refused: no OpenSearch). After the fifth failure the engine raised one Circuit Breaker alert for the rule. A test-only copy without the history search raised the 8 expected alerts. This shows the search is reached; it does not test it. | + +In a separate 30-day count on the instance, all 32 genuine intrusion events carry `Priority` 1 +and `Attempted User Privilege Gain`, so the corrected intrusion condition matches all of them. + +**Fabricated regression, committed.** `plugins/alerts/testdata/cisco-firepower/` holds 58 +invented raw lines (`raw.json`), their expected fields and alerts (`expected.json`), the 13 +shared grok definitions this filter uses (`patterns.yaml`, copied from the repository changelog +`20250616001_insert_utm_regex_pattern.xml`), invented geolocation data (`geolocation-data/`) +and `replay.py`. Addresses are RFC 5737 and RFC 3849 documentation addresses, except the +RFC 1918 and RFC 4193 private addresses of the private-destination near misses; device, user, +zone, rule and policy names are examples and the device UUID is made up. `replay.py` stages the +non-standard-port rule as a condition-only test copy because no OpenSearch runs; it passed: 58 +events without errors, every stored field as recorded, 10 alerts, each from its intended rule +with the event's origin as adversary, and no history search. + +**Go tests.** `cisco_firepower_filter_test.go` has seven tests. They check that no `where` +clause compares `log.*` directly, that every clause runs without a `log` object and that each +helper clause keeps the direct comparison's results; that no geolocation destination lies under +a field that holds a value; that a model of the engine's step plugins, with every `where` clause +evaluated by go-sdk v1.1.36, gives the playground's result for every stored field of the 58 +lines except the geolocation ones, including a positive and a near-miss line for each change; the +new, reordered and guarded `where` clauses in filter order; the rules' names, metadata, impact, +grouping, MITRE labels and history search; 27 synthetic rule cases plus the rule results on all +58 lines; and that whenever the rule with a history search matches, its placeholders resolve. +Six of the seven fail against the original filter and rules; the placeholder test passes on both, +because the original condition also required both addresses. The full `plugins/alerts` suite +passes: 50 tests pass and the same 11 tests that need other technologies' private evidence skip, +as they do on the base commit (43 pass, 11 skip). + +## Deferred + +Each of these needs Cisco's documentation, more real records or an owner decision, and is +unchanged here. + +| Id | What | What would unblock it | +|---|---|---| +| D01 | Header shapes other than the real one: no day of week, no ``, RFC 5424, other timestamp and device-id options. | Cisco's logging options and defaults, or real records in those shapes. | +| D02 | Parse 430004 to 430006 (file, malware and other unseen events); rewrite the malware, indicator-of-compromise and threat-intelligence rules with that parsing, and fix their labels (T1566 on the malware and indicator rules, the TA0040 reference, the parent T1071 link). Keys keep underscores, so a rewritten rule must read, for example, `log.SHA_Disposition`. | Cisco's field list for these events, plus one real record of each from a device with those features. | +| D03 | Severity of 4300xx events: the syslog level (today `high` on all), `EventPriority` or `Priority`. | Cisco's definitions and a review of severity consumers. | +| D04 | `deviceTime` from `FirstPacketSecond` (a connection's first packet, not its end) and the time zone of the older header time. | Cisco's definition and a platform decision. | +| D05 | `User`, byte and packet counters, `URL`, DNS names and the original client address to standard fields. | Cisco's definition of the side each belongs to. | +| D06 | Outcome from `AccessControlRuleAction` and `InlineResult`; action names; the LINA outcome meanings. | The separate action-result correction and Cisco's value definitions. | +| D07 | Intrusion rule technique T1203 against the device's own tag T1190 on the real events. | An owner decision; no detection effect. | +| D08 | Non-standard-port options: `Unknown` on TCP, deduplication instead of grouping, more TLS ports (for example 5222 and 50051). | Cisco's meaning of `Unknown` and an owner decision after some weeks of data. | +| D09 | The LINA items shared with the Cisco ASA review (sides, formats, counters, vocabulary, cleanups). | Cisco's syslog guide or real LINA records from this device type (none seen). | +| D10 | Records with two syslog messages joined (about 1.25 %) and the routing below. | A sender or collector change, outside this filter. | +| D11 | New rules for real data no rule reads (blocked connections, intrusion results, DNS answers, remote-access VPN users, long or large connections). | Cisco's meaning of each key and a volume check. | + +MITRE ATT&CK v19.2 still lists T1571, T1203, T1566 and T1071 with the tactics the rules use; +no label changes here. + +## Routing note for customers + +The only device seen sends to the syslog listener of another Cisco integration, so its records +are stored under that integration's data type and this filter never sees them. The collector +assigns the data type of the listener that received a record, and the Cisco integrations share +the same default ports (UDP 514 and TCP 1470). Until such a device is pointed at the Firepower +integration's own listener (or the collector distinguishes the senders), none of these +corrections apply to its records. + +## Known limits + +- History searches were not executed. No OpenSearch was available for this review; the + non-standard-port rule's threshold, window and address terms are unchanged and untested, and + its volume figures are a model. +- Production indexing of the new `log.*` fields was not tested. Each genuine record adds + 22 to 31 text fields under `log`; the Firepower index is empty everywhere today, so no + stored field changes type, but mapping growth was not measured. +- One device, one software version and one configuration. Keys or layouts that device did not + send (for example the file and malware events) are not parsed. +- Every committed input is fabricated. The genuine records prove the layout of one device; + they stay private and are described here only in aggregate. +- Key-value values are text, including counters such as `log.InitiatorPackets`; the rule's + `greaterThan` compares numeric text as a number. +- A record whose collector framing joined two messages keeps only its header fields. +- The playground's alert writer only records alerts. Indexing, grouping, deduplication, + notifications and production alerts were not tested. +- The Go extraction test is a model of the engine's step plugins; it agreed with the playground + on every non-geolocation field of the 58 lines, but `replay.py` is the check that runs the + engine. +- The separate action-result correction edits the same filter. This change moves the two + 106102/106103 `actionResult` blocks without changing their values; combining the two needs a + rebase at those blocks and at the version line. + +## Reproduce + +Build the EventProcessor commit above without changing its dependencies. With `EP` set to +that checkout's absolute path: + +```sh +mkdir -p "$EP/test-bin" "$EP/test-plugins" +(cd "$EP" && go build -mod=readonly -o "$EP/test-bin/playground" ./cmd/playground) +for plugin in add cast cel delete grok kv rename saw sew trim; do + (cd "$EP/plugins/$plugin" && go build -mod=readonly -o "$EP/test-plugins/$plugin.plugin" .) +done +``` + +From this UTMStack checkout, with PyYAML installed: + +```sh +(cd plugins/geolocation && go build -mod=readonly -o "$EP/test-plugins/com.utmstack.geolocation.plugin" .) +python3 plugins/alerts/testdata/cisco-firepower/replay.py --playground "$EP/test-bin/playground" \ + --plugins "$EP/test-plugins" --geolocation-plugin "$EP/test-plugins/com.utmstack.geolocation.plugin" +(cd plugins/alerts && go test ./... -count=1) +``` + +The playground run takes about two and a half minutes. The Go suite alone does not run the +engine. From 193a0cf56aa24d3816441a202c6122de5735400b Mon Sep 17 00:00:00 2001 From: Ricardo Valdes Date: Thu, 24 Sep 2026 16:33:30 -0400 Subject: [PATCH 4/6] docs(cisco-firepower): tie the validated engine to the newest published image Co-Authored-By: Claude Opus 5.5 --- filters/audits/cisco-firepower.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/filters/audits/cisco-firepower.md b/filters/audits/cisco-firepower.md index ad3d05cda..81a72ab5c 100644 --- a/filters/audits/cisco-firepower.md +++ b/filters/audits/cisco-firepower.md @@ -103,6 +103,11 @@ threshold over the device's records, not an executed search. **Engine and versions.** EventProcessor `8a3ade72bd9d12db21f6b273200588fb49540f14`: every parser, writer and CEL plugin links go-sdk v1.1.36; the geolocation plugin was built from this repository (go-sdk v1.1.34). Rule predicates were also replayed with go-sdk v1.1.36. +This is the same source revision as the newest published engine image at the time of testing: +`ghcr.io/utmstack/utmstack/eventprocessor:v11.2.14` (built 2026-09-24 19:13 UTC) sits on +`ghcr.io/utmstack/eventprocessor/base:1.1.7`, whose `playground` and `eventprocessor` binaries +embed `vcs.revision=8a3ade72bd9d12db21f6b273200588fb49540f14` and go-sdk v1.1.36 (go1.26.8, +linux/amd64). The local binaries were compiled natively from that revision (darwin/arm64, go1.25.7). **Genuine records (private, not committed).** The unchanged and the corrected filter ran on the same 148 lines: the 83 genuine records above and 65 fabricated lines. From e4f340cb527fd1743abf90d25f64a0efb9e8ea9f Mon Sep 17 00:00:00 2001 From: Ricardo Valdes Date: Thu, 24 Sep 2026 20:15:19 -0400 Subject: [PATCH 5/6] fix(cisco-firepower): stop three grok steps from matching empty text The EventProcessor 8a3ade7 grok plugin trims the remaining text before each pattern and counts an empty match as no match, and then the whole step writes nothing. Three patterns in filters/cisco/firepower.yml match empty text at the start: - 302003/302004, first variant: '(to\s)?' is empty on every 302003 line, so a 302003 line whose local side is a host name was parsed by neither variant. The optional "to" is now read with the local address, and a trim step removes it. - 302022/302024/302026: the two steps for a mapped address without a port read it with '{{.data}}', which on its own always matches empty text, so they never wrote. They now read '[^/()]+', which only fits when no port follows. Filter version 3.1.1. Five fabricated lines are added to testdata/cisco-firepower (302003 with an address and with a host name, 302004 with "to", 302022 with a mapped port, 302024 without one). Their expected fields were recorded from the 8a3ade7 playground after the declared checks (F-G1, F-G2). TestCiscoFirepowerGrokPatternsNeverMatchEmpty tries every expanded pattern alone on texts that start with each printable character. The model test and the new test both fail on 3.1.0 and pass on 3.1.1. Co-Authored-By: Claude Opus 5.5 --- filters/cisco/firepower.yml | 17 +- plugins/alerts/cisco_firepower_filter_test.go | 42 ++++ .../testdata/cisco-firepower/expected.json | 187 ++++++++++++++++++ .../alerts/testdata/cisco-firepower/raw.json | 7 +- 4 files changed, 246 insertions(+), 7 deletions(-) diff --git a/filters/cisco/firepower.yml b/filters/cisco/firepower.yml index eb66bbbdc..1373b82b6 100644 --- a/filters/cisco/firepower.yml +++ b/filters/cisco/firepower.yml @@ -1,4 +1,4 @@ -# CISCO Firepower filter, version 3.1.0 +# CISCO Firepower filter, version 3.1.1 # Based on examples provided and https://www.cisco.com/c/en/us/td/docs/security/firepower/Syslogs/b_fptd_syslog_guide.html # Filter Input requirements -> Syslog # @@ -1425,14 +1425,19 @@ pipeline: pattern: '({{.ipv4}}|{{.ipv6}}|{{.hostname}})' - fieldName: log.tmpToip pattern: '({{.ipv4}}|{{.ipv6}}|{{.hostname}})((\s)?/(\s)?{{.integer}}|(\s)?/)?' - - fieldName: log.irrelevant - pattern: '(to\s)?' - fieldName: log.localAddress - pattern: '({{.ipv4}}|{{.ipv6}}|{{.hostname}})' + pattern: '(?:to\s+)?({{.ipv4}}|{{.ipv6}}|{{.hostname}})' - fieldName: log.tmpFromip pattern: '({{.ipv4}}|{{.ipv6}}|{{.hostname}})((\s)?/(\s)?{{.integer}}|(\s)?/)?' source: log.msg where: equals("log.messageId", 302003) || equals("log.messageId", 302004) + # The optional "to" is read with the local address above; keep only the address. + - trim: + function: regex + substring: '^to\s+' + fields: + - log.localAddress + where: equals("log.messageId", 302003) || equals("log.messageId", 302004) # Second variant - grok: patterns: @@ -2591,7 +2596,7 @@ pipeline: - fieldName: log.irrelevant pattern: '\(' - fieldName: log.mappedIpFrom - pattern: '{{.data}}' + pattern: '[^/()]+' - fieldName: log.irrelevant pattern: '\)' source: log.tmpMappedIpPortFrom @@ -2616,7 +2621,7 @@ pipeline: - fieldName: log.irrelevant pattern: '\(' - fieldName: log.mappedIpTo - pattern: '{{.data}}' + pattern: '[^/()]+' - fieldName: log.irrelevant pattern: '\)' source: log.tmpMappedIpPortTo diff --git a/plugins/alerts/cisco_firepower_filter_test.go b/plugins/alerts/cisco_firepower_filter_test.go index 5b23a0ac8..01e1bfca6 100644 --- a/plugins/alerts/cisco_firepower_filter_test.go +++ b/plugins/alerts/cisco_firepower_filter_test.go @@ -764,6 +764,16 @@ var fpChangeCases = []struct { {"F-A8 near miss", "113009-without-equals", "origin.user", "alice"}, {"F-W2", "109201-uauth", "origin.user", "alice"}, {"F-W2", "109201-uauth", "log.session", "0x1a2b"}, + {"F-G1", "302003-hostname", "origin.ip", "host-b.example.com"}, + {"F-G1", "302003-hostname", "target.ip", "198.51.100.7"}, + {"F-G1", "302003-hostname", "log.localAddress", "host-b.example.com"}, + {"F-G1 near miss", "302003-ip", "log.localAddress", "192.0.2.10"}, + {"F-G1 near miss", "302004-to", "log.localAddress", "192.0.2.10"}, + {"F-G2", "302024-mapped-no-port", "log.mappedIpFrom", "198.51.100.7"}, + {"F-G2", "302024-mapped-no-port", "log.mappedIpTo", "203.0.113.5"}, + {"F-G2", "302024-mapped-no-port", "log.mappedPortFrom", nil}, + {"F-G2 near miss", "302022-mapped-port", "log.mappedIpFrom", "198.51.100.7"}, + {"F-G2 near miss", "302022-mapped-port", "log.mappedPortFrom", "443"}, } // Every fabricated line through the model: the named change cases, no where errors, and every @@ -820,6 +830,38 @@ func TestCiscoFirepowerExtractionModel(t *testing.T) { } } +// The grok plugin trims the remaining text before each pattern and treats an empty match as no +// match, which drops the whole step. So no pattern may prefer empty text at the start of a +// non-empty text: each pattern, expanded as the engine does, is tried alone on texts that start +// with every printable ASCII character and with one non-ASCII letter. +func TestCiscoFirepowerGrokPatternsNeverMatchEmpty(t *testing.T) { + m := fpNewModel(t) + probes := []string{"é x"} + for c := '!'; c <= '~'; c++ { + probes = append(probes, string(c)+" x") + } + checked := 0 + for i, step := range m.steps { + if step.Grok == nil { + continue + } + for j, p := range step.Grok.Patterns { + re := m.compile(t, p.Pattern) + checked++ + for _, probe := range probes { + if loc := re.FindStringIndex(probe); loc != nil && loc[1] == 0 { + t.Errorf("step %d pattern %d (%s) %q matches empty text at the start of %q", + i, j, p.FieldName, p.Pattern, probe) + break + } + } + } + } + if checked == 0 { + t.Fatal("no grok pattern checked") + } +} + // The where clauses of the new, reordered and guarded steps, evaluated with the SDK in filter order. func TestCiscoFirepowerStepPredicates(t *testing.T) { steps := fpPipeline(t).Steps diff --git a/plugins/alerts/testdata/cisco-firepower/expected.json b/plugins/alerts/testdata/cisco-firepower/expected.json index c47e99d54..8d792487b 100644 --- a/plugins/alerts/testdata/cisco-firepower/expected.json +++ b/plugins/alerts/testdata/cisco-firepower/expected.json @@ -2266,6 +2266,193 @@ "logObject": false, "fields": {}, "alerts": [] + }, + "302003-hostname": { + "logObject": true, + "fields": { + "log.ciscoTime": "Wed Sep 23 2026 10:00:00", + "log.foreignAddress": "198.51.100.7", + "log.localAddress": "host-b.example.com", + "log.localIp": "ftd01.example.com", + "log.messageId": 302003, + "log.msg": "Built H245 connection for 198.51.100.7 198.51.100.7/1720 host-b.example.com host-b.example.com/5678", + "log.severity": "6", + "target.ip": "198.51.100.7", + "target.port": 1720, + "target.geolocation.country": "Fabricated Country A", + "target.geolocation.city": "Fabricated City A", + "target.geolocation.latitude": 10.5, + "target.geolocation.longitude": -20.25, + "target.geolocation.asn": 64500, + "target.geolocation.aso": "FABRICATED-AS-64500", + "target.geolocation.countryCode": "XA", + "origin.ip": "host-b.example.com", + "origin.port": 5678, + "actionResult": "accepted", + "action": "Built H245 connection", + "severity": "low" + }, + "alerts": [] + }, + "302003-ip": { + "logObject": true, + "fields": { + "log.ciscoTime": "Wed Sep 23 2026 10:00:00", + "log.foreignAddress": "198.51.100.7", + "log.localAddress": "192.0.2.10", + "log.localIp": "ftd01.example.com", + "log.messageId": 302003, + "log.msg": "Built H245 connection for 198.51.100.7 198.51.100.7/1720 192.0.2.10 192.0.2.10/5678", + "log.severity": "6", + "target.ip": "198.51.100.7", + "target.port": 1720, + "target.geolocation.country": "Fabricated Country A", + "target.geolocation.city": "Fabricated City A", + "target.geolocation.latitude": 10.5, + "target.geolocation.longitude": -20.25, + "target.geolocation.asn": 64500, + "target.geolocation.aso": "FABRICATED-AS-64500", + "target.geolocation.countryCode": "XA", + "origin.ip": "192.0.2.10", + "origin.port": 5678, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "actionResult": "accepted", + "action": "Built H245 connection", + "severity": "low" + }, + "alerts": [] + }, + "302004-to": { + "logObject": true, + "fields": { + "log.ciscoTime": "Wed Sep 23 2026 10:00:00", + "log.foreignAddress": "198.51.100.7", + "log.localAddress": "192.0.2.10", + "log.localIp": "ftd01.example.com", + "log.messageId": 302004, + "log.msg": "Pre-allocate H323 UDP backconnection for 198.51.100.7 198.51.100.7/1720 to 192.0.2.10 192.0.2.10/5678", + "log.severity": "6", + "target.ip": "198.51.100.7", + "target.port": 1720, + "target.geolocation.country": "Fabricated Country A", + "target.geolocation.city": "Fabricated City A", + "target.geolocation.latitude": 10.5, + "target.geolocation.longitude": -20.25, + "target.geolocation.asn": 64500, + "target.geolocation.aso": "FABRICATED-AS-64500", + "target.geolocation.countryCode": "XA", + "origin.ip": "192.0.2.10", + "origin.port": 5678, + "origin.geolocation.country": "Fabricated Country B", + "origin.geolocation.city": "Fabricated City B", + "origin.geolocation.latitude": -30.125, + "origin.geolocation.longitude": 40.75, + "origin.geolocation.asn": 64501, + "origin.geolocation.aso": "FABRICATED-AS-64501", + "origin.geolocation.countryCode": "XB", + "actionResult": "accepted", + "action": "Pre-allocate H323 UDP backconnection", + "severity": "low" + }, + "alerts": [] + }, + "302022-mapped-port": { + "logObject": true, + "fields": { + "log.ciscoTime": "Wed Sep 23 2026 10:00:00", + "log.dstInterface": "inside", + "log.localIp": "ftd01.example.com", + "log.mappedIpFrom": "198.51.100.7", + "log.mappedIpFromGeolocation.asn": 64500, + "log.mappedIpFromGeolocation.aso": "FABRICATED-AS-64500", + "log.mappedIpFromGeolocation.city": "Fabricated City A", + "log.mappedIpFromGeolocation.country": "Fabricated Country A", + "log.mappedIpFromGeolocation.countryCode": "XA", + "log.mappedIpFromGeolocation.latitude": 10.5, + "log.mappedIpFromGeolocation.longitude": -20.25, + "log.mappedIpTo": "203.0.113.5", + "log.mappedIpToGeolocation": {}, + "log.mappedPortFrom": "443", + "log.mappedPortTo": "51234", + "log.messageId": 302022, + "log.msg": "Built backup stub TCP connection for outside:198.51.100.7/443 (198.51.100.7/443) to inside:192.0.2.10/51234 (203.0.113.5/51234)", + "log.role": "backup", + "log.severity": "6", + "log.srcInterface": "outside", + "target.ip": "192.0.2.10", + "target.port": 51234, + "target.geolocation.country": "Fabricated Country B", + "target.geolocation.city": "Fabricated City B", + "target.geolocation.latitude": -30.125, + "target.geolocation.longitude": 40.75, + "target.geolocation.asn": 64501, + "target.geolocation.aso": "FABRICATED-AS-64501", + "target.geolocation.countryCode": "XB", + "origin.ip": "198.51.100.7", + "origin.port": 443, + "origin.geolocation.country": "Fabricated Country A", + "origin.geolocation.city": "Fabricated City A", + "origin.geolocation.latitude": 10.5, + "origin.geolocation.longitude": -20.25, + "origin.geolocation.asn": 64500, + "origin.geolocation.aso": "FABRICATED-AS-64500", + "origin.geolocation.countryCode": "XA", + "protocol": "TCP", + "actionResult": "accepted", + "severity": "low" + }, + "alerts": [] + }, + "302024-mapped-no-port": { + "logObject": true, + "fields": { + "log.ciscoTime": "Wed Sep 23 2026 10:00:00", + "log.dstInterface": "inside", + "log.localIp": "ftd01.example.com", + "log.mappedIpFrom": "198.51.100.7", + "log.mappedIpFromGeolocation.asn": 64500, + "log.mappedIpFromGeolocation.aso": "FABRICATED-AS-64500", + "log.mappedIpFromGeolocation.city": "Fabricated City A", + "log.mappedIpFromGeolocation.country": "Fabricated Country A", + "log.mappedIpFromGeolocation.countryCode": "XA", + "log.mappedIpFromGeolocation.latitude": 10.5, + "log.mappedIpFromGeolocation.longitude": -20.25, + "log.mappedIpTo": "203.0.113.5", + "log.mappedIpToGeolocation": {}, + "log.messageId": 302024, + "log.msg": "Built backup stub UDP connection for outside:198.51.100.7/53 (198.51.100.7) to inside:192.0.2.10/40000 (203.0.113.5)", + "log.role": "backup", + "log.severity": "6", + "log.srcInterface": "outside", + "target.ip": "192.0.2.10", + "target.port": 40000, + "target.geolocation.country": "Fabricated Country B", + "target.geolocation.city": "Fabricated City B", + "target.geolocation.latitude": -30.125, + "target.geolocation.longitude": 40.75, + "target.geolocation.asn": 64501, + "target.geolocation.aso": "FABRICATED-AS-64501", + "target.geolocation.countryCode": "XB", + "origin.ip": "198.51.100.7", + "origin.port": 53, + "origin.geolocation.country": "Fabricated Country A", + "origin.geolocation.city": "Fabricated City A", + "origin.geolocation.latitude": 10.5, + "origin.geolocation.longitude": -20.25, + "origin.geolocation.asn": 64500, + "origin.geolocation.aso": "FABRICATED-AS-64500", + "origin.geolocation.countryCode": "XA", + "protocol": "UDP", + "actionResult": "accepted", + "severity": "low" + }, + "alerts": [] } } } diff --git a/plugins/alerts/testdata/cisco-firepower/raw.json b/plugins/alerts/testdata/cisco-firepower/raw.json index cce278b90..1c10c203a 100644 --- a/plugins/alerts/testdata/cisco-firepower/raw.json +++ b/plugins/alerts/testdata/cisco-firepower/raw.json @@ -58,6 +58,11 @@ "unparsed-glued-header": "<113>%FTD-1-430<113>%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", "unparsed-linux-sshd": "<86>Sep 23 10:00:00 host01.example.com sshd[1234]: Accepted publickey for alice from 192.0.2.10 port 51000 ssh2", "unparsed-no-pri": "%FTD-1-430003: EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", - "unparsed-no-space": "<113>%FTD-1-430003:EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a" + "unparsed-no-space": "<113>%FTD-1-430003:EventPriority: Low, DeviceUUID: 00000000-0000-4000-8000-00000000f7d1, InstanceID: 1, FirstPacketSecond: 2026-09-23T10:00:00Z, ConnectionID: 1001, AccessControlRuleAction: Allow, SrcIP: 192.0.2.10, DstIP: 198.51.100.20, SrcPort: 51000, DstPort: 443, Protocol: tcp, IngressInterface: in1, EgressInterface: out1, IngressZone: zone-a, EgressZone: zone-b, ACPolicy: policy-a, AccessControlRuleName: rule-a, Prefilter Policy: Example Prefilter Policy, User: alice, Client: SSL client, ApplicationProtocol: HTTPS, ConnectionDuration: 12, InitiatorPackets: 6, ResponderPackets: 12, InitiatorBytes: 1200, ResponderBytes: 5400, NAPPolicy: nap-a", + "302003-ip": "<166>Wed Sep 23 2026 10:00:00 ftd01.example.com : %FTD-6-302003: Built H245 connection for 198.51.100.7 198.51.100.7/1720 192.0.2.10 192.0.2.10/5678", + "302003-hostname": "<166>Wed Sep 23 2026 10:00:00 ftd01.example.com : %FTD-6-302003: Built H245 connection for 198.51.100.7 198.51.100.7/1720 host-b.example.com host-b.example.com/5678", + "302004-to": "<166>Wed Sep 23 2026 10:00:00 ftd01.example.com : %FTD-6-302004: Pre-allocate H323 UDP backconnection for 198.51.100.7 198.51.100.7/1720 to 192.0.2.10 192.0.2.10/5678", + "302022-mapped-port": "<166>Wed Sep 23 2026 10:00:00 ftd01.example.com : %FTD-6-302022: Built backup stub TCP connection for outside:198.51.100.7/443 (198.51.100.7/443) to inside:192.0.2.10/51234 (203.0.113.5/51234)", + "302024-mapped-no-port": "<166>Wed Sep 23 2026 10:00:00 ftd01.example.com : %FTD-6-302024: Built backup stub UDP connection for outside:198.51.100.7/53 (198.51.100.7) to inside:192.0.2.10/40000 (203.0.113.5)" } } From 99d21261b374bb5b5e2fe05881ec7b01f4acaec8 Mon Sep 17 00:00:00 2001 From: Ricardo Valdes Date: Thu, 24 Sep 2026 20:15:19 -0400 Subject: [PATCH 6/6] docs(cisco-firepower): record the empty-match correction Adds the version 3.1.1 section: what the grok plugin does with an empty match, the three LINA steps and their fix, and the checks on EventProcessor 8a3ade7. replay.py gives 63 events and the same 10 alerts, none of the 148 private lines is one of these messages, the go-sdk v1.1.36 rule replay passes 21 of 21 checks, and the full suite has 51 passing and 11 skipped tests. The fixture and test counts elsewhere in the audit are updated. Co-Authored-By: Claude Opus 5.5 --- filters/audits/cisco-firepower.md | 75 +++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/filters/audits/cisco-firepower.md b/filters/audits/cisco-firepower.md index 81a72ab5c..4445dcc43 100644 --- a/filters/audits/cisco-firepower.md +++ b/filters/audits/cisco-firepower.md @@ -9,7 +9,9 @@ five Firepower rules can use, and none of those rules could fire. It also shared the misplaced geolocation results and the direct `log.*` comparisons with the Cisco ASA filter. This revision parses the observed security events, fixes the engine-visible defects, and corrects two rule conditions. The schema is ThreatWinds go-sdk **v1.1.36**, as pinned by -`plugins/alerts/go.mod`. +`plugins/alerts/go.mod`. A later check of every grok pattern found three LINA steps that could +never write on the engine; version 3.1.1 fixes them, see +[Empty-match correction](#empty-match-correction-version-311). ## Evidence basis @@ -98,6 +100,39 @@ internal scanner. Non-standard-port rule: 122 to 188 alert documents under 7 to alerts, mostly SSL to outside hosts on port 9001. The second figure is a model of the history threshold over the device's records, not an executed search. +## Empty-match correction (version 3.1.1) + +The grok plugin of EventProcessor `8a3ade7` reads a step's patterns in order. Before each +pattern it trims the text that is left, and it counts an empty match as no match. If any +pattern fails, the step writes nothing +([grok plugin](https://github.com/utmstack/EventProcessor/blob/8a3ade72bd9d12db21f6b273200588fb49540f14/plugins/grok/main.go)). +So a pattern that can match empty text at the start of the remaining text stops its whole +step. All 1,074 grok patterns of version 3.1.0 were expanded with `patterns.yaml` and tried +on their own. Three of them, in LINA steps shared with the Cisco ASA filter, had this problem: + +| Change | Why | Basis | Proof on the engine | +|---|---|---|---| +| F-G1: 302003/302004, first variant: read the optional `to` together with the local address, `(?:to\s+)?(...)`, and remove it with a new `trim` step right after. | The optional `to` was its own pattern, `(to\s)?`. On a 302003 line, which has no `to`, it matched empty text and the step failed. The second variant accepts only an IP address, so a 302003 line whose local side is a host name was parsed by neither. | Filter patterns and engine | `302003-hostname` kept only `actionResult`; now it has `action`, both addresses and both ports. `302003-ip` and `302004-to` are unchanged. | +| F-G2: 302022/302024/302026: the two steps for a mapped address without a port read it with `[^/()]+` instead of `{{.data}}`. | `{{.data}}` is a lazy "any text" pattern. On its own it always matches empty text, so these two steps never wrote. The new pattern stops at `/`, so it applies only when no port follows and cannot replace what the steps for an address with a port wrote. | Filter patterns and engine | `302024-mapped-no-port` had no mapped address; now it has `log.mappedIpFrom` and `log.mappedIpTo`, and no port. `302022-mapped-port` is unchanged. | + +The other 140 patterns that can match empty text are a `{{.greedy}}` ("any text") at the end +of a step. The plugin never tries a pattern once no text is left, and on any other text these +match at least one character, so they cannot cause this. + +After the fix, a host name in the local address reaches `origin.ip` on 302003 lines, as it +already did on 302004 lines and on the foreign side of both; which LINA values belong in the +address fields is part of D09. No genuine record reviewed here is one of these messages, so the +fix rests on fabricated lines. The Cisco ASA filter gets the same fix in its own review. + +| Check | Result | +|---|---| +| Five new fabricated lines on EventProcessor 8a3ade7, versions 3.1.0 and 3.1.1 | As in the table above. No line has an error with either version. | +| `replay.py` on EventProcessor 8a3ade7, now 63 lines | 63 events without errors, every stored field as in `expected.json`; the 58 earlier lines are unchanged. 10 alerts, each from its intended rule with the event's origin as adversary; no Circuit Breaker alert and no history search. An earlier run wrote the same events and alerts, but the playground then never finished shutting down and the script stopped it at its 15-minute limit; the next run passed. | +| The 148 private lines | None is a 302003, 302004, 302022, 302024 or 302026 message, so the fix cannot change them. | +| go-sdk v1.1.36 rule replay over the 63 events | The intrusion and non-standard-port rules each match exactly the five events whose recorded alerts name them, and the other three rules match nothing; no error and no unresolved placeholder (21 of 21 checks). | +| Full `plugins/alerts` suite | 51 tests pass, 11 skip, none fail (2,280 passing results with subtests). | +| New Go checks, on versions 3.1.0 and 3.1.1 | `TestCiscoFirepowerGrokPatternsNeverMatchEmpty` tries each grok pattern alone on texts that start with every printable character, and fails when one matches empty text at the start. The model test now also covers the five new lines (F-G1, F-G2). On 3.1.0 both fail, naming the three patterns and the missing fields; on 3.1.1 both pass. | + ## Validation **Engine and versions.** EventProcessor `8a3ade72bd9d12db21f6b273200588fb49540f14`: every @@ -124,30 +159,32 @@ same 148 lines: the 83 genuine records above and 65 fabricated lines. In a separate 30-day count on the instance, all 32 genuine intrusion events carry `Priority` 1 and `Attempted User Privilege Gain`, so the corrected intrusion condition matches all of them. -**Fabricated regression, committed.** `plugins/alerts/testdata/cisco-firepower/` holds 58 -invented raw lines (`raw.json`), their expected fields and alerts (`expected.json`), the 13 -shared grok definitions this filter uses (`patterns.yaml`, copied from the repository changelog +**Fabricated regression, committed.** `plugins/alerts/testdata/cisco-firepower/` holds 63 +invented raw lines (`raw.json`; five were added with the empty-match correction), their +expected fields and alerts (`expected.json`), the 13 shared grok definitions this filter uses +(`patterns.yaml`, copied from the repository changelog `20250616001_insert_utm_regex_pattern.xml`), invented geolocation data (`geolocation-data/`) -and `replay.py`. Addresses are RFC 5737 and RFC 3849 documentation addresses, except the -RFC 1918 and RFC 4193 private addresses of the private-destination near misses; device, user, -zone, rule and policy names are examples and the device UUID is made up. `replay.py` stages the -non-standard-port rule as a condition-only test copy because no OpenSearch runs; it passed: 58 +and `replay.py`. Addresses are RFC 5737 and RFC 3849 documentation addresses, except the RFC +1918 and RFC 4193 private addresses of the private-destination near misses; device, user, zone, +rule and policy names are examples and the device UUID is made up. `replay.py` stages the +non-standard-port rule as a condition-only test copy because no OpenSearch runs; it passed: 63 events without errors, every stored field as recorded, 10 alerts, each from its intended rule with the event's origin as adversary, and no history search. -**Go tests.** `cisco_firepower_filter_test.go` has seven tests. They check that no `where` +**Go tests.** `cisco_firepower_filter_test.go` has eight tests. They check that no `where` clause compares `log.*` directly, that every clause runs without a `log` object and that each helper clause keeps the direct comparison's results; that no geolocation destination lies under -a field that holds a value; that a model of the engine's step plugins, with every `where` clause -evaluated by go-sdk v1.1.36, gives the playground's result for every stored field of the 58 -lines except the geolocation ones, including a positive and a near-miss line for each change; the -new, reordered and guarded `where` clauses in filter order; the rules' names, metadata, impact, -grouping, MITRE labels and history search; 27 synthetic rule cases plus the rule results on all -58 lines; and that whenever the rule with a history search matches, its placeholders resolve. -Six of the seven fail against the original filter and rules; the placeholder test passes on both, +a field that holds a value; that no grok pattern matches empty text at the start of a text; +that a model of the engine's step plugins, with every `where` clause evaluated by go-sdk +v1.1.36, gives the playground's result for every stored field of the 63 lines except the +geolocation ones, including a positive and a near-miss line for each change; the new, reordered +and guarded `where` clauses in filter order; the rules' names, metadata, impact, grouping, +MITRE labels and history search; 27 synthetic rule cases plus the rule results on all 63 lines; +and that whenever the rule with a history search matches, its placeholders resolve. Seven of +the eight fail against the original filter and rules; the placeholder test passes on both, because the original condition also required both addresses. The full `plugins/alerts` suite -passes: 50 tests pass and the same 11 tests that need other technologies' private evidence skip, -as they do on the base commit (43 pass, 11 skip). +passes: 51 tests pass and the same 11 tests that need other technologies' private evidence +skip, as they do on the base commit (43 pass, 11 skip). ## Deferred @@ -198,7 +235,7 @@ corrections apply to its records. - The playground's alert writer only records alerts. Indexing, grouping, deduplication, notifications and production alerts were not tested. - The Go extraction test is a model of the engine's step plugins; it agreed with the playground - on every non-geolocation field of the 58 lines, but `replay.py` is the check that runs the + on every non-geolocation field of the 63 lines, but `replay.py` is the check that runs the engine. - The separate action-result correction edits the same filter. This change moves the two 106102/106103 `actionResult` blocks without changing their values; combining the two needs a