From 00d3b9a37d78efded9f7f4d067b743076195ecaa Mon Sep 17 00:00:00 2001 From: Ricardo Valdes Date: Wed, 23 Sep 2026 23:24:37 -0400 Subject: [PATCH 1/8] fix(cisco-asa): helper where clauses, geolocation keys and six step fixes Filter 3.1.0. Every change rests on the filter's own patterns and on the EventProcessor and go-sdk behaviour. Cisco's syslog guide could not be read, so nothing that depends on Cisco wording, address sides or result meanings is changed. - Rewrite the 519 raw log.messageId and log.severity comparisons in where clauses with equals, greaterOrEqual and lessOrEqual. A line that no header pattern accepts has no log object; each raw clause then failed to compile, its step was skipped and an error was stored (519 errors, about 145 KB, per event). The helpers return false instead. Parsed events are unchanged. - Write the 16 log.* geolocation results to log.Geolocation. The plugin wrote them under the address itself, which replaced the address with an object for every non-private address, so one field was a string in some events and an object in others. - 106102/106103: run the denied add before the accepted add, so a permitted hit is no longer overwritten with denied. - 302013: drop the direction grok's trailing pattern, which always met empty input, so log.direction is set. - 302304: the protocol grok reads 'Teardown', the only prefix its source can have, so protocol is set. - 305011/305012: the protocol grok no longer cuts action to its first words. - 302017: log.firewallUserTo is set and target.user loses its leading parenthesis. - 113009/113011: the second grok variant runs only when the first set no user, so origin.user is 'alice' rather than '= alice'. The actionResult values and filters/cisco/firepower.yml are unchanged. Co-Authored-By: Claude Opus 5.5 --- filters/cisco/asa.yml | 1096 +++++++++++++++++++++-------------------- 1 file changed, 549 insertions(+), 547 deletions(-) diff --git a/filters/cisco/asa.yml b/filters/cisco/asa.yml index 1b0f54d0e..27f97fa32 100644 --- a/filters/cisco/asa.yml +++ b/filters/cisco/asa.yml @@ -1,4 +1,4 @@ -# CISCO ASA filter, version 3.0.3 +# CISCO ASA filter, version 3.1.0 # Based on examples provided and https://www.cisco.com/c/en/us/td/docs/security/asa/syslog/b_syslog.html # Filter Input requirements -> Syslog # @@ -50,7 +50,7 @@ pipeline: plugin: com.utmstack.geolocation params: source: log.localIp - destination: log.localIp.geolocation + destination: log.localIpGeolocation where: exists("log.localIp") # Removing unwanted prefixes - trim: @@ -97,7 +97,7 @@ pipeline: - fieldName: log.srcInterface pattern: '{{.greedy}}' source: log.msg - where: log.messageId==106001 + where: equals("log.messageId", 106001) - trim: function: prefix @@ -105,7 +105,7 @@ pipeline: fields: - origin.port - target.port - where: log.messageId==106001 + where: equals("log.messageId", 106001) #......................................................................# #ASA-2-106017 @@ -124,13 +124,13 @@ pipeline: - fieldName: target.ip pattern: '({{.ipv4}}|{{.ipv6}})' source: log.msg - where: log.messageId==106017 + where: equals("log.messageId", 106017) - trim: function: suffix substring: 'from' fields: - action - where: log.messageId==106017 + where: equals("log.messageId", 106017) #......................................................................# # ASA-1-106021 - grok: @@ -150,19 +150,19 @@ pipeline: - fieldName: log.srcInterface pattern: '{{.greedy}}' source: log.msg - where: log.messageId==106021 + where: equals("log.messageId", 106021) - trim: function: suffix substring: 'from' fields: - action - where: log.messageId==106021 + where: equals("log.messageId", 106021) - add: function: 'string' params: key: actionResult value: 'denied' - where: log.messageId==106021 + where: equals("log.messageId", 106021) #......................................................................# # ASA-2-106101 - grok: @@ -172,7 +172,7 @@ pipeline: - fieldName: log.rest pattern: '{{.greedy}}' source: log.msg - where: log.messageId==106101 + where: equals("log.messageId", 106101) - grok: patterns: - fieldName: log.irrelevant @@ -182,7 +182,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: log.messageId==106101 + where: equals("log.messageId", 106101) # Parsing the limit number - grok: patterns: @@ -193,13 +193,13 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.rest - where: log.messageId==106101 + where: equals("log.messageId", 106101) # Field conversions - cast: fields: - log.numFlows to: int - where: log.messageId==106101 + where: equals("log.messageId", 106101) #......................................................................# # ASA-6-106102,106103 @@ -237,7 +237,7 @@ pipeline: - fieldName: log.rest pattern: '{{.greedy}}' source: log.msg - where: log.messageId==106102 || log.messageId==106103 + where: equals("log.messageId", 106102) || equals("log.messageId", 106103) # Second variant - grok: patterns: @@ -274,54 +274,54 @@ pipeline: - fieldName: log.rest pattern: '{{.greedy}}' source: log.msg - where: log.messageId==106102 || log.messageId==106103 + where: equals("log.messageId", 106102) || equals("log.messageId", 106103) - trim: function: suffix substring: '/' fields: - log.srcInterface - log.dstInterface - where: log.messageId==106102 || log.messageId==106103 + where: equals("log.messageId", 106102) || equals("log.messageId", 106103) - trim: function: prefix substring: '''' fields: - origin.user - where: log.messageId==106102 || log.messageId==106103 + where: equals("log.messageId", 106102) || equals("log.messageId", 106103) - trim: function: suffix substring: '''' fields: - origin.user - where: log.messageId==106102 || log.messageId==106103 + where: equals("log.messageId", 106102) || equals("log.messageId", 106103) - trim: function: prefix substring: '(' fields: - origin.port - target.port - where: log.messageId==106102 || log.messageId==106103 + where: equals("log.messageId", 106102) || equals("log.messageId", 106103) - trim: function: suffix substring: ')' fields: - origin.port - target.port - where: log.messageId==106102 || log.messageId==106103 + where: equals("log.messageId", 106102) || equals("log.messageId", 106103) # Adding action result - add: 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") #......................................................................# # ASA-4-109017 - grok: @@ -337,19 +337,19 @@ pipeline: - fieldName: log.rest pattern: '{{.greedy}}' source: log.msg - where: log.messageId==109017 + where: equals("log.messageId", 109017) - trim: function: prefix substring: '(' fields: - log.limit - where: log.messageId==109017 + where: equals("log.messageId", 109017) - cast: fields: - log.limit to: int - where: log.messageId==109017 + where: equals("log.messageId", 109017) #......................................................................# # ASA-6-109101 - grok: @@ -369,7 +369,7 @@ pipeline: - fieldName: log.auditSessionId pattern: '{{.greedy}}' source: log.msg - where: log.messageId==109101 + where: equals("log.messageId", 109101) # Action result - grok: patterns: @@ -380,19 +380,19 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: log.messageId==109101 + where: equals("log.messageId", 109101) - trim: function: suffix substring: ',' fields: - origin.user - where: log.messageId==109101 + where: equals("log.messageId", 109101) - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==109101 + where: equals("log.messageId", 109101) #......................................................................# # ASA-4-109102 - grok: @@ -408,7 +408,7 @@ pipeline: - fieldName: log.auditSessionId pattern: '{{.greedy}}' source: log.msg - where: log.messageId==109102 + where: equals("log.messageId", 109102) # Action result - grok: patterns: @@ -417,13 +417,13 @@ pipeline: - fieldName: log.ciscoResult pattern: '{{.greedy}}' source: action - where: log.messageId==109102 + where: equals("log.messageId", 109102) - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==109102 + where: equals("log.messageId", 109102) #......................................................................# # ASA-3-109103 - grok: @@ -443,7 +443,7 @@ pipeline: - fieldName: log.auditSessionId pattern: '{{.greedy}}' source: log.msg - where: log.messageId==109103 + where: equals("log.messageId", 109103) # Action result - grok: patterns: @@ -452,19 +452,19 @@ pipeline: - fieldName: log.ciscoResult pattern: '{{.greedy}}' source: action - where: log.messageId==109103 + where: equals("log.messageId", 109103) - trim: function: suffix substring: ',' fields: - origin.user - where: log.messageId==109103 + where: equals("log.messageId", 109103) - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==109103 + where: equals("log.messageId", 109103) #......................................................................# # ASA-109201 to 109213 - grok: @@ -486,21 +486,21 @@ pipeline: - fieldName: action pattern: '{{.greedy}}' source: log.msg - where: 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: 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: log.messageId>=109201 && log.messageId<=109213 + where: greaterOrEqual("log.messageId", 109201) && lessOrEqual("log.messageId", 109213) #......................................................................# # ASA-6-113004 - grok: @@ -518,7 +518,7 @@ pipeline: - fieldName: origin.user pattern: '{{.greedy}}' source: log.msg - where: log.messageId==113004 + where: equals("log.messageId", 113004) # Action result - grok: patterns: @@ -527,25 +527,25 @@ pipeline: - fieldName: log.ciscoResult pattern: '{{.greedy}}' source: action - where: log.messageId==113004 + where: equals("log.messageId", 113004) - trim: function: suffix substring: ':' fields: - target.ip - where: log.messageId==113004 + where: equals("log.messageId", 113004) - trim: function: suffix substring: ',' fields: - target.ip - where: log.messageId==113004 + where: equals("log.messageId", 113004) - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==113004 + where: equals("log.messageId", 113004) #......................................................................# # ASA-6-113005 - grok: @@ -569,7 +569,7 @@ pipeline: - fieldName: origin.ip pattern: '{{.greedy}}' source: log.msg - where: log.messageId==113005 + where: equals("log.messageId", 113005) # Action result - grok: patterns: @@ -578,7 +578,7 @@ pipeline: - fieldName: log.ciscoResult pattern: '{{.greedy}}' source: action - where: log.messageId==113005 + where: equals("log.messageId", 113005) - trim: function: suffix substring: ':' @@ -586,13 +586,13 @@ pipeline: - log.reason - target.ip - origin.user - where: log.messageId==113005 + where: equals("log.messageId", 113005) - add: function: 'string' params: key: actionResult value: 'denied' - where: log.messageId==113005 + where: equals("log.messageId", 113005) #......................................................................# # ASA-6-113008 - grok: @@ -604,14 +604,14 @@ pipeline: - fieldName: origin.user pattern: '{{.greedy}}' source: log.msg - where: log.messageId==113008 + where: equals("log.messageId", 113008) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==113008 + where: equals("log.messageId", 113008) #......................................................................# # ASA-6-113009 # First variant @@ -626,19 +626,19 @@ pipeline: - fieldName: origin.user pattern: '{{.greedy}}' source: log.msg - where: log.messageId==113009 + where: equals("log.messageId", 113009) - trim: function: prefix substring: '(' fields: - log.policy - where: log.messageId==113009 + where: equals("log.messageId", 113009) - trim: function: suffix substring: ')' fields: - log.policy - where: log.messageId==113009 + where: equals("log.messageId", 113009) # Second variant - grok: patterns: @@ -651,20 +651,20 @@ pipeline: - fieldName: origin.user pattern: '{{.greedy}}' source: log.msg - where: log.messageId==113009 + where: equals("log.messageId", 113009) && !exists("origin.user") - trim: function: suffix substring: 'for' fields: - log.policy - where: log.messageId==113009 + where: equals("log.messageId", 113009) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==113009 + where: equals("log.messageId", 113009) #......................................................................# # ASA-6-113010 - grok: @@ -676,13 +676,13 @@ pipeline: - fieldName: origin.ip pattern: '{{.greedy}}' source: log.msg - where: log.messageId==113010 + where: equals("log.messageId", 113010) - trim: function: suffix substring: 'from server' fields: - origin.user - where: log.messageId==113010 + where: equals("log.messageId", 113010) #......................................................................# # ASA-6-113011 # First variant @@ -697,19 +697,19 @@ pipeline: - fieldName: origin.user pattern: '{{.greedy}}' source: log.msg - where: log.messageId==113011 + where: equals("log.messageId", 113011) - trim: function: prefix substring: '(' fields: - log.policy - where: log.messageId==113011 + where: equals("log.messageId", 113011) - trim: function: suffix substring: ')' fields: - log.policy - where: log.messageId==113011 + where: equals("log.messageId", 113011) # Second variant - grok: patterns: @@ -722,13 +722,13 @@ pipeline: - fieldName: origin.user pattern: '{{.greedy}}' source: log.msg - where: log.messageId==113011 + where: equals("log.messageId", 113011) && !exists("origin.user") - trim: function: suffix substring: 'for' fields: - log.policy - where: log.messageId==113011 + where: equals("log.messageId", 113011) #......................................................................# # ASA-6-113012 - grok: @@ -740,7 +740,7 @@ pipeline: - fieldName: origin.user pattern: '{{.greedy}}' source: log.msg - where: log.messageId==113012 + where: equals("log.messageId", 113012) # Action result - grok: patterns: @@ -749,13 +749,13 @@ pipeline: - fieldName: log.ciscoResult pattern: '{{.greedy}}' source: action - where: log.messageId==113012 + where: equals("log.messageId", 113012) - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==113012 + where: equals("log.messageId", 113012) #......................................................................# # ASA-6-113013 - grok: @@ -771,13 +771,13 @@ pipeline: - fieldName: origin.user pattern: '{{.greedy}}' source: log.msg - where: log.messageId==113013 + where: equals("log.messageId", 113013) - trim: function: suffix substring: ':' fields: - log.reason - where: log.messageId==113013 + where: equals("log.messageId", 113013) #......................................................................# # ASA-6-113014 - grok: @@ -793,7 +793,7 @@ pipeline: - fieldName: origin.user pattern: '{{.greedy}}' source: log.msg - where: log.messageId==113014 + where: equals("log.messageId", 113014) #......................................................................# # ASA-6-113015, 113017 - grok: @@ -813,14 +813,14 @@ pipeline: - fieldName: target.ip pattern: '{{.greedy}}' source: log.msg - where: log.messageId==113015 || log.messageId==113017 + where: equals("log.messageId", 113015) || equals("log.messageId", 113017) - trim: function: suffix substring: ':' fields: - log.reason - origin.user - where: log.messageId==113015 || log.messageId==113017 + where: equals("log.messageId", 113015) || equals("log.messageId", 113017) # Action result - grok: patterns: @@ -829,13 +829,13 @@ pipeline: - fieldName: log.ciscoResult pattern: '{{.greedy}}' source: action - where: log.messageId==113015 || log.messageId==113017 + where: equals("log.messageId", 113015) || equals("log.messageId", 113017) - add: function: 'string' params: key: actionResult value: 'denied' - where: log.messageId==113015 || log.messageId==113017 + where: equals("log.messageId", 113015) || equals("log.messageId", 113017) #......................................................................# # ASA-6-113016 - grok: @@ -859,7 +859,7 @@ pipeline: - fieldName: origin.ip pattern: '{{.greedy}}' source: log.msg - where: log.messageId==113016 + where: equals("log.messageId", 113016) - trim: function: suffix substring: ':' @@ -867,7 +867,7 @@ pipeline: - log.reason - target.ip - origin.user - where: log.messageId==113016 + where: equals("log.messageId", 113016) # Action result - grok: patterns: @@ -876,13 +876,13 @@ pipeline: - fieldName: log.ciscoResult pattern: '{{.greedy}}' source: action - where: log.messageId==113016 + where: equals("log.messageId", 113016) - add: function: 'string' params: key: actionResult value: 'denied' - where: log.messageId==113016 + where: equals("log.messageId", 113016) #......................................................................# # ASA-4-113019 - grok: @@ -922,7 +922,7 @@ pipeline: - fieldName: log.reason pattern: '{{.greedy}}' source: log.msg - where: log.messageId==113019 + where: equals("log.messageId", 113019) - trim: function: suffix substring: ',' @@ -934,20 +934,20 @@ pipeline: - log.duration - origin.bytesSent - origin.bytesReceived - where: log.messageId==113019 + where: equals("log.messageId", 113019) - cast: fields: - origin.bytesSent - origin.bytesReceived to: int - where: log.messageId==113019 + where: equals("log.messageId", 113019) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==113019 + where: equals("log.messageId", 113019) #......................................................................# # ASA-113031,113032,113033 # Common fields @@ -964,19 +964,19 @@ pipeline: - fieldName: log.rest pattern: '{{.greedy}}' source: log.msg - where: 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: 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: 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: @@ -987,7 +987,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.rest - where: 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 @@ -995,20 +995,20 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.rest - where: 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: 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: log.messageId==113031 || log.messageId==113032 || log.messageId==113033 + where: equals("log.messageId", 113031) || equals("log.messageId", 113032) || equals("log.messageId", 113033) #......................................................................# # ASA-113034,113035,113036,113038,113039 - grok: @@ -1024,20 +1024,20 @@ pipeline: - fieldName: log.rest pattern: '{{.greedy}}' source: log.msg - where: 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: 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: 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: '<' @@ -1045,7 +1045,7 @@ pipeline: - origin.group - origin.user - origin.ip - where: 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: '>' @@ -1053,21 +1053,21 @@ pipeline: - origin.group - origin.user - origin.ip - where: 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' params: key: actionResult value: 'accepted' - where: log.messageId==113039 + where: equals("log.messageId", 113039) # Adding action result - add: function: 'string' params: key: actionResult value: 'denied' - where: log.messageId==113035 || log.messageId==113038 + where: equals("log.messageId", 113035) || equals("log.messageId", 113038) #......................................................................# # ASA-4-113042 - grok: @@ -1099,33 +1099,33 @@ pipeline: - fieldName: log.rest pattern: '{{.greedy}}' source: log.msg - where: log.messageId==113042 + where: equals("log.messageId", 113042) - trim: function: suffix substring: 'from' fields: - action - where: log.messageId==113042 + where: equals("log.messageId", 113042) - trim: function: suffix substring: ':' fields: - log.srcInterface - log.dstInterface - where: log.messageId==113042 + where: equals("log.messageId", 113042) - trim: function: suffix substring: '/' fields: - origin.ip - target.ip - where: log.messageId==113042 + where: equals("log.messageId", 113042) - trim: function: suffix substring: 'at' fields: - log.remoteUser - where: log.messageId==113042 + where: equals("log.messageId", 113042) #......................................................................# # ASA-2-201003 - grok: @@ -1159,26 +1159,26 @@ pipeline: - fieldName: log.dstInterface pattern: '{{.greedy}}' source: log.msg - where: log.messageId==201003 + where: equals("log.messageId", 201003) - trim: function: prefix substring: '(' fields: - log.globalIp - where: log.messageId==201003 + where: equals("log.messageId", 201003) - trim: function: suffix substring: ')' fields: - log.globalIp - where: log.messageId==201003 + where: equals("log.messageId", 201003) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==201003 + where: equals("log.messageId", 201003) #......................................................................# # ASA-4-209003 - grok: @@ -1204,7 +1204,7 @@ pipeline: - fieldName: log.id pattern: '{{.greedy}}' source: log.msg - where: log.messageId==209003 + where: equals("log.messageId", 209003) - trim: function: suffix substring: ',' @@ -1212,19 +1212,19 @@ pipeline: - origin.ip - target.ip - protocol - where: log.messageId==209003 + where: equals("log.messageId", 209003) - cast: fields: - log.limit to: int - where: log.messageId==209003 + where: equals("log.messageId", 209003) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==209003 + where: equals("log.messageId", 209003) #......................................................................# # ASA-3-316001 - grok: @@ -1238,7 +1238,7 @@ pipeline: - fieldName: log.rest pattern: '{{.greedy}}' source: log.msg - where: log.messageId==316001 + where: equals("log.messageId", 316001) # Action result - grok: patterns: @@ -1247,14 +1247,14 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: log.messageId==316001 + where: equals("log.messageId", 316001) # Adding action result - add: function: 'string' params: key: actionResult value: 'denied' - where: log.messageId==316001 + where: equals("log.messageId", 316001) #......................................................................# # ASA-3-316002 - grok: @@ -1282,26 +1282,26 @@ pipeline: - fieldName: target.ip pattern: '{{.greedy}}' source: log.msg - where: log.messageId==316002 + where: equals("log.messageId", 316002) - trim: function: suffix substring: ',' fields: - protocol - where: log.messageId==316002 + where: equals("log.messageId", 316002) - cast: fields: - log.inIfNum - log.outIfNum to: int - where: log.messageId==316002 + where: equals("log.messageId", 316002) # Adding action result - add: function: 'string' params: key: actionResult value: 'denied' - where: log.messageId==316002 + where: equals("log.messageId", 316002) #......................................................................# # ASA-302003,302004 # First variant @@ -1322,7 +1322,7 @@ pipeline: - fieldName: log.tmpFromip pattern: '({{.ipv4}}|{{.ipv6}}|{{.hostname}})((\s)?/(\s)?{{.integer}}|(\s)?/)?' source: log.msg - where: log.messageId==302003 || log.messageId==302004 + where: equals("log.messageId", 302003) || equals("log.messageId", 302004) # Second variant - grok: patterns: @@ -1339,7 +1339,7 @@ pipeline: - fieldName: log.tmpFromip pattern: '({{.ipv4}}|{{.ipv6}})((\s)?/(\s)?{{.integer}}|(\s)?/)?' source: log.msg - where: log.messageId==302003 || log.messageId==302004 + where: equals("log.messageId", 302003) || equals("log.messageId", 302004) # Parsing from/to ip and port - grok: patterns: @@ -1348,13 +1348,13 @@ pipeline: - fieldName: target.port pattern: '{{.greedy}}' source: log.tmpToip - where: 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: log.messageId==302003 || log.messageId==302004 + where: equals("log.messageId", 302003) || equals("log.messageId", 302004) - grok: patterns: - fieldName: origin.ip @@ -1362,33 +1362,33 @@ pipeline: - fieldName: origin.port pattern: '{{.greedy}}' source: log.tmpFromip - where: 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: 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: 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: 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: log.messageId==302003 || log.messageId==302004 + where: equals("log.messageId", 302003) || equals("log.messageId", 302004) #......................................................................# # ASA-6-302012 - grok: @@ -1406,20 +1406,20 @@ pipeline: - fieldName: target.ip pattern: '{{.greedy}}' source: log.msg - where: log.messageId==302012 + where: equals("log.messageId", 302012) - trim: function: prefix substring: '/' fields: - origin.port - where: log.messageId==302012 + where: equals("log.messageId", 302012) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==302012 + where: equals("log.messageId", 302012) #......................................................................# # ASA-6-302013 - grok: @@ -1461,7 +1461,7 @@ pipeline: - fieldName: log.rest pattern: '{{.greedy}}' source: log.msg - where: log.messageId==302013 + where: equals("log.messageId", 302013) # Parsing direction field - grok: patterns: @@ -1469,10 +1469,8 @@ pipeline: pattern: 'Built' - fieldName: log.direction pattern: '{{.word}}' - - fieldName: log.irrelevant - pattern: '{{.greedy}}' source: log.tmpDirection - where: log.messageId==302013 + where: equals("log.messageId", 302013) # Parsing firewall user from - grok: patterns: @@ -1483,7 +1481,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserFrom - where: log.messageId==302013 + where: equals("log.messageId", 302013) # Parsing firewall user to, and AAA name of the user if exists - grok: patterns: @@ -1498,7 +1496,7 @@ pipeline: - fieldName: origin.user pattern: '{{.data}}\)' source: log.rest - where: log.messageId==302013 + where: equals("log.messageId", 302013) - grok: patterns: @@ -1509,7 +1507,7 @@ pipeline: - fieldName: log.firewallUserTo pattern: '{{.data}}\)' source: log.rest - where: log.messageId==302013 + where: equals("log.messageId", 302013) - trim: function: suffix @@ -1518,13 +1516,13 @@ pipeline: - log.firewallUserFrom - log.firewallUserTo - origin.user - where: log.messageId==302013 + where: equals("log.messageId", 302013) # Removing tmp fields - delete: fields: - log.tmpDirection - log.tmpFwUserFrom - where: log.messageId==302013 + where: equals("log.messageId", 302013) # Cleaning fields - trim: @@ -1533,7 +1531,7 @@ pipeline: fields: - log.srcInterface - log.dstInterface - where: log.messageId==302013 + where: equals("log.messageId", 302013) - trim: function: suffix substring: '/' @@ -1542,20 +1540,20 @@ pipeline: - target.ip - log.mappedIpFrom - log.mappedIpTo - where: log.messageId==302013 + where: equals("log.messageId", 302013) - trim: function: suffix substring: 'for' fields: - log.connectionId - where: log.messageId==302013 + where: equals("log.messageId", 302013) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==302013 + where: equals("log.messageId", 302013) #......................................................................# # ASA-6-302014 - grok: @@ -1593,7 +1591,7 @@ pipeline: - fieldName: log.rest pattern: '{{.greedy}}' source: log.msg - where: log.messageId==302014 + where: equals("log.messageId", 302014) # Parsing firewall user from - grok: patterns: @@ -1604,7 +1602,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserFrom - where: log.messageId==302014 + where: equals("log.messageId", 302014) # Parsing firewall user to - grok: patterns: @@ -1615,7 +1613,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserTo - where: log.messageId==302014 + where: equals("log.messageId", 302014) # Parsing AAA user if exists - grok: patterns: @@ -1624,7 +1622,7 @@ pipeline: - fieldName: origin.user pattern: '{{.data}}\)' source: log.rest - where: log.messageId==302014 + where: equals("log.messageId", 302014) - trim: function: suffix @@ -1633,13 +1631,13 @@ pipeline: - log.firewallUserFrom - log.firewallUserTo - origin.user - where: log.messageId==302014 + where: equals("log.messageId", 302014) # Removing tmp fields - delete: fields: - log.tmpFwUserFrom - log.tmpFwUserTo - where: log.messageId==302014 + where: equals("log.messageId", 302014) # Cleaning fields - trim: @@ -1648,27 +1646,27 @@ pipeline: fields: - log.srcInterface - log.dstInterface - where: log.messageId==302014 + where: equals("log.messageId", 302014) - trim: function: suffix substring: '/' fields: - origin.ip - target.ip - where: log.messageId==302014 + where: equals("log.messageId", 302014) - trim: function: suffix substring: 'for' fields: - log.connectionId - where: log.messageId==302014 + where: equals("log.messageId", 302014) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==302014 + where: equals("log.messageId", 302014) #......................................................................# # ASA-6-302015 - grok: @@ -1712,7 +1710,7 @@ pipeline: - fieldName: log.rest pattern: '{{.greedy}}' source: log.msg - where: log.messageId==302015 + where: equals("log.messageId", 302015) # Parsing direction field - grok: patterns: @@ -1721,7 +1719,7 @@ pipeline: - fieldName: log.direction pattern: '{{.greedy}}' source: log.tmpDirection - where: log.messageId==302015 + where: equals("log.messageId", 302015) # Parsing firewall user from - grok: patterns: @@ -1732,7 +1730,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserFrom - where: log.messageId==302015 + where: equals("log.messageId", 302015) # Parsing firewall user to, and AAA name of the user if exists - grok: patterns: @@ -1747,7 +1745,7 @@ pipeline: - fieldName: origin.user pattern: '{{.data}}\)' source: log.rest - where: log.messageId==302015 + where: equals("log.messageId", 302015) - grok: patterns: @@ -1758,7 +1756,7 @@ pipeline: - fieldName: log.firewallUserTo pattern: '{{.data}}\)' source: log.rest - where: log.messageId==302015 + where: equals("log.messageId", 302015) - trim: function: suffix @@ -1767,13 +1765,13 @@ pipeline: - log.firewallUserFrom - log.firewallUserTo - origin.user - where: log.messageId==302015 + where: equals("log.messageId", 302015) # Removing tmp fields - delete: fields: - log.tmpDirection - log.tmpFwUserFrom - where: log.messageId==302015 + where: equals("log.messageId", 302015) # Cleaning fields - trim: @@ -1782,7 +1780,7 @@ pipeline: fields: - log.srcInterface - log.dstInterface - where: log.messageId==302015 + where: equals("log.messageId", 302015) - trim: function: suffix substring: '/' @@ -1791,20 +1789,20 @@ pipeline: - target.ip - log.mappedIpFrom - log.mappedIpTo - where: log.messageId==302015 + where: equals("log.messageId", 302015) - trim: function: suffix substring: 'for' fields: - log.connectionId - where: log.messageId==302015 + where: equals("log.messageId", 302015) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==302015 + where: equals("log.messageId", 302015) #......................................................................# # ASA-6-302016 - grok: @@ -1838,7 +1836,7 @@ pipeline: - fieldName: log.rest pattern: '{{.greedy}}' source: log.msg - where: log.messageId==302016 + where: equals("log.messageId", 302016) # Parsing firewall user from - grok: patterns: @@ -1849,7 +1847,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserFrom - where: log.messageId==302016 + where: equals("log.messageId", 302016) # Parsing firewall user to - grok: patterns: @@ -1860,7 +1858,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserTo - where: log.messageId==302016 + where: equals("log.messageId", 302016) # Parsing firewall user to, and AAA name of the user if exists - grok: patterns: @@ -1869,7 +1867,7 @@ pipeline: - fieldName: origin.bytesSent pattern: '{{.integer}}' source: log.rest - where: log.messageId==302016 + where: equals("log.messageId", 302016) - grok: patterns: @@ -1882,7 +1880,7 @@ pipeline: - fieldName: origin.user pattern: '{{.data}}\)' source: log.rest - where: log.messageId==302016 + where: equals("log.messageId", 302016) - trim: function: suffix @@ -1891,13 +1889,13 @@ pipeline: - log.firewallUserFrom - log.firewallUserTo - origin.user - where: log.messageId==302016 + where: equals("log.messageId", 302016) # Removing tmp fields - delete: fields: - log.tmpFwUserFrom - log.tmpFwUserTo - where: log.messageId==302016 + where: equals("log.messageId", 302016) # Cleaning fields - trim: @@ -1906,27 +1904,27 @@ pipeline: fields: - log.srcInterface - log.dstInterface - where: log.messageId==302016 + where: equals("log.messageId", 302016) - trim: function: suffix substring: '/' fields: - origin.ip - target.ip - where: log.messageId==302016 + where: equals("log.messageId", 302016) - trim: function: suffix substring: 'for' fields: - log.connectionId - where: log.messageId==302016 + where: equals("log.messageId", 302016) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==302016 + where: equals("log.messageId", 302016) #......................................................................# # ASA-6-302017 - grok: @@ -1962,7 +1960,7 @@ pipeline: - fieldName: log.rest pattern: '{{.greedy}}' source: log.msg - where: log.messageId==302017 + where: equals("log.messageId", 302017) # Parsing firewall user from - grok: patterns: @@ -1973,7 +1971,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserFrom - where: log.messageId==302017 + where: equals("log.messageId", 302017) # Parsing translatedCid and, firewall user to and AAA user if present - grok: patterns: @@ -1982,13 +1980,13 @@ pipeline: - fieldName: log.tmpUserInfo pattern: '{{.greedy}}' source: log.rest - where: log.messageId==302017 + where: equals("log.messageId", 302017) - grok: patterns: - fieldName: log.translatedCid pattern: '{{.data}}\)' source: log.rest - where: log.messageId==302017 + where: equals("log.messageId", 302017) # FW user and AAA user - grok: patterns: @@ -1997,13 +1995,13 @@ pipeline: - fieldName: target.user pattern: '\({{.data}}\)' source: log.tmpUserInfo - where: log.messageId==302017 + where: equals("log.messageId", 302017) - grok: patterns: - fieldName: log.tmpFwUserTo pattern: '\({{.data}}\)' source: log.tmpUserInfo - where: log.messageId==302017 + where: equals("log.messageId", 302017) # Parsing firewall user to - grok: patterns: @@ -2011,10 +2009,8 @@ pipeline: pattern: '\(' - fieldName: log.firewallUserTo pattern: '{{.data}}\)' - - fieldName: log.irrelevant - pattern: '{{.greedy}}' source: log.tmpFwUserTo - where: log.messageId==302017 + where: equals("log.messageId", 302017) - trim: function: suffix substring: ')' @@ -2023,14 +2019,20 @@ pipeline: - log.firewallUserTo - target.user - log.translatedCid - where: log.messageId==302017 + where: equals("log.messageId", 302017) - trim: function: suffix substring: '(' fields: - log.realCid - target.user - where: log.messageId==302017 + where: equals("log.messageId", 302017) + - trim: + function: prefix + substring: '(' + fields: + - target.user + where: equals("log.messageId", 302017) # Removing tmp fields - delete: fields: @@ -2038,7 +2040,7 @@ pipeline: - log.tmpFwUserFrom - log.tmpFwUserTo - log.tmpUserInfo - where: log.messageId==302017 + where: equals("log.messageId", 302017) # Cleaning fields - trim: function: suffix @@ -2046,27 +2048,27 @@ pipeline: fields: - log.srcInterface - log.dstInterface - where: log.messageId==302017 + where: equals("log.messageId", 302017) - trim: function: suffix substring: '/' fields: - target.ip - log.translatedIpTo - where: log.messageId==302017 + where: equals("log.messageId", 302017) - trim: function: suffix substring: 'from' fields: - log.connectionId - where: log.messageId==302017 + where: equals("log.messageId", 302017) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==302017 + where: equals("log.messageId", 302017) #......................................................................# # ASA-6-302018 - grok: @@ -2104,7 +2106,7 @@ pipeline: - fieldName: log.rest pattern: '{{.greedy}}' source: log.msg - where: log.messageId==302018 + where: equals("log.messageId", 302018) # Parsing firewall user from - grok: patterns: @@ -2115,7 +2117,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserFrom - where: log.messageId==302018 + where: equals("log.messageId", 302018) # Parsing firewall user to - grok: patterns: @@ -2126,7 +2128,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserTo - where: log.messageId==302018 + where: equals("log.messageId", 302018) # Parsing firewall user to, and AAA name of the user if exists - grok: patterns: @@ -2135,7 +2137,7 @@ pipeline: - fieldName: origin.bytesSent pattern: '{{.integer}}' source: log.rest - where: log.messageId==302018 + where: equals("log.messageId", 302018) - grok: patterns: @@ -2148,7 +2150,7 @@ pipeline: - fieldName: origin.user pattern: '{{.data}}\)' source: log.rest - where: log.messageId==302018 + where: equals("log.messageId", 302018) - trim: function: suffix @@ -2158,47 +2160,47 @@ pipeline: - log.firewallUserTo - origin.user - log.translatedCid - where: log.messageId==302018 + where: equals("log.messageId", 302018) # Removing tmp fields - delete: fields: - log.tmpFwUserFrom - log.tmpFwUserTo - where: log.messageId==302018 + where: equals("log.messageId", 302018) # Cleaning fields - trim: function: suffix substring: '(' fields: - log.realCid - where: log.messageId==302018 + where: equals("log.messageId", 302018) - trim: function: suffix substring: ':' fields: - log.srcInterface - log.dstInterface - where: log.messageId==302018 + where: equals("log.messageId", 302018) - trim: function: suffix substring: '/' fields: - target.ip - log.translatedIpTo - where: log.messageId==302018 + where: equals("log.messageId", 302018) - trim: function: suffix substring: 'from' fields: - log.connectionId - where: log.messageId==302018 + where: equals("log.messageId", 302018) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==302018 + where: equals("log.messageId", 302018) #......................................................................# # ASA-6-302020, 302021 # 302020 @@ -2223,7 +2225,7 @@ pipeline: - fieldName: log.icmpCode pattern: '{{.greedy}}' source: log.msg - where: log.messageId==302020 + where: equals("log.messageId", 302020) # 302021 - grok: patterns: @@ -2244,7 +2246,7 @@ pipeline: - fieldName: log.icmpCode pattern: '{{.greedy}}' source: log.msg - where: log.messageId==302021 + where: equals("log.messageId", 302021) # Common parsing for 302020 and 302021 # For parsing faddr and fwuser from (Begin with ip/port), or icmpSeqNum - grok: @@ -2256,7 +2258,7 @@ pipeline: - fieldName: log.tmpFwUserFrom pattern: '{{.greedy}}' source: log.tmpFaddrAndFwUser - where: log.messageId==302020 || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) - grok: patterns: - fieldName: origin.ip @@ -2264,7 +2266,7 @@ pipeline: - fieldName: log.tmpFwUserFrom pattern: '{{.greedy}}' source: log.tmpFaddrAndFwUser - where: log.messageId==302020 || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) # icmpSeqNum variants - grok: patterns: @@ -2275,7 +2277,7 @@ pipeline: - fieldName: log.irrelevant pattern: 'gaddr' source: log.tmpFaddrAndFwUser - where: log.messageId==302020 || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) - grok: patterns: - fieldName: log.icmpSeqNum @@ -2283,14 +2285,14 @@ pipeline: - fieldName: log.tmpFwUserFrom pattern: 'gaddr' source: log.tmpFaddrAndFwUser - where: 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: log.messageId==302020 || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) # Continue with firewall user from - grok: patterns: @@ -2301,7 +2303,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserFrom - where: 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: @@ -2312,7 +2314,7 @@ pipeline: - fieldName: log.tmpFwUserGlobal pattern: '{{.greedy}}' source: log.tmpGaddrAndFwUser - where: log.messageId==302020 || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) - grok: patterns: - fieldName: log.globalIp @@ -2320,7 +2322,7 @@ pipeline: - fieldName: log.tmpFwUserGlobal pattern: '{{.greedy}}' source: log.tmpGaddrAndFwUser - where: log.messageId==302020 || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) # icmpType variants - grok: patterns: @@ -2331,7 +2333,7 @@ pipeline: - fieldName: log.irrelevant pattern: 'laddr' source: log.tmpGaddrAndFwUser - where: log.messageId==302020 || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) - grok: patterns: - fieldName: log.icmpType @@ -2339,20 +2341,20 @@ pipeline: - fieldName: log.tmpFwUserGlobal pattern: 'laddr' source: log.tmpGaddrAndFwUser - where: 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: log.messageId==302020 || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) - trim: function: suffix substring: 'code' fields: - log.icmpType - where: log.messageId==302020 || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) # Continue with global firewall - grok: patterns: @@ -2363,7 +2365,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserGlobal - where: 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: @@ -2374,7 +2376,7 @@ pipeline: - fieldName: log.tmpFwUserTo pattern: '{{.greedy}}' source: log.tmpLaddrAndFwUser - where: log.messageId==302020 || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) - grok: patterns: - fieldName: target.ip @@ -2382,7 +2384,7 @@ pipeline: - fieldName: log.tmpFwUserTo pattern: '{{.greedy}}' source: log.tmpLaddrAndFwUser - where: log.messageId==302020 || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) # Continue with firewall user to - grok: patterns: @@ -2393,7 +2395,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserTo - where: log.messageId==302020 || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) # Cleaning fields - trim: function: suffix @@ -2401,7 +2403,7 @@ pipeline: fields: - target.ip - origin.ip - where: log.messageId==302020 || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) - trim: function: suffix substring: ')' @@ -2409,7 +2411,7 @@ pipeline: - log.firewallUserFrom - log.firewallUserTo - log.globalFirewallUser - where: log.messageId==302020 || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) # Removing tmp fields - delete: fields: @@ -2419,14 +2421,14 @@ pipeline: - log.tmpFaddrAndFwUser - log.tmpGaddrAndFwUser - log.tmpLaddrAndFwUser - where: 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: log.messageId==302020 || log.messageId==302021 + where: equals("log.messageId", 302020) || equals("log.messageId", 302021) #......................................................................# # ASA-6-302022, 302024, 302026 - grok: @@ -2458,7 +2460,7 @@ pipeline: - fieldName: log.tmpMappedIpPortTo pattern: '\({{.data}}\)' source: log.msg - where: 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: @@ -2472,7 +2474,7 @@ pipeline: - fieldName: log.irrelevant pattern: '\)' source: log.tmpMappedIpPortFrom - where: 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: @@ -2483,7 +2485,7 @@ pipeline: - fieldName: log.irrelevant pattern: '\)' source: log.tmpMappedIpPortFrom - where: 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: @@ -2497,7 +2499,7 @@ pipeline: - fieldName: log.irrelevant pattern: '\)' source: log.tmpMappedIpPortTo - where: 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: @@ -2508,21 +2510,21 @@ pipeline: - fieldName: log.irrelevant pattern: '\)' source: log.tmpMappedIpPortTo - where: 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: 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: 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: '/' @@ -2531,20 +2533,20 @@ pipeline: - target.ip - log.mappedIpFrom - log.mappedIpTo - where: 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: 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: log.messageId==302022 || log.messageId==302024 || log.messageId==302026 + where: equals("log.messageId", 302022) || equals("log.messageId", 302024) || equals("log.messageId", 302026) #......................................................................# # ASA-6-302023, 302025, 302027 - grok: @@ -2580,7 +2582,7 @@ pipeline: - fieldName: log.reason pattern: '{{.greedy}}' source: log.msg - where: 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 @@ -2588,21 +2590,21 @@ pipeline: fields: - log.srcInterface - log.dstInterface - where: 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: 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: log.messageId==302023 || log.messageId==302025 || log.messageId==302027 + where: equals("log.messageId", 302023) || equals("log.messageId", 302025) || equals("log.messageId", 302027) #......................................................................# # ASA-302033,302034 - grok: @@ -2622,7 +2624,7 @@ pipeline: - fieldName: log.tmpIpPortTo pattern: '{{.greedy}}' source: log.msg - where: log.messageId==302033 || log.messageId==302034 + where: equals("log.messageId", 302033) || equals("log.messageId", 302034) # Parsing optional ports from y to - grok: patterns: @@ -2631,7 +2633,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpPortFrom - where: log.messageId==302033 || log.messageId==302034 + where: equals("log.messageId", 302033) || equals("log.messageId", 302034) - grok: patterns: - fieldName: target.ip @@ -2639,13 +2641,13 @@ pipeline: - fieldName: target.port pattern: '{{.greedy}}' source: log.tmpIpPortTo - where: 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: log.messageId==302033 || log.messageId==302034 + where: equals("log.messageId", 302033) || equals("log.messageId", 302034) # Cleaning fields - trim: function: suffix @@ -2653,33 +2655,33 @@ pipeline: fields: - log.srcInterface - log.dstInterface - where: 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: 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: log.messageId==302033 || log.messageId==302034 + where: equals("log.messageId", 302033) || equals("log.messageId", 302034) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==302033 + where: equals("log.messageId", 302033) - add: function: 'string' params: key: actionResult value: 'denied' - where: log.messageId==302034 + where: equals("log.messageId", 302034) #......................................................................# #ASA-6-302035 - grok: @@ -2717,7 +2719,7 @@ pipeline: - fieldName: log.rest pattern: '{{.greedy}}' source: log.msg - where: log.messageId==302035 + where: equals("log.messageId", 302035) # Parsing direction field - grok: patterns: @@ -2730,7 +2732,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpDirection - where: log.messageId==302035 + where: equals("log.messageId", 302035) # Parsing firewall user from - grok: patterns: @@ -2741,7 +2743,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserFrom - where: log.messageId==302035 + where: equals("log.messageId", 302035) # Parsing port to, firewall user to, and, from user - grok: patterns: @@ -2752,7 +2754,7 @@ pipeline: - fieldName: log.tmpFwUserTo pattern: '{{.greedy}}' source: log.rest - where: log.messageId==302035 + where: equals("log.messageId", 302035) # Without user info - grok: patterns: @@ -2761,7 +2763,7 @@ pipeline: - fieldName: log.irrelevant pattern: '\)' source: log.rest - where: log.messageId==302035 + where: equals("log.messageId", 302035) # Parsing firewall user to # With origin.user - grok: @@ -2775,7 +2777,7 @@ pipeline: - fieldName: origin.user pattern: '{{.data}}\)' source: log.tmpFwUserTo - where: log.messageId==302035 + where: equals("log.messageId", 302035) # Without origin.user - grok: patterns: @@ -2784,7 +2786,7 @@ pipeline: - fieldName: log.firewallUserTo pattern: '{{.data}}\)' source: log.tmpFwUserTo - where: log.messageId==302035 + where: equals("log.messageId", 302035) # Decoding outside and inside sg_info - grok: @@ -2794,7 +2796,7 @@ pipeline: - fieldName: log.outsideSgInfo pattern: '{{.data}}\)' source: log.firewallUserFrom - where: log.messageId==302035 + where: equals("log.messageId", 302035) - grok: patterns: - fieldName: log.firewallUserTo @@ -2802,7 +2804,7 @@ pipeline: - fieldName: log.insideSgInfo pattern: '{{.data}}\)' source: log.firewallUserTo - where: log.messageId==302035 + where: equals("log.messageId", 302035) - trim: function: suffix substring: ')' @@ -2812,14 +2814,14 @@ pipeline: - origin.user - log.insideSgInfo - log.outsideSgInfo - where: log.messageId==302035 + where: equals("log.messageId", 302035) # Removing tmp fields - delete: fields: - log.tmpDirection - log.tmpFwUserFrom - log.tmpFwUserTo - where: log.messageId==302035 + where: equals("log.messageId", 302035) # Cleaning fields - trim: @@ -2828,7 +2830,7 @@ pipeline: fields: - log.srcInterface - log.dstInterface - where: log.messageId==302035 + where: equals("log.messageId", 302035) - trim: function: suffix substring: '/' @@ -2837,27 +2839,27 @@ pipeline: - target.ip - log.mappedIpFrom - log.mappedIpTo - where: log.messageId==302035 + where: equals("log.messageId", 302035) - trim: function: suffix substring: 'for' fields: - log.connectionId - where: log.messageId==302035 + where: equals("log.messageId", 302035) - trim: function: suffix substring: ',' fields: - log.firewallUserFrom - log.firewallUserTo - where: log.messageId==302035 + where: equals("log.messageId", 302035) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==302035 + where: equals("log.messageId", 302035) #......................................................................# # ASA-6-302036 - grok: @@ -2891,7 +2893,7 @@ pipeline: - fieldName: log.reason pattern: '{{.greedy}}' source: log.msg - where: log.messageId==302036 + where: equals("log.messageId", 302036) # Protocol - grok: patterns: @@ -2902,7 +2904,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: log.messageId==302036 + where: equals("log.messageId", 302036) # Decoding outside and inside firewall user, and sg_info # Side from - grok: @@ -2916,7 +2918,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserFrom - where: log.messageId==302036 + where: equals("log.messageId", 302036) - grok: patterns: - fieldName: log.irrelevant @@ -2926,7 +2928,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserFrom - where: log.messageId==302036 + where: equals("log.messageId", 302036) # Side to - grok: patterns: @@ -2939,7 +2941,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserTo - where: log.messageId==302036 + where: equals("log.messageId", 302036) - grok: patterns: - fieldName: log.irrelevant @@ -2949,7 +2951,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserTo - where: log.messageId==302036 + where: equals("log.messageId", 302036) # Parsing origin.user from log.reason field - grok: patterns: @@ -2958,7 +2960,7 @@ pipeline: - fieldName: origin.user pattern: '{{.data}}\)' source: log.reason - where: log.messageId==302036 + where: equals("log.messageId", 302036) - trim: function: suffix substring: ')' @@ -2966,13 +2968,13 @@ pipeline: - log.firewallUserFrom - log.firewallUserTo - origin.user - where: log.messageId==302036 + where: equals("log.messageId", 302036) # Removing tmp fields - delete: fields: - log.tmpFwUserFrom - log.tmpFwUserTo - where: log.messageId==302036 + where: equals("log.messageId", 302036) # Cleaning fields - trim: function: suffix @@ -2980,34 +2982,34 @@ pipeline: fields: - log.srcInterface - log.dstInterface - where: log.messageId==302036 + where: equals("log.messageId", 302036) - trim: function: suffix substring: '/' fields: - origin.ip - target.ip - where: log.messageId==302036 + where: equals("log.messageId", 302036) - trim: function: suffix substring: 'for' fields: - log.connectionId - where: log.messageId==302036 + where: equals("log.messageId", 302036) - trim: function: suffix substring: ',' fields: - log.firewallUserFrom - log.firewallUserTo - where: log.messageId==302036 + where: equals("log.messageId", 302036) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==302036 + where: equals("log.messageId", 302036) #......................................................................# # ASA-6-302303 - grok: @@ -3047,7 +3049,7 @@ pipeline: - fieldName: log.rest pattern: '{{.greedy}}' source: log.msg - where: log.messageId==302303 + where: equals("log.messageId", 302303) # Protocol - grok: patterns: @@ -3058,7 +3060,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: log.messageId==302303 + where: equals("log.messageId", 302303) # Cleaning fields - trim: function: suffix @@ -3066,7 +3068,7 @@ pipeline: fields: - log.srcInterface - log.dstInterface - where: log.messageId==302303 + where: equals("log.messageId", 302303) - trim: function: suffix substring: '/' @@ -3075,20 +3077,20 @@ pipeline: - target.ip - log.mappedIpFrom - log.mappedIpTo - where: log.messageId==302303 + where: equals("log.messageId", 302303) - trim: function: suffix substring: 'from' fields: - log.connectionId - where: log.messageId==302303 + where: equals("log.messageId", 302303) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==302303 + where: equals("log.messageId", 302303) #......................................................................# # ASA-6-302304 - grok: @@ -3122,18 +3124,18 @@ pipeline: - fieldName: log.reason pattern: '{{.greedy}}' source: log.msg - where: log.messageId==302304 + where: equals("log.messageId", 302304) # Protocol - grok: patterns: - fieldName: log.irrelevant - pattern: 'Built' + pattern: 'Teardown' - fieldName: protocol pattern: '{{.word}}' - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: log.messageId==302304 + where: equals("log.messageId", 302304) # Cleaning fields - trim: function: suffix @@ -3141,27 +3143,27 @@ pipeline: fields: - log.srcInterface - log.dstInterface - where: log.messageId==302304 + where: equals("log.messageId", 302304) - trim: function: suffix substring: '/' fields: - origin.ip - target.ip - where: log.messageId==302304 + where: equals("log.messageId", 302304) - trim: function: suffix substring: 'from' fields: - log.connectionId - where: log.messageId==302304 + where: equals("log.messageId", 302304) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==302304 + where: equals("log.messageId", 302304) #......................................................................# # ASA-6-302305 - grok: @@ -3199,7 +3201,7 @@ pipeline: - fieldName: log.rest pattern: '{{.greedy}}' source: log.msg - where: log.messageId==302305 + where: equals("log.messageId", 302305) # Protocol - grok: patterns: @@ -3210,7 +3212,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: log.messageId==302305 + where: equals("log.messageId", 302305) # Parsing firewall user from - grok: patterns: @@ -3221,7 +3223,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserFrom - where: log.messageId==302305 + where: equals("log.messageId", 302305) # Parsing port to, firewall user to, and, from user - grok: patterns: @@ -3232,7 +3234,7 @@ pipeline: - fieldName: log.tmpFwUserTo pattern: '{{.greedy}}' source: log.rest - where: log.messageId==302305 + where: equals("log.messageId", 302305) # Without user info - grok: patterns: @@ -3241,7 +3243,7 @@ pipeline: - fieldName: log.irrelevant pattern: '\)' source: log.rest - where: log.messageId==302305 + where: equals("log.messageId", 302305) # Parsing firewall user to - grok: patterns: @@ -3250,7 +3252,7 @@ pipeline: - fieldName: log.firewallUserTo pattern: '{{.data}}\)' source: log.tmpFwUserTo - where: log.messageId==302305 + where: equals("log.messageId", 302305) # Decoding outside and inside sg_info - grok: @@ -3260,7 +3262,7 @@ pipeline: - fieldName: log.outsideSgInfo pattern: '{{.data}}\)' source: log.firewallUserFrom - where: log.messageId==302305 + where: equals("log.messageId", 302305) - grok: patterns: - fieldName: log.firewallUserTo @@ -3268,7 +3270,7 @@ pipeline: - fieldName: log.insideSgInfo pattern: '{{.data}}\)' source: log.firewallUserTo - where: log.messageId==302305 + where: equals("log.messageId", 302305) - trim: function: suffix substring: ')' @@ -3278,13 +3280,13 @@ pipeline: - origin.user - log.insideSgInfo - log.outsideSgInfo - where: log.messageId==302305 + where: equals("log.messageId", 302305) # Removing tmp fields - delete: fields: - log.tmpFwUserFrom - log.tmpFwUserTo - where: log.messageId==302305 + where: equals("log.messageId", 302305) # Cleaning fields - trim: @@ -3293,7 +3295,7 @@ pipeline: fields: - log.srcInterface - log.dstInterface - where: log.messageId==302305 + where: equals("log.messageId", 302305) - trim: function: suffix substring: '/' @@ -3302,27 +3304,27 @@ pipeline: - target.ip - log.mappedIpFrom - log.mappedIpTo - where: log.messageId==302305 + where: equals("log.messageId", 302305) - trim: function: suffix substring: 'for' fields: - log.connectionId - where: log.messageId==302305 + where: equals("log.messageId", 302305) - trim: function: suffix substring: ',' fields: - log.firewallUserFrom - log.firewallUserTo - where: log.messageId==302305 + where: equals("log.messageId", 302305) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==302305 + where: equals("log.messageId", 302305) #......................................................................# # ASA-6-302306 - grok: @@ -3356,7 +3358,7 @@ pipeline: - fieldName: log.reason pattern: '{{.greedy}}' source: log.msg - where: log.messageId==302306 + where: equals("log.messageId", 302306) # Protocol - grok: patterns: @@ -3367,7 +3369,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: log.messageId==302306 + where: equals("log.messageId", 302306) # Parsing firewall user from - grok: patterns: @@ -3378,7 +3380,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserFrom - where: log.messageId==302306 + where: equals("log.messageId", 302306) # Parsing firewall user to - grok: patterns: @@ -3387,7 +3389,7 @@ pipeline: - fieldName: log.firewallUserTo pattern: '{{.data}}\)' source: log.tmpFwUserTo - where: log.messageId==302306 + where: equals("log.messageId", 302306) # Decoding outside and inside sg_info - grok: @@ -3397,7 +3399,7 @@ pipeline: - fieldName: log.outsideSgInfo pattern: '{{.data}}\)' source: log.firewallUserFrom - where: log.messageId==302306 + where: equals("log.messageId", 302306) - grok: patterns: - fieldName: log.firewallUserTo @@ -3405,7 +3407,7 @@ pipeline: - fieldName: log.insideSgInfo pattern: '{{.data}}\)' source: log.firewallUserTo - where: log.messageId==302306 + where: equals("log.messageId", 302306) - trim: function: suffix substring: ')' @@ -3414,13 +3416,13 @@ pipeline: - log.firewallUserTo - log.insideSgInfo - log.outsideSgInfo - where: log.messageId==302306 + where: equals("log.messageId", 302306) # Removing tmp fields - delete: fields: - log.tmpFwUserFrom - log.tmpFwUserTo - where: log.messageId==302306 + where: equals("log.messageId", 302306) # Cleaning fields - trim: @@ -3429,34 +3431,34 @@ pipeline: fields: - log.srcInterface - log.dstInterface - where: log.messageId==302306 + where: equals("log.messageId", 302306) - trim: function: suffix substring: '/' fields: - origin.ip - target.ip - where: log.messageId==302306 + where: equals("log.messageId", 302306) - trim: function: suffix substring: 'for' fields: - log.connectionId - where: log.messageId==302306 + where: equals("log.messageId", 302306) - trim: function: suffix substring: ',' fields: - log.firewallUserFrom - log.firewallUserTo - where: log.messageId==302306 + where: equals("log.messageId", 302306) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==302306 + where: equals("log.messageId", 302306) #......................................................................# # ASA-6-305009 - grok: @@ -3476,7 +3478,7 @@ pipeline: - fieldName: log.mappedIpTo pattern: '{{.greedy}}' source: log.msg - where: log.messageId==305009 + where: equals("log.messageId", 305009) # Parsing srcInterface # With ACL - grok: @@ -3488,14 +3490,14 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpAclSrcInterface - where: log.messageId==305009 + where: equals("log.messageId", 305009) # Without ACL - grok: patterns: - fieldName: log.srcInterface pattern: '{{.data}}:' source: log.tmpAclSrcInterface - where: log.messageId==305009 + where: equals("log.messageId", 305009) # Parsing firewall user from - grok: patterns: @@ -3506,7 +3508,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserFrom - where: log.messageId==305009 + where: equals("log.messageId", 305009) # Removing tmp fields - delete: fields: @@ -3519,20 +3521,20 @@ pipeline: fields: - log.firewallUserFrom - log.accessList - where: log.messageId==305009 + where: equals("log.messageId", 305009) - trim: function: suffix substring: '(' fields: - log.srcInterface - where: log.messageId==305009 + where: equals("log.messageId", 305009) - trim: function: suffix substring: ':' fields: - log.srcInterface - log.dstInterface - where: log.messageId==305009 + where: equals("log.messageId", 305009) #......................................................................# # ASA-6-305010 - grok: @@ -3556,7 +3558,7 @@ pipeline: - fieldName: log.duration pattern: '{{.time}}' source: log.msg - where: log.messageId==305010 + where: equals("log.messageId", 305010) # Parsing firewall user from - grok: patterns: @@ -3567,7 +3569,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserFrom - where: log.messageId==305010 + where: equals("log.messageId", 305010) # Removing tmp fields - delete: fields: @@ -3578,14 +3580,14 @@ pipeline: substring: ')' fields: - log.firewallUserFrom - where: log.messageId==305010 + where: equals("log.messageId", 305010) - trim: function: suffix substring: ':' fields: - log.srcInterface - log.dstInterface - where: log.messageId==305010 + where: equals("log.messageId", 305010) #......................................................................# # ASA-6-305011 - grok: @@ -3609,7 +3611,7 @@ pipeline: - fieldName: target.port pattern: '{{.integer}}' source: log.msg - where: log.messageId==305011 + where: equals("log.messageId", 305011) # Parsing firewall user from - grok: patterns: @@ -3620,30 +3622,30 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserFrom - where: log.messageId==305011 + where: equals("log.messageId", 305011) # Parsing protocol - grok: patterns: - - fieldName: action + - fieldName: log.irrelevant pattern: 'Built\s(dynamic|static)' - fieldName: protocol pattern: '{{.word}}' - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.msg - where: log.messageId==305011 + where: equals("log.messageId", 305011) - trim: function: suffix substring: ')' fields: - log.firewallUserFrom - where: log.messageId==305011 + where: equals("log.messageId", 305011) # Removing tmp fields - delete: fields: - log.tmpFwUserFrom - where: log.messageId==305011 + where: equals("log.messageId", 305011) # Cleaning fields - trim: @@ -3652,14 +3654,14 @@ pipeline: fields: - log.srcInterface - log.dstInterface - where: log.messageId==305011 + where: equals("log.messageId", 305011) - trim: function: suffix substring: '/' fields: - origin.ip - target.ip - where: log.messageId==305011 + where: equals("log.messageId", 305011) #......................................................................# # ASA-6-305012 - grok: @@ -3687,7 +3689,7 @@ pipeline: - fieldName: log.duration pattern: '{{.time}}' source: log.msg - where: log.messageId==305012 + where: equals("log.messageId", 305012) # Parsing firewall user from - grok: patterns: @@ -3698,18 +3700,18 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpFwUserFrom - where: log.messageId==305012 + where: equals("log.messageId", 305012) # Parsing protocol - grok: patterns: - - fieldName: action + - fieldName: log.irrelevant pattern: 'Teardown\s(dynamic|static)' - fieldName: protocol pattern: '{{.word}}' - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.msg - where: log.messageId==305012 + where: equals("log.messageId", 305012) # Parsing srcInterface # With ACL - grok: @@ -3721,14 +3723,14 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.tmpAclSrcInterface - where: log.messageId==305012 + where: equals("log.messageId", 305012) # Without ACL - grok: patterns: - fieldName: log.srcInterface pattern: '{{.data}}:' source: log.tmpAclSrcInterface - where: log.messageId==305012 + where: equals("log.messageId", 305012) # Parsing srcInterface from himself to avoid parsing issues - grok: patterns: @@ -3737,7 +3739,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.srcInterface - where: log.messageId==305012 + where: equals("log.messageId", 305012) - trim: function: suffix @@ -3745,13 +3747,13 @@ pipeline: fields: - log.firewallUserFrom - log.accessList - where: log.messageId==305012 + where: equals("log.messageId", 305012) # Removing tmp fields - delete: fields: - log.tmpFwUserFrom - log.tmpAclSrcInterface - where: log.messageId==305012 + where: equals("log.messageId", 305012) # Cleaning fields - trim: @@ -3760,20 +3762,20 @@ pipeline: fields: - log.srcInterface - log.dstInterface - where: log.messageId==305012 + where: equals("log.messageId", 305012) - trim: function: suffix substring: '(' fields: - log.srcInterface - where: log.messageId==305012 + where: equals("log.messageId", 305012) - trim: function: suffix substring: '/' fields: - origin.ip - target.ip - where: log.messageId==305012 + where: equals("log.messageId", 305012) #......................................................................# # ASA-3-322001 - grok: @@ -3789,7 +3791,7 @@ pipeline: - fieldName: log.srcInterface pattern: '{{.greedy}}' source: log.msg - where: log.messageId==322001 + where: equals("log.messageId", 322001) # Action result - grok: patterns: @@ -3798,20 +3800,20 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: log.messageId==322001 + where: equals("log.messageId", 322001) # Cleaning fields - trim: function: suffix substring: ',' fields: - log.localMac - where: log.messageId==322001 + where: equals("log.messageId", 322001) - trim: function: suffix substring: 'on' fields: - log.reason - where: log.messageId==322001 + where: equals("log.messageId", 322001) #......................................................................# # ASA-3-322002 - grok: @@ -3845,7 +3847,7 @@ pipeline: - fieldName: log.boudSrcMac pattern: '{{.greedy}}' source: log.msg - where: log.messageId==322002 + where: equals("log.messageId", 322002) # Protocol - grok: patterns: @@ -3854,26 +3856,26 @@ pipeline: - fieldName: log.irrelevant pattern: 'inspection check' source: action - where: log.messageId==322002 + where: equals("log.messageId", 322002) # Cleaning fields - trim: function: suffix substring: 'on' fields: - origin.mac - where: log.messageId==322002 + where: equals("log.messageId", 322002) - trim: function: suffix substring: 'for' fields: - log.fakeSrcMac - where: log.messageId==322002 + where: equals("log.messageId", 322002) - trim: function: suffix substring: '.' fields: - log.srcInterface - where: log.messageId==322002 + where: equals("log.messageId", 322002) #......................................................................# # ASA-3-322003 - grok: @@ -3905,7 +3907,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.msg - where: log.messageId==322003 + where: equals("log.messageId", 322003) # Protocol - grok: patterns: @@ -3914,26 +3916,26 @@ pipeline: - fieldName: log.irrelevant pattern: 'inspection check' source: action - where: log.messageId==322003 + where: equals("log.messageId", 322003) # Cleaning fields - trim: function: suffix substring: 'on' fields: - origin.mac - where: log.messageId==322003 + where: equals("log.messageId", 322003) - trim: function: suffix substring: 'for' fields: - log.fakeSrcMac - where: log.messageId==322003 + where: equals("log.messageId", 322003) - trim: function: suffix substring: '.' fields: - log.srcInterface - where: log.messageId==322003 + where: equals("log.messageId", 322003) #......................................................................# # ASA-4-400000 to 400050 - grok: @@ -3955,14 +3957,14 @@ pipeline: - fieldName: log.srcInterface pattern: '{{.greedy}}' source: log.msg - where: log.messageId>=400000 && log.messageId<=400050 + where: greaterOrEqual("log.messageId", 400000) && lessOrEqual("log.messageId", 400050) # Cleaning action before extract protocol - trim: function: suffix substring: 'from' fields: - action - where: log.messageId>=400000 && log.messageId<=400050 + where: greaterOrEqual("log.messageId", 400000) && lessOrEqual("log.messageId", 400050) - grok: patterns: - fieldName: protocol @@ -3970,7 +3972,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: (log.messageId>=400000 && log.messageId<=400022) || (log.messageId>=400026 && log.messageId<=400040) + where: (greaterOrEqual("log.messageId", 400000) && lessOrEqual("log.messageId", 400022)) || (greaterOrEqual("log.messageId", 400026) && lessOrEqual("log.messageId", 400040)) - grok: patterns: - fieldName: log.irrelevant @@ -3980,7 +3982,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: log.messageId==400023 || log.messageId==400024 || log.messageId==400041 + where: equals("log.messageId", 400023) || equals("log.messageId", 400024) || equals("log.messageId", 400041) #......................................................................# # ASA-4-402114 - grok: @@ -4008,7 +4010,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.msg - where: log.messageId==402114 + where: equals("log.messageId", 402114) # Protocol - grok: patterns: @@ -4019,27 +4021,27 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: log.messageId==402114 + where: equals("log.messageId", 402114) # Cleaning fields - trim: function: suffix substring: ',' fields: - log.spi - where: log.messageId==402114 + where: equals("log.messageId", 402114) - trim: function: suffix substring: ')' fields: - log.seqNum - where: log.messageId==402114 + where: equals("log.messageId", 402114) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==402114 + where: equals("log.messageId", 402114) #......................................................................# # ASA-4-402115 - grok: @@ -4067,14 +4069,14 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.msg - where: log.messageId==402115 + where: equals("log.messageId", 402115) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==402115 + where: equals("log.messageId", 402115) #......................................................................# # ASA-4-402116 - grok: @@ -4136,7 +4138,7 @@ pipeline: - fieldName: log.remoteProxyPort pattern: '{{.greedy}}' source: log.msg - where: log.messageId==402116 + where: equals("log.messageId", 402116) # Protocol - grok: patterns: @@ -4147,21 +4149,21 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: log.messageId==402116 + where: equals("log.messageId", 402116) # Cleaning fields - trim: function: suffix substring: ',' fields: - log.spi - where: log.messageId==402116 + where: equals("log.messageId", 402116) - trim: function: suffix substring: ')' fields: - log.seqNum - origin.user - where: log.messageId==402116 + where: equals("log.messageId", 402116) - trim: function: suffix substring: '/' @@ -4173,27 +4175,27 @@ pipeline: - log.remoteProxyIpMask - log.remoteProxyPort - log.remoteProxyProto - where: log.messageId==402116 + where: equals("log.messageId", 402116) - trim: function: suffix substring: ')' fields: - log.seqNum - origin.user - where: log.messageId==402116 + where: equals("log.messageId", 402116) - trim: function: suffix substring: 'and' fields: - log.localProxyPort - where: log.messageId==402116 + where: equals("log.messageId", 402116) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==402116 + where: equals("log.messageId", 402116) #......................................................................# # ASA-4-402117 - grok: @@ -4211,7 +4213,7 @@ pipeline: - fieldName: target.ip pattern: '{{.greedy}}' source: log.msg - where: log.messageId==402117 + where: equals("log.messageId", 402117) # Protocol - grok: patterns: @@ -4222,14 +4224,14 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: log.messageId==402117 + where: equals("log.messageId", 402117) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==402117 + where: equals("log.messageId", 402117) #......................................................................# # ASA-4-402118 - grok: @@ -4267,7 +4269,7 @@ pipeline: - fieldName: log.fragmentOffset pattern: '{{.integer}}' source: log.msg - where: log.messageId==402118 + where: equals("log.messageId", 402118) # Protocol - grok: patterns: @@ -4278,7 +4280,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: log.messageId==402118 + where: equals("log.messageId", 402118) # Cleaning fields - trim: function: suffix @@ -4286,20 +4288,20 @@ pipeline: fields: - log.seqNum - origin.user - where: log.messageId==402118 + where: equals("log.messageId", 402118) - trim: function: suffix substring: ',' fields: - log.spi - where: log.messageId==402118 + where: equals("log.messageId", 402118) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==402118 + where: equals("log.messageId", 402118) #......................................................................# # ASA-4-402119, 402120 - grok: @@ -4331,7 +4333,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.msg - where: log.messageId==402119 || log.messageId==402120 + where: equals("log.messageId", 402119) || equals("log.messageId", 402120) # Protocol - grok: patterns: @@ -4342,7 +4344,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: log.messageId==402119 || log.messageId==402120 + where: equals("log.messageId", 402119) || equals("log.messageId", 402120) # Cleaning fields - trim: function: suffix @@ -4350,20 +4352,20 @@ pipeline: fields: - log.seqNum - origin.user - where: log.messageId==402119 || log.messageId==402120 + where: equals("log.messageId", 402119) || equals("log.messageId", 402120) - trim: function: suffix substring: ',' fields: - log.spi - where: 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: log.messageId==402119 || log.messageId==402120 + where: equals("log.messageId", 402119) || equals("log.messageId", 402120) #......................................................................# # ASA-5-402128 - grok: @@ -4383,7 +4385,7 @@ pipeline: - fieldName: log.memoryLimit pattern: '{{.integer}}' source: log.msg - where: log.messageId==402128 + where: equals("log.messageId", 402128) #......................................................................# # ASA-4-405001 - grok: @@ -4407,14 +4409,14 @@ pipeline: - fieldName: target.mac pattern: '{{.greedy}}' source: log.msg - where: log.messageId==405001 + where: equals("log.messageId", 405001) # Adding protocol - add: function: 'string' params: key: protocol value: 'ARP' - where: log.messageId==405001 + where: equals("log.messageId", 405001) # Cleaning fields - trim: function: suffix @@ -4422,19 +4424,19 @@ pipeline: fields: - origin.ip - target.ip - where: log.messageId==405001 + where: equals("log.messageId", 405001) - trim: function: suffix substring: 'on' fields: - origin.mac - where: log.messageId==405001 + where: equals("log.messageId", 405001) - trim: function: suffix substring: 'with' fields: - log.srcInterface - where: log.messageId==405001 + where: equals("log.messageId", 405001) #......................................................................# # ASA-4-405002 - grok: @@ -4450,20 +4452,20 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.msg - where: log.messageId==405002 + where: equals("log.messageId", 405002) # Cleaning fields - trim: function: suffix substring: 'for' fields: - origin.mac - where: log.messageId==405002 + where: equals("log.messageId", 405002) - trim: function: suffix substring: '/' fields: - origin.ip - where: log.messageId==405002 + where: equals("log.messageId", 405002) #......................................................................# # ASA-4-406001 - grok: @@ -4483,27 +4485,27 @@ pipeline: - fieldName: log.srcInterface pattern: '{{.greedy}}' source: log.msg - where: log.messageId==406001 + where: equals("log.messageId", 406001) # Adding protocol - add: function: 'string' params: key: protocol value: 'FTP' - where: log.messageId==406001 + where: equals("log.messageId", 406001) # Cleaning fields - trim: function: suffix substring: ':' fields: - action - where: log.messageId==406001 + where: equals("log.messageId", 406001) - trim: function: suffix substring: '/' fields: - origin.ip - where: log.messageId==406001 + where: equals("log.messageId", 406001) #......................................................................# # ASA-4-406002 - grok: @@ -4527,21 +4529,21 @@ pipeline: - fieldName: log.srcInterface pattern: '{{.greedy}}' source: log.msg - where: log.messageId==406002 + where: equals("log.messageId", 406002) # Adding protocol - add: function: 'string' params: key: protocol value: 'FTP' - where: log.messageId==406002 + where: equals("log.messageId", 406002) # Cleaning fields - trim: function: suffix substring: ':' fields: - action - where: log.messageId==406002 + where: equals("log.messageId", 406002) #......................................................................# # ASA-4-407002 - grok: @@ -4571,7 +4573,7 @@ pipeline: - fieldName: log.srcInterface pattern: '{{.greedy}}' source: log.msg - where: log.messageId==407002 + where: equals("log.messageId", 407002) # Connection number and connection limit - grok: patterns: @@ -4586,7 +4588,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: log.messageId==407002 + where: equals("log.messageId", 407002) # Cleaning fields - trim: function: suffix @@ -4594,7 +4596,7 @@ pipeline: fields: - origin.ip - target.ip - where: log.messageId==407002 + where: equals("log.messageId", 407002) #......................................................................# # ASA-6-603109 - grok: @@ -4614,7 +4616,7 @@ pipeline: - fieldName: origin.ip pattern: '{{.greedy}}' source: log.msg - where: log.messageId==603109 + where: equals("log.messageId", 603109) # Protocol - grok: patterns: @@ -4625,7 +4627,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: log.messageId==603109 + where: equals("log.messageId", 603109) # Cleaning fields - trim: function: suffix @@ -4633,14 +4635,14 @@ pipeline: fields: - log.srcInterface - log.tunnelId - where: log.messageId==603109 + where: equals("log.messageId", 603109) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==603109 + where: equals("log.messageId", 603109) #......................................................................# # ASA-6-605004 - grok: @@ -4666,7 +4668,7 @@ pipeline: - fieldName: origin.user pattern: '{{.greedy}}' source: log.msg - where: log.messageId==605004 + where: equals("log.messageId", 605004) # Action result - grok: patterns: @@ -4675,13 +4677,13 @@ pipeline: - fieldName: log.ciscoResult pattern: '{{.word}}' source: action - where: log.messageId==605004 + where: equals("log.messageId", 605004) - add: function: 'string' params: key: actionResult value: 'denied' - where: log.messageId==605004 + where: equals("log.messageId", 605004) # Cleaning fields - trim: function: suffix @@ -4689,31 +4691,31 @@ pipeline: fields: - origin.ip - target.ip - where: log.messageId==605004 + where: equals("log.messageId", 605004) - trim: function: suffix substring: 'for' fields: - log.service - where: log.messageId==605004 + where: equals("log.messageId", 605004) - trim: function: suffix substring: ':' fields: - log.dstInterface - where: log.messageId==605004 + where: equals("log.messageId", 605004) - trim: function: prefix substring: '"' fields: - origin.user - where: log.messageId==605004 + where: equals("log.messageId", 605004) - trim: function: suffix substring: '"' fields: - origin.user - where: log.messageId==605004 + where: equals("log.messageId", 605004) #......................................................................# # ASA-6-617100 - grok: @@ -4725,7 +4727,7 @@ pipeline: - fieldName: origin.ip pattern: '{{.greedy}}' source: log.msg - where: log.messageId==617100 + where: equals("log.messageId", 617100) # Connection number - grok: patterns: @@ -4736,14 +4738,14 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.msg - where: log.messageId==617100 + where: equals("log.messageId", 617100) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==617100 + where: equals("log.messageId", 617100) #......................................................................# # ASA-2-106018 - grok: @@ -4771,28 +4773,28 @@ pipeline: - fieldName: target.ip pattern: '{{.greedy}}' source: log.msg - where: log.messageId==106018 + where: equals("log.messageId", 106018) # Adding protocol - add: function: 'string' params: key: protocol value: 'ICMP' - where: log.messageId==106018 + where: equals("log.messageId", 106018) # Cleaning fields - trim: function: suffix substring: 'src' fields: - log.accessList - where: log.messageId==106018 + where: equals("log.messageId", 106018) # Adding action result - add: function: 'string' params: key: actionResult value: 'denied' - where: log.messageId==106018 + where: equals("log.messageId", 106018) #......................................................................# # ASA-2-106020 - grok: @@ -4816,21 +4818,21 @@ pipeline: - fieldName: target.ip pattern: '{{.greedy}}' source: log.msg - where: log.messageId==106020 + where: equals("log.messageId", 106020) # Adding log.ciscoResult - add: function: 'string' params: key: log.ciscoResult value: 'Denied' - where: log.messageId==106020 + where: equals("log.messageId", 106020) # Adding action result - add: function: 'string' params: key: actionResult value: 'denied' - where: log.messageId==106020 + where: equals("log.messageId", 106020) #......................................................................# # ASA-6-611301 - grok: @@ -4844,14 +4846,14 @@ pipeline: - fieldName: log.mappedIp pattern: '{{.greedy}}' source: log.msg - where: log.messageId==611301 + where: equals("log.messageId", 611301) # Adding protocol - add: function: 'string' params: key: protocol value: 'NAT' - where: log.messageId==611301 + where: equals("log.messageId", 611301) #......................................................................# # ASA-6-611303 - grok: @@ -4875,14 +4877,14 @@ pipeline: - fieldName: log.netmaskTo pattern: '{{.greedy}}' source: log.msg - where: log.messageId==611303 + where: equals("log.messageId", 611303) # Adding protocol - add: function: 'string' params: key: protocol value: 'NAT' - where: log.messageId==611303 + where: equals("log.messageId", 611303) # Cleaning fields - trim: function: suffix @@ -4890,7 +4892,7 @@ pipeline: fields: - origin.ip - target.ip - where: log.messageId==611303 + where: equals("log.messageId", 611303) #......................................................................# # ASA-6-611304 - grok: @@ -4910,14 +4912,14 @@ pipeline: - fieldName: log.netmaskTo pattern: '{{.greedy}}' source: log.msg - where: log.messageId==611304 + where: equals("log.messageId", 611304) # Adding protocol - add: function: 'string' params: key: protocol value: 'NAT' - where: log.messageId==611304 + where: equals("log.messageId", 611304) # Cleaning fields - trim: function: suffix @@ -4925,7 +4927,7 @@ pipeline: fields: - origin.ip - target.ip - where: log.messageId==611304 + where: equals("log.messageId", 611304) #......................................................................# # ASA-6-611307 - grok: @@ -4935,21 +4937,21 @@ pipeline: - fieldName: target.ip pattern: '({{.ipv4}}|{{.ipv6}}|{{.hostname}})' source: log.msg - where: log.messageId==611307 + where: equals("log.messageId", 611307) # Adding action and log.ciscoResult - add: function: 'string' params: key: action value: 'The VPN client is connected to the specified headend' - where: log.messageId==611307 + where: equals("log.messageId", 611307) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==611307 + where: equals("log.messageId", 611307) #......................................................................# # ASA-6-611309 - grok: @@ -4963,14 +4965,14 @@ pipeline: - fieldName: origin.ip pattern: '({{.ipv4}}|{{.ipv6}}|{{.hostname}})' source: log.msg - where: log.messageId==611309 + where: equals("log.messageId", 611309) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==611309 + where: equals("log.messageId", 611309) #......................................................................# # ASA-6-611310,611311 - grok: @@ -4986,20 +4988,20 @@ pipeline: - fieldName: target.ip pattern: '({{.ipv4}}|{{.ipv6}}|{{.hostname}})' source: log.msg - where: log.messageId==611310 || log.messageId==611311 + where: equals("log.messageId", 611310) || equals("log.messageId", 611311) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==611310 + where: equals("log.messageId", 611310) - add: function: 'string' params: key: actionResult value: 'denied' - where: log.messageId==611311 + where: equals("log.messageId", 611311) #......................................................................# # ASA-6-611314 - grok: @@ -5013,21 +5015,21 @@ pipeline: - fieldName: target.ip pattern: '({{.ipv4}}|{{.ipv6}}|{{.hostname}})' source: log.msg - where: log.messageId==611314 + where: equals("log.messageId", 611314) # Adding action - add: function: 'string' params: key: action value: 'Load Balancing Cluster redirected the Secure Firewall' - where: log.messageId==611314 + where: equals("log.messageId", 611314) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==611314 + where: equals("log.messageId", 611314) #......................................................................# # ASA-6-611315 - grok: @@ -5037,21 +5039,21 @@ pipeline: - fieldName: origin.ip pattern: '({{.ipv4}}|{{.ipv6}}|{{.hostname}})' source: log.msg - where: log.messageId==611315 + where: equals("log.messageId", 611315) # Adding action - add: function: 'string' params: key: action value: 'Disconnecting from Load Balancing Cluster' - where: log.messageId==611315 + where: equals("log.messageId", 611315) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==611315 + where: equals("log.messageId", 611315) #......................................................................# # ASA-6-611318 - grok: @@ -5073,7 +5075,7 @@ pipeline: - fieldName: log.timeOut pattern: '{{.greedy}}' source: log.msg - where: log.messageId==611318 + where: equals("log.messageId", 611318) #......................................................................# # ASA-3-710003 - grok: @@ -5095,7 +5097,7 @@ pipeline: - fieldName: target.port pattern: '{{.greedy}}' source: log.msg - where: log.messageId==710003 + where: equals("log.messageId", 710003) # Protocol and log.ciscoResult - grok: patterns: @@ -5108,7 +5110,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.msg - where: log.messageId==710003 + where: equals("log.messageId", 710003) # Cleaning fields - trim: function: suffix @@ -5116,20 +5118,20 @@ pipeline: fields: - origin.ip - target.ip - where: log.messageId==710003 + where: equals("log.messageId", 710003) - trim: function: suffix substring: ':' fields: - log.dstInterface - where: log.messageId==710003 + where: equals("log.messageId", 710003) # Adding action result - add: function: 'string' params: key: actionResult value: 'denied' - where: log.messageId==710003 + where: equals("log.messageId", 710003) #......................................................................# # ASA-5-713252,713253 - grok: @@ -5151,7 +5153,7 @@ pipeline: - fieldName: action pattern: '{{.greedy}}' source: log.msg - where: log.messageId==713252 || log.messageId==713253 + where: equals("log.messageId", 713252) || equals("log.messageId", 713253) # Cleaning fields - trim: function: suffix @@ -5160,32 +5162,32 @@ pipeline: - origin.group - origin.user - origin.ip - where: log.messageId==713252 || log.messageId==713253 + where: equals("log.messageId", 713252) || equals("log.messageId", 713253) - trim: function: prefix substring: '''' fields: - origin.user - where: log.messageId==713252 || log.messageId==713253 + where: equals("log.messageId", 713252) || equals("log.messageId", 713253) - trim: function: suffix substring: '''' fields: - origin.user - where: log.messageId==713252 || log.messageId==713253 + where: equals("log.messageId", 713252) || equals("log.messageId", 713253) # Adding action result - add: function: 'string' params: key: actionResult value: 'denied' - where: log.messageId==713252 + where: equals("log.messageId", 713252) - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==713253 + where: equals("log.messageId", 713253) #......................................................................# # ASA-6-716001,716002,716003 - grok: @@ -5201,20 +5203,20 @@ pipeline: - fieldName: action pattern: '{{.greedy}}' source: log.msg - where: 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: 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: 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: '<' @@ -5222,7 +5224,7 @@ pipeline: - origin.group - origin.user - origin.ip - where: 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: '>' @@ -5230,26 +5232,26 @@ pipeline: - origin.group - origin.user - origin.ip - where: 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: 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: 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: log.messageId==716001 || log.messageId==716002 + where: equals("log.messageId", 716001) || equals("log.messageId", 716002) #......................................................................# # ASA-6-716004,716005,716006,716007,716009 - grok: @@ -5263,53 +5265,53 @@ pipeline: - fieldName: action pattern: '{{.greedy}}' source: log.msg - where: 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: 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: 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: 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: 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: 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: 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: key: actionResult value: 'accepted' - where: log.messageId==716006 + where: equals("log.messageId", 716006) #......................................................................# # ASA-6-716038 - grok: @@ -5327,27 +5329,27 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.msg - where: log.messageId==716038 + where: equals("log.messageId", 716038) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==716038 + where: equals("log.messageId", 716038) # Cleaning fields - trim: function: suffix substring: 'User' fields: - origin.group - where: log.messageId==716038 + where: equals("log.messageId", 716038) - trim: function: suffix substring: 'IP' fields: - origin.user - where: log.messageId==716038 + where: equals("log.messageId", 716038) - trim: function: prefix substring: '<' @@ -5355,7 +5357,7 @@ pipeline: - origin.group - origin.user - origin.ip - where: log.messageId==716038 + where: equals("log.messageId", 716038) - trim: function: suffix substring: '>' @@ -5363,25 +5365,25 @@ pipeline: - origin.group - origin.user - origin.ip - where: log.messageId==716038 + where: equals("log.messageId", 716038) - trim: function: prefix substring: '''' fields: - origin.user - where: log.messageId==716038 + where: equals("log.messageId", 716038) - trim: function: suffix substring: '''' fields: - origin.user - where: log.messageId==716038 + where: equals("log.messageId", 716038) - trim: function: suffix substring: ',' fields: - action - where: log.messageId==716038 + where: equals("log.messageId", 716038) #......................................................................# # ASA-6-716039 - grok: @@ -5399,14 +5401,14 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.msg - where: log.messageId==716039 + where: equals("log.messageId", 716039) # Adding action result - add: function: 'string' params: key: actionResult value: 'denied' - where: log.messageId==716039 + where: equals("log.messageId", 716039) # Cleaning fields - trim: function: suffix @@ -5414,13 +5416,13 @@ pipeline: fields: - action - origin.user - where: log.messageId==716039 + where: equals("log.messageId", 716039) - trim: function: suffix substring: 'user' fields: - origin.group - where: log.messageId==716039 + where: equals("log.messageId", 716039) #......................................................................# # ASA-6-719017,719019,719020,719021,719022,719023 - grok: @@ -5432,7 +5434,7 @@ pipeline: - fieldName: action pattern: '{{.greedy}}' source: log.msg - where: 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: @@ -5440,44 +5442,44 @@ pipeline: params: key: actionResult value: 'denied' - where: log.messageId==719019 || log.messageId==719023 + where: equals("log.messageId", 719019) || equals("log.messageId", 719023) - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==719020 || log.messageId==719022 + where: equals("log.messageId", 719020) || equals("log.messageId", 719022) - add: function: 'string' params: key: action value: 'WebVPN user is not checked against ACL' - where: log.messageId==719021 + where: equals("log.messageId", 719021) - add: function: 'string' params: key: action value: 'WebVPN user has been authenticated' - where: log.messageId==719022 + where: equals("log.messageId", 719022) - add: function: 'string' params: key: action value: 'WebVPN user has not been authenticated' - where: log.messageId==719023 + where: equals("log.messageId", 719023) # Cleaning fields - trim: function: prefix substring: '''' fields: - origin.user - where: 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: log.messageId==719017 || (log.messageId>=719019 && log.messageId<=719023) + where: equals("log.messageId", 719017) || (greaterOrEqual("log.messageId", 719019) && lessOrEqual("log.messageId", 719023)) #......................................................................# # ASA-6-719018 - grok: @@ -5493,20 +5495,20 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.msg - where: log.messageId==719018 + where: equals("log.messageId", 719018) # Cleaning fields - trim: function: suffix substring: '''' fields: - origin.user - where: log.messageId==719018 + where: equals("log.messageId", 719018) - trim: function: suffix substring: 'not' fields: - log.accessList - where: log.messageId==719018 + where: equals("log.messageId", 719018) #......................................................................# # ASA-6-719024 - grok: @@ -5526,27 +5528,27 @@ pipeline: - fieldName: origin.ip pattern: '{{.greedy}}' source: log.msg - where: log.messageId==719024 + where: equals("log.messageId", 719024) # Cleaning fields - trim: function: suffix substring: 'user' fields: - log.sessionPointer - where: log.messageId==719024 + where: equals("log.messageId", 719024) - trim: function: suffix substring: 'addr' fields: - origin.user - where: log.messageId==719024 + where: equals("log.messageId", 719024) # Adding action result - add: function: 'string' params: key: actionResult value: 'denied' - where: log.messageId==719024 + where: equals("log.messageId", 719024) #......................................................................# # ASA-7-609002 - grok: @@ -5564,21 +5566,21 @@ pipeline: - fieldName: log.duration pattern: '{{.greedy}}' source: log.msg - where: log.messageId==609002 + where: equals("log.messageId", 609002) # Cleaning fields - trim: function: suffix substring: '/' fields: - log.zoneName - where: log.messageId==609002 + where: equals("log.messageId", 609002) # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==609002 + where: equals("log.messageId", 609002) #......................................................................# # ASA-6-611305 - grok: @@ -5604,7 +5606,7 @@ pipeline: - fieldName: log.secondaryWins pattern: '({{.ipv4}}|{{.ipv6}}|{{.hostname}})' source: log.msg - where: log.messageId==611305 + where: equals("log.messageId", 611305) #......................................................................# # ASA-4-733100 - grok: @@ -5632,7 +5634,7 @@ pipeline: - fieldName: log.cumulativeTotalCount pattern: '{{.greedy}}' source: log.msg - where: log.messageId==733100 + where: equals("log.messageId", 733100) # Cleaning cumulative total count - grok: patterns: @@ -5641,7 +5643,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.cumulativeTotalCount - where: log.messageId==733100 + where: equals("log.messageId", 733100) # Parsing drop source object - grok: patterns: @@ -5650,26 +5652,26 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: log.messageId==733100 + where: equals("log.messageId", 733100) # Cleaning fields - trim: function: suffix substring: 'drop' fields: - log.dropSourceObject - where: log.messageId==733100 + where: equals("log.messageId", 733100) - trim: function: suffix substring: ']' fields: - log.dropSourceObject - where: log.messageId==733100 + where: equals("log.messageId", 733100) - trim: function: prefix substring: '[' fields: - log.dropSourceObject - where: log.messageId==733100 + where: equals("log.messageId", 733100) #......................................................................# # ASA-4-733101 - grok: @@ -5697,7 +5699,7 @@ pipeline: - fieldName: log.cumulativeTotalCount pattern: '{{.greedy}}' source: log.msg - where: log.messageId==733101 + where: equals("log.messageId", 733101) # Cleaning cumulative total count - grok: patterns: @@ -5706,7 +5708,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.cumulativeTotalCount - where: log.messageId==733101 + where: equals("log.messageId", 733101) # Parsing drop source object - grok: patterns: @@ -5717,25 +5719,25 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: action - where: log.messageId==733101 + where: equals("log.messageId", 733101) # Renaming from or to ip, according to -> if (targeted|attacking) - rename: from: - log.tmpIp to: origin.ip - where: log.messageId==733101 && contains("action", "attacking") + where: equals("log.messageId", 733101) && contains("action", "attacking") - rename: from: - log.tmpIp to: target.ip - where: log.messageId==733101 && contains("action", "targeted") + where: equals("log.messageId", 733101) && contains("action", "targeted") # Adding action result - add: function: 'string' params: key: actionResult value: 'accepted' - where: log.messageId==733101 + where: equals("log.messageId", 733101) #......................................................................# # ASA-4-733102, 733103 - grok: @@ -5747,7 +5749,7 @@ pipeline: - fieldName: log.irrelevant pattern: '{{.greedy}}' source: log.msg - where: log.messageId==733102 || log.messageId==733103 + where: equals("log.messageId", 733102) || equals("log.messageId", 733103) # Adding action - add: function: 'string' @@ -5774,7 +5776,7 @@ pipeline: params: key: severity value: 'medium' - where: log.severity=="4" + where: equals("log.severity", "4") - add: function: 'string' params: @@ -5800,105 +5802,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 From 8fbb1b077ceb54d363fed82c3887512fa4aed0a4 Mon Sep 17 00:00:00 2001 From: Ricardo Valdes Date: Wed, 23 Sep 2026 23:24:45 -0400 Subject: [PATCH 2/8] fix(cisco-asa): guard the IPS history lookup; VPN rule reads log.msg Names, thresholds, windows, impact, adversary and MITRE labels are unchanged. The botnet rule is unchanged. - ips_signature_matches (v1.0.1): require origin.ip before the rule can match. 108003 events carry no origin.ip, so the {{.origin.ip}} history placeholder failed on each one and the CEL plugin disabled the rule after five events with a 'Circuit Breaker' alert. Drop the log.action branch: no ASA step writes log.action. The log.message text branches are left as they are. - multiple_failed_vpn_attempts (v1.0.1): read log.msg, which the filter writes, instead of log.message, which nothing writes. This has no effect until the 113015 source address reaches origin.ip. Co-Authored-By: Claude Opus 5.5 --- rules/cisco/asa/ips_signature_matches.yml | 11 ++++++----- rules/cisco/asa/multiple_failed_vpn_attempts.yml | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/rules/cisco/asa/ips_signature_matches.yml b/rules/cisco/asa/ips_signature_matches.yml index e4ed5586a..2d48681b2 100644 --- a/rules/cisco/asa/ips_signature_matches.yml +++ b/rules/cisco/asa/ips_signature_matches.yml @@ -1,4 +1,4 @@ -# Rule version v1.0.0 +# Rule version v1.0.1 dataTypes: - firewall-cisco-asa @@ -25,10 +25,11 @@ description: | 6. Verify that IPS signatures are up-to-date 7. Document the incident and any actions taken where: | - equals("log.messageId", "108003") - || (contains("log.message", "malicious pattern") && contains("log.message", ["detected", "terminated", "blocked"])) - || (contains("log.message", "IPS") && contains("log.message", "signature") && contains("log.message", ["matched", "triggered", "detected"])) - || oneOf("log.action", ["ips_alert", "ids_alert", "threat_detected"]) + exists("origin.ip") && ( + equals("log.messageId", "108003") + || (contains("log.message", "malicious pattern") && contains("log.message", ["detected", "terminated", "blocked"])) + || (contains("log.message", "IPS") && contains("log.message", "signature") && contains("log.message", ["matched", "triggered", "detected"])) + ) afterEvents: - indexPattern: v11-log-firewall-cisco-asa-* with: diff --git a/rules/cisco/asa/multiple_failed_vpn_attempts.yml b/rules/cisco/asa/multiple_failed_vpn_attempts.yml index 0da2846bb..0d539157f 100644 --- a/rules/cisco/asa/multiple_failed_vpn_attempts.yml +++ b/rules/cisco/asa/multiple_failed_vpn_attempts.yml @@ -1,4 +1,4 @@ -# Rule version v1.0.0 +# Rule version v1.0.1 dataTypes: - firewall-cisco-asa @@ -30,7 +30,7 @@ where: | equals("log.messageId", "611102")) && exists("origin.ip") && (regexMatch("log.reason", "(?i)(invalid|failed|rejected|authentication)") || - regexMatch("log.message", "(?i)(authentication.*failed|invalid.*password)")) + regexMatch("log.msg", "(?i)(authentication.*failed|invalid.*password)")) afterEvents: - indexPattern: v11-log-firewall-cisco-asa-* with: From 8bf3a91240efcf24840ab78e9436b9d502a2f03d Mon Sep 17 00:00:00 2001 From: Ricardo Valdes Date: Wed, 23 Sep 2026 23:50:22 -0400 Subject: [PATCH 3/8] test(cisco-asa): add fabricated raw fixtures and regression checks cisco_asa_filter_test.go checks, with go-sdk v1.1.33, that no where clause compares log.* directly and that the 524 helper clauses keep the old results on 2,510 events with a log object and are false without one; that no geolocation destination lies under a field that holds a value; that a model of the engine's step plugins reproduces the playground result for every non-geolocation field of 41 fabricated lines, with a positive and a near-miss line for each step change; the reordered and guarded where clauses in filter order; the unchanged rule names, metadata, impact, grouping and history searches; 14 synthetic rule cases; and that a rule with a history search never matches an event that lacks its placeholder fields. All seven tests fail against the original filter and rules. testdata/cisco-asa/replay.py runs the same 41 lines through the public EventProcessor playground with the filter, the three rules, the shared grok definitions and invented geolocation data, and checks every field and alert. The rules' OpenSearch address is a closed local port, so a history search would fail and be reported. All inputs are invented, use RFC 5737 and RFC 3849 addresses and example names, and are not taken from Cisco's documentation, which could not be read. Co-Authored-By: Claude Opus 5.5 --- plugins/alerts/cisco_asa_filter_test.go | 974 ++++++++++++++ .../alerts/testdata/cisco-asa/expected.json | 1121 +++++++++++++++++ .../geolocation-data/asn-blocks-v4.csv | 3 + .../geolocation-data/asn-blocks-v6.csv | 2 + .../cisco-asa/geolocation-data/blocks-v4.csv | 3 + .../cisco-asa/geolocation-data/blocks-v6.csv | 2 + .../geolocation-data/locations-en.csv | 4 + .../alerts/testdata/cisco-asa/patterns.yaml | 16 + plugins/alerts/testdata/cisco-asa/raw.json | 46 + plugins/alerts/testdata/cisco-asa/replay.py | 206 +++ 10 files changed, 2377 insertions(+) create mode 100644 plugins/alerts/cisco_asa_filter_test.go create mode 100644 plugins/alerts/testdata/cisco-asa/expected.json create mode 100644 plugins/alerts/testdata/cisco-asa/geolocation-data/asn-blocks-v4.csv create mode 100644 plugins/alerts/testdata/cisco-asa/geolocation-data/asn-blocks-v6.csv create mode 100644 plugins/alerts/testdata/cisco-asa/geolocation-data/blocks-v4.csv create mode 100644 plugins/alerts/testdata/cisco-asa/geolocation-data/blocks-v6.csv create mode 100644 plugins/alerts/testdata/cisco-asa/geolocation-data/locations-en.csv create mode 100644 plugins/alerts/testdata/cisco-asa/patterns.yaml create mode 100644 plugins/alerts/testdata/cisco-asa/raw.json create mode 100644 plugins/alerts/testdata/cisco-asa/replay.py diff --git a/plugins/alerts/cisco_asa_filter_test.go b/plugins/alerts/cisco_asa_filter_test.go new file mode 100644 index 000000000..93738cbb9 --- /dev/null +++ b/plugins/alerts/cisco_asa_filter_test.go @@ -0,0 +1,974 @@ +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 ASA regression checks. Every raw input is FABRICATED (testdata/cisco-asa): Cisco's +// documentation site refused automated access and no Cisco ASA record was available, so the +// lines follow the filter's own patterns, use RFC 5737 and RFC 3849 documentation addresses +// and example names, and none is claimed to be a documented Cisco format. These tests use +// the pinned go-sdk v1.1.33 for YAML decoding, CEL and Event conversion. They do not run the +// EventProcessor: asaModel mirrors its step plugins, and testdata/cisco-asa/replay.py runs +// the same lines through the public playground. See filters/audits/cisco-asa.md. + +const ( + asaFilter = "../../filters/cisco/asa.yml" + asaRulesDir = "../../rules/cisco/asa" + asaData = "testdata/cisco-asa" + asaTenant = "00000000-0000-4000-8000-000000000001" + asaAbsent = "" +) + +var asaEnvelope = map[string]bool{"id": true, "timestamp": true, "deviceTime": true, "dataType": true, + "dataSource": true, "tenantId": true, "tenantName": true, "raw": true, "errors": true} + +func asaPipeline(t *testing.T) *plugins.Pipeline { + t.Helper() + encoded, err := utils.ReadPbYaml(asaFilter) + 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-asa" { + t.Fatalf("unexpected pipeline layout: %v", config.Pipeline) + } + return config.Pipeline[0] +} + +// asaWhere returns the where clause of whichever step kind is set. +func asaWhere(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, 519 clauses compared log.* directly (for example log.messageId==106001). +// These rewrite a helper clause back to that form, so both can be compared. +var ( + asaRawLog = regexp.MustCompile(`(^|[^"\w.])log\.[A-Za-z0-9_.]+\s*(==|!=|>=|<=|<|>)`) + asaHelperToRaw = []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", "4"\)`), `log.severity=="4"`}, + } + asaHelperCall = regexp.MustCompile(`(\w+)\("([A-Za-z0-9_.]+)"(?:, ("[^"]*"|\d+))?\)`) +) + +func asaRawForm(where string) string { + for _, r := range asaHelperToRaw { + where = r.helper.ReplaceAllString(where, r.raw) + } + return where +} + +// asaTruthTable 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. Severity text such as 04 or +4 is left +// out on purpose: equals compares it as the number 4, the old clause did not (see the audit). +func asaTruthTable(where string) []string { + ids := map[float64]bool{100000: true} + severities := []string{"6"} + others := map[string][]any{} + for _, m := range asaHelperCall.FindAllStringSubmatch(where, -1) { + field, arg := m[2], m[3] + switch field { + case "log.messageId": + n, _ := strconv.Atoi(arg) + 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-asa", "raw": "x", + "log": map[string]any{"messageId": id, "severity": sev}} + for f, v := range c { + if v != nil { + asaSet(doc, f, v) + } + } + b, _ := json.Marshal(doc) + docs = append(docs, string(b)) + } + } + } + return docs +} + +// No where clause compares log.* directly. Every clause that uses equals, greaterOrEqual or +// lessOrEqual on log.messageId, or equals on log.severity, compiles, has the same truth table +// as the direct comparison on events with a log object, and is false without an error when no +// header pattern produced a log object. +func TestCiscoASAWhereHelpers(t *testing.T) { + steps := asaPipeline(t).Steps + cache := plugins.NewCELCache("cisco-asa-where") + noLog := `{"id":"x","dataType":"firewall-cisco-asa","dataSource":"fixture-asa","tenantId":"` + asaTenant + `","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 + checked, rows := 0, 0 + for i, step := range steps { + where := asaWhere(step) + if where == "" { + continue + } + if asaRawLog.MatchString(where) { + raw = append(raw, where) + continue + } + pre := asaRawForm(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 asaTruthTable(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 checked < 520 { + t.Errorf("%d helper clauses read log.messageId or log.severity, want at least 520", checked) + } + t.Logf("%d helper clauses, %d truth-table rows", 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 TestCiscoASAGeolocationDestinations(t *testing.T) { + steps := asaPipeline(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-asa","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) + } +} + +// asaModel mirrors the ordered step execution of the public EventProcessor at commit +// 497bf53dbd1ae096f7b2dbc7bce77a6bf9f22ce1 (pkg/parsing/parsing.go and plugins/{grok,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, 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 asaModel struct { + steps []*plugins.Step + defs map[string]string + cache *plugins.CELCache + regex map[string]*regexp.Regexp +} + +func asaNewModel(t *testing.T) *asaModel { + t.Helper() + encoded, err := utils.ReadPbYaml(filepath.Join(asaData, "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 &asaModel{steps: asaPipeline(t).Steps, defs: file.Patterns, + cache: plugins.NewCELCache("cisco-asa-model"), regex: map[string]*regexp.Regexp{}} +} + +// compile expands {{.name}} like the SDK regexp cache and compiles the result. +func (m *asaModel) 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 asaGet(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 +} + +// asaSet follows sjson.Set for plain dotted paths: a missing or scalar parent becomes an object. +func asaSet(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 asaDelete(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]) +} + +// asaString follows gjson.Result.String for JSON-decoded values. +func asaString(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 *asaModel) run(t *testing.T, raw string) (map[string]any, []string) { + t.Helper() + doc := map[string]any{"id": "fixture", "dataType": "firewall-cisco-asa", "dataSource": "fixture-asa", + "@timestamp": "2026-09-23T14:00:00Z", "tenantId": asaTenant, "raw": raw} + var errs []string + for i, step := range m.steps { + if where := asaWhere(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.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" { + asaSet(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 := asaGet(doc, from); ok { + switch v.(type) { // utils.GetValueOf keeps scalars and turns JSON into text + case map[string]any, []any: + v = asaString(v) + case nil: + v = "" + } + asaSet(doc, to, v) + asaDelete(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 := asaGet(doc, f); ok { + asaSet(doc, f, float64(utils.CastInt64(v))) + } + } + case step.Delete != nil: + for _, f := range step.Delete.Fields { + asaDelete(doc, f) + } + case step.Dynamic != nil: + // geolocation: see TestCiscoASAGeolocationDestinations 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 *asaModel) grok(t *testing.T, doc map[string]any, g *plugins.Grok) error { + source := "raw" + if g.Source != "" { + source = g.Source + } + v, ok := asaGet(doc, source) + if !ok { + return nil + } + value := asaString(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 { + asaSet(doc, c.field, c.value) + } + } + return nil +} + +func (m *asaModel) 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 := asaGet(doc, f) + if !ok || asaString(v) == "" { + continue + } + s := strings.TrimSpace(asaString(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, "") + } + } + asaSet(doc, f, strings.TrimSpace(s)) + } + return nil +} + +// asaFinalize converts the draft to the SDK Event and back to JSON the way the playground's +// event writer stores it. +func asaFinalize(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 +} + +// asaFields flattens an event to dotted leaf paths, keeping empty objects as leaves. +func asaFields(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 == "" && asaEnvelope[k] { + continue + } + p := k + if prefix != "" { + p = prefix + "." + k + } + walk(x, p) + } + return + } + out[prefix] = v + } + walk(event, "") + return out +} + +var asaGeoPath = regexp.MustCompile(`^(origin\.geolocation|target\.geolocation|log\.[A-Za-z0-9]+Geolocation)(\.|$)`) + +type asaCase struct { + LogObject bool `json:"logObject"` + Fields map[string]any `json:"fields"` + Alerts []string `json:"alerts"` +} + +func asaFixtures(t *testing.T) (map[string]string, map[string]asaCase) { + t.Helper() + var raw struct { + Cases map[string]string `json:"cases"` + } + var expected struct { + Cases map[string]asaCase `json:"cases"` + } + for name, target := range map[string]any{"raw.json": &raw, "expected.json": &expected} { + data, err := os.ReadFile(filepath.Join(asaData, 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 +} + +// asaModelEvents runs every fabricated line through the model and finalizes it. +func asaModelEvents(t *testing.T) (map[string]*plugins.Event, map[string]map[string]any, map[string][]string) { + t.Helper() + model := asaNewModel(t) + raw, _ := asaFixtures(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] = asaFinalize(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 asaChangeCases = []struct { + change, fixture, path string + want any +}{ + {"F-C1", "unparsed-no-timestamp", "log", nil}, + {"F-C1", "unparsed-rfc5424", "log", nil}, + {"F-C1", "unparsed-linux-sshd", "log", nil}, + {"F-C1", "botnet-338001", "severity", "medium"}, + {"F-C3", "302013-outbound", "log.direction", "outbound"}, + {"F-C3", "header-bsd", "log.direction", "inbound"}, + {"F-C3 near miss", "302013-probe", "log.direction", "inbound"}, + {"F-C3 near miss", "302015-outbound", "log.direction", "outbound"}, + {"F-C4", "302304-teardown", "protocol", "TCP"}, + {"F-C4 near miss", "302303-built", "protocol", "TCP"}, + {"F-C5", "305011-built", "action", "Built dynamic TCP translation"}, + {"F-C5", "305011-built", "protocol", "TCP"}, + {"F-C5", "305012-teardown", "action", "Teardown dynamic TCP translation"}, + {"F-C5 near miss", "305012-one-digit-hour", "action", nil}, + {"F-C6", "302017-gre", "target.user", "erin"}, + {"F-C6", "302017-gre", "log.firewallUserTo", "dave"}, + {"F-C6", "302017-gre", "log.firewallUserFrom", "carol"}, + {"F-C6 near miss", "302018-gre", "origin.user", "erin"}, + {"F-C7", "106102-permitted", "actionResult", "accepted"}, + {"F-C7", "106102-permitted-arrow", "actionResult", "accepted"}, + {"F-C7", "106103-permitted", "actionResult", "accepted"}, + {"F-C7 near miss", "106102-denied", "actionResult", "denied"}, + {"F-C7 near miss", "106102-denied-arrow", "actionResult", "denied"}, + {"F-C8", "113009-with-equals", "origin.user", "alice"}, + {"F-C8", "113009-with-equals", "log.policy", "DfltGrpPolicy"}, + {"F-C8", "113011-with-equals", "origin.user", "alice"}, + {"F-C8", "113011-with-equals", "log.policy", "GP1"}, + {"F-C8 near miss", "113009-without-equals", "origin.user", "alice"}, +} + +// 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 TestCiscoASAExtractionModel(t *testing.T) { + _, stored, errs := asaModelEvents(t) + _, expected := asaFixtures(t) + for _, c := range asaChangeCases { + var got any = asaAbsent + if v, ok := asaGet(stored[c.fixture], c.path); ok { + got = v + } + want := c.want + if want == nil { + want = asaAbsent + } + 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 := asaFields(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 asaGeoPath.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 reordered and guarded steps, evaluated with the SDK in filter order. +func TestCiscoASAStepPredicates(t *testing.T) { + steps := asaPipeline(t).Steps + cache := plugins.NewCELCache("cisco-asa-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-C7: 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-C7: %d with %q ends as %q, want %q", c.id, c.captured, value, c.final) + } + } + // F-C8: 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(asaRawForm(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-C8: %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-C8: %d variants must run while no user is set", id) + } + if eval(writers[1], with) { + t.Errorf("F-C8: %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-C8: %d second variant matches another message", id) + } + } +} + +func asaLoadRules(t *testing.T) map[string]*plugins.Rule { + t.Helper() + files, err := filepath.Glob(filepath.Join(asaRulesDir, "*.y*ml")) + if err != nil || len(files) != 3 { + t.Fatalf("Cisco ASA 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 asaSearches(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(" + asaSearches(s.Or) + ")" + } + parts = append(parts, p) + } + return strings.Join(parts, " | ") +} + +func asaPlaceholders(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 + } + } + asaPlaceholders(s.Or, out) + } +} + +// Names, metadata, impact, grouping and history searches stay as they were; only the two +// conditions change. +func TestCiscoASARuleContract(t *testing.T) { + const index = "v11-log-firewall-cisco-asa-*" + vpnBranch := func(id string) string { + return index + "[origin.ip filter_term {{.origin.ip}}; log.messageId filter_term " + id + "] within 15m count 10" + } + want := map[string]string{ + "botnet_traffic_detection": "Botnet Command and Control Traffic Detected|Command and Control|T1071 - Application Layer Protocol|origin|3/2/1|adversary.ip,target.ip|" + + "https://www.cisco.com/c/en/us/td/docs/security/asa/special/botnet/asa-botnet.pdf,https://attack.mitre.org/techniques/T1071/|", + "ips_signature_matches": "IPS Signature Match - Malicious Pattern Detected|Initial Access|T1190 - Exploit Public-Facing Application|origin|3/3/2|adversary.ip,target.ip|" + + "https://www.cisco.com/c/en/us/td/docs/security/asa/syslog/b_syslog.html,https://attack.mitre.org/techniques/T1190/|" + + index + "[origin.ip filter_term {{.origin.ip}}] within 15m count 3", + "multiple_failed_vpn_attempts": "Multiple Failed VPN Authentication Attempts|Credential Access|T1110 - Brute Force|origin|3/2/1|adversary.ip,adversary.user|" + + "https://attack.mitre.org/techniques/T1110/,https://www.cisco.com/c/en/us/td/docs/security/asa/syslog/b_syslog/syslogs1.html|" + + vpnBranch("113015") + " or(" + vpnBranch("113021") + " | " + vpnBranch("109034") + " | " + vpnBranch("611102") + ")", + } + rules := asaLoadRules(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, ","), asaSearches(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-asa" || len(rule.DeduplicateBy) != 0 { + t.Errorf("%s: dataTypes %v deduplicateBy %v", stem, rule.DataTypes, rule.DeduplicateBy) + } + } + ips := strings.TrimSpace(rules["ips_signature_matches"].Where) + if !strings.HasPrefix(ips, `exists("origin.ip") && (`) || !strings.HasSuffix(ips, ")") || strings.Contains(ips, "log.action") { + t.Errorf("IPS condition must be exists(\"origin.ip\") && (...) without log.action: %s", ips) + } + vpn := rules["multiple_failed_vpn_attempts"].Where + if !strings.Contains(vpn, `regexMatch("log.msg", `) || strings.Contains(vpn, `"log.message"`) { + t.Errorf("VPN condition must read log.msg: %s", vpn) + } +} + +func asaEvent(t *testing.T, body string) *plugins.Event { + t.Helper() + input := `{"dataType":"firewall-cisco-asa","dataSource":"fixture-asa","tenantId":"` + asaTenant + `",` + body + `}` + event := new(plugins.Event) + if err := utils.StringToProtoMessage(&input, event); err != nil { + t.Fatalf("%s: %v", body, err) + } + return event +} + +var asaRuleCases = []struct { + rule, name, body string + want bool +}{ + {"ips_signature_matches", "108003 with a source address (a future mapping, D02)", `"log":{"messageId":108003,"msg":"Terminating ESMTP/SMTP connection; malicious pattern detected"},"origin":{"ip":"198.51.100.7"}`, true}, + {"ips_signature_matches", "108003 as a text id with a source address", `"log":{"messageId":"108003"},"origin":{"ip":"198.51.100.7"}`, true}, + {"ips_signature_matches", "108003 as this filter stores it, no source address", `"log":{"messageId":108003,"msg":"Terminating ESMTP/SMTP connection; malicious pattern detected"}`, false}, + {"ips_signature_matches", "log.action value that no step writes", `"log":{"messageId":420997,"action":"ips_alert"},"origin":{"ip":"198.51.100.7"}`, false}, + {"ips_signature_matches", "unrelated connection with an address", `"log":{"messageId":302013},"origin":{"ip":"198.51.100.7"}`, false}, + {"multiple_failed_vpn_attempts", "113015 with address and failing reason", `"log":{"messageId":113015,"reason":"Invalid password"},"origin":{"ip":"198.51.100.7","user":"alice"}`, true}, + {"multiple_failed_vpn_attempts", "109034 failure text in log.msg", `"log":{"messageId":109034,"msg":"Authentication failed for network user alice from 198.51.100.7/51234 to 192.0.2.10/443"},"origin":{"ip":"198.51.100.7"}`, true}, + {"multiple_failed_vpn_attempts", "611102 failure text in log.msg", `"log":{"messageId":611102,"msg":"User authentication failed: IP address: 198.51.100.7, Uname: alice"},"origin":{"ip":"198.51.100.7"}`, true}, + {"multiple_failed_vpn_attempts", "109034 failure text only in log.message", `"log":{"messageId":109034,"message":"Authentication failed for network user alice"},"origin":{"ip":"198.51.100.7"}`, false}, + {"multiple_failed_vpn_attempts", "113015 as this filter stores it, address in target.ip (D01)", `"log":{"messageId":113015,"reason":"Invalid password"},"target":{"ip":"198.51.100.7"}`, false}, + {"multiple_failed_vpn_attempts", "failure text on an unlisted message", `"log":{"messageId":113005,"msg":"authentication failed"},"origin":{"ip":"198.51.100.7"}`, false}, + {"multiple_failed_vpn_attempts", "109034 success text", `"log":{"messageId":109034,"msg":"Authentication succeeded for network user alice"},"origin":{"ip":"198.51.100.7"}`, false}, + {"botnet_traffic_detection", "338001 by message id", `"log":{"messageId":338001}`, true}, + {"botnet_traffic_detection", "unlisted 338003", `"log":{"messageId":338003}`, false}, +} + +// SDK v1.1.33 CEL on synthetic normalized events for the changed rule conditions. +func TestCiscoASARulePredicates(t *testing.T) { + rules := asaLoadRules(t) + cache := plugins.NewCELCache("cisco-asa-rules") + for _, c := range asaRuleCases { + t.Run(c.rule+"/"+c.name, func(t *testing.T) { + got, err := cache.Eval(rules[c.rule].Where, asaEvent(t, c.body)) + if err != nil { + t.Fatal(err) + } + if got != c.want { + t.Fatalf("match=%t want=%t", got, c.want) + } + }) + } +} + +// 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 TestCiscoASAHistoryPlaceholders(t *testing.T) { + rules := asaLoadRules(t) + cache := plugins.NewCELCache("cisco-asa-history") + events, _, _ := asaModelEvents(t) + for _, c := range asaRuleCases { + events["synthetic: "+c.name] = asaEvent(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{} + asaPlaceholders(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-asa/expected.json b/plugins/alerts/testdata/cisco-asa/expected.json new file mode 100644 index 000000000..975d3b493 --- /dev/null +++ b/plugins/alerts/testdata/cisco-asa/expected.json @@ -0,0 +1,1121 @@ +{ + "provenance": "Expected results for the FABRICATED lines in raw.json, recorded from the public EventProcessor playground (commit 497bf53dbd1ae096f7b2dbc7bce77a6bf9f22ce1) with this filter, the three rules, 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": "Sep 23 2026 10:00:00", + "log.dstInterface": "inside", + "log.hitCount": "1", + "log.localIp": "asa01.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-denied-arrow": { + "logObject": true, + "fields": { + "log.accessList": "outside_in", + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.dstInterface": "inside", + "log.hitCount": "1", + "log.localIp": "asa01.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": "Sep 23 2026 10:00:00", + "log.dstInterface": "inside", + "log.hitCount": "1", + "log.localIp": "asa01.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": [] + }, + "106102-permitted-arrow": { + "logObject": true, + "fields": { + "log.accessList": "outside_in", + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.dstInterface": "inside", + "log.hitCount": "1", + "log.localIp": "asa01.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": [] + }, + "106103-permitted": { + "logObject": true, + "fields": { + "log.accessList": "outside_in", + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.dstInterface": "inside", + "log.hitCount": "1", + "log.localIp": "asa01.example.com", + "log.messageId": 106103, + "log.msg": "access-list outside_in permitted udp for user alice outside/198.51.100.7 51234 inside/192.0.2.10 53 hit-cnt 1 first hit [0x1a2b, 0x0]", + "log.severity": "5", + "log.srcInterface": "outside", + "target.ip": "192.0.2.10", + "target.port": 53, + "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": "udp", + "actionResult": "accepted", + "severity": "low" + }, + "alerts": [] + }, + "113009-with-equals": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.localIp": "asa01.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": "Sep 23 2026 10:00:00", + "log.localIp": "asa01.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": "Sep 23 2026 10:00:00", + "log.localIp": "asa01.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-outbound": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.connectionId": "connection 7", + "log.direction": "outbound", + "log.dstInterface": "inside", + "log.localIp": "asa01.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-probe": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.connectionId": "connection 8", + "log.direction": "inbound", + "log.dstInterface": "inside", + "log.localIp": "asa01.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 Probe TCP connection 8 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": [] + }, + "302015-outbound": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.connectionId": "11", + "log.direction": "outbound", + "log.dstInterface": "inside", + "log.localIp": "asa01.example.com", + "log.mappedIpFrom": "198.51.100.53", + "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": "53", + "log.mappedPortTo": "51000", + "log.messageId": 302015, + "log.msg": "Built outbound UDP connection 11 for outside:198.51.100.53/53 (198.51.100.53/53) to inside:192.0.2.10/51000 (203.0.113.5/51000)", + "log.severity": "6", + "log.srcInterface": "outside", + "target.ip": "192.0.2.10", + "target.port": 51000, + "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.53", + "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": [] + }, + "302017-gre": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.connectionId": "12", + "log.direction": "inbound", + "log.dstInterface": "inside", + "log.firewallUserFrom": "carol", + "log.firewallUserTo": "dave", + "log.localIp": "asa01.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": [] + }, + "302018-gre": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.connectionId": "12", + "log.dstInterface": "inside", + "log.duration": "00:01:02", + "log.firewallUserFrom": "carol", + "log.firewallUserTo": "dave", + "log.localIp": "asa01.example.com", + "log.messageId": 302018, + "log.msg": "Teardown 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) duration 00:01:02 bytes 2048 (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.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": 2048, + "origin.ip": "198.51.100.7", + "origin.user": "erin", + "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", + "action": "Teardown GRE connection", + "severity": "low" + }, + "alerts": [] + }, + "302303-built": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.connectionId": "4", + "log.dstInterface": "inside", + "log.localIp": "asa01.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": "192.0.2.10", + "log.mappedIpToGeolocation.asn": 64501, + "log.mappedIpToGeolocation.aso": "FABRICATED-AS-64501", + "log.mappedIpToGeolocation.city": "Fabricated City B", + "log.mappedIpToGeolocation.country": "Fabricated Country B", + "log.mappedIpToGeolocation.countryCode": "XB", + "log.mappedIpToGeolocation.latitude": -30.125, + "log.mappedIpToGeolocation.longitude": 40.75, + "log.mappedPortFrom": "443", + "log.mappedPortTo": "51234", + "log.messageId": 302303, + "log.msg": "Built TCP state-bypass connection 4 from outside:198.51.100.7/443 (198.51.100.7/443) to inside:192.0.2.10/51234 (192.0.2.10/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", + "action": "Built TCP state-bypass connection", + "severity": "low" + }, + "alerts": [] + }, + "302304-teardown": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.connectionId": "5", + "log.dstInterface": "inside", + "log.duration": "00:00:05", + "log.localIp": "asa01.example.com", + "log.messageId": 302304, + "log.msg": "Teardown TCP state-bypass connection 5 from outside:198.51.100.7/443 to inside:192.0.2.10/51234 00:00:05, 1024, TCP FINs", + "log.reason": "TCP FINs", + "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.bytesSent": 1024, + "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", + "action": "Teardown TCP state-bypass connection", + "severity": "low" + }, + "alerts": [] + }, + "305011-built": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.dstInterface": "outside", + "log.localIp": "asa01.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-one-digit-hour": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.localIp": "asa01.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 0:00:30", + "log.severity": "6", + "protocol": "TCP", + "severity": "low" + }, + "alerts": [] + }, + "305012-teardown": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.dstInterface": "outside", + "log.duration": "00:00:30", + "log.localIp": "asa01.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:30", + "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": [] + }, + "botnet-338001": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.localIp": "asa01.example.com", + "log.messageId": 338001, + "log.msg": "Dynamic filter monitored blacklisted TCP traffic from outside:198.51.100.7/51234 (198.51.100.7/51234) to inside:192.0.2.10/443 (192.0.2.10/443), source malicious address resolved from local or dynamic list: bad.example.invalid, threat-level: very-high, category: botnet", + "log.severity": "4", + "severity": "medium" + }, + "alerts": [ + "botnet_traffic_detection" + ] + }, + "botnet-338002": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.localIp": "asa01.example.com", + "log.messageId": 338002, + "log.msg": "Dynamic filter monitored blacklisted TCP traffic from inside:192.0.2.10/51234 (192.0.2.10/51234) to outside:198.51.100.7/443 (198.51.100.7/443), destination malicious address resolved from local or dynamic list: bad.example.invalid, threat-level: very-high, category: botnet", + "log.severity": "4", + "severity": "medium" + }, + "alerts": [ + "botnet_traffic_detection" + ] + }, + "geo-113042-remote": { + "logObject": true, + "fields": { + "log.ciscoResult": "denied", + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.dstInterface": "outside", + "log.localIp": "asa01.example.com", + "log.messageId": 113042, + "log.msg": "CoA: Non-HTTP connection from inside:192.0.2.10/51234 to outside:203.0.113.80/25 for user alice at 198.51.100.7 denied due to URL filtering", + "log.remoteIp": "198.51.100.7", + "log.remoteIpGeolocation.asn": 64500, + "log.remoteIpGeolocation.aso": "FABRICATED-AS-64500", + "log.remoteIpGeolocation.city": "Fabricated City A", + "log.remoteIpGeolocation.country": "Fabricated Country A", + "log.remoteIpGeolocation.countryCode": "XA", + "log.remoteIpGeolocation.latitude": 10.5, + "log.remoteIpGeolocation.longitude": -20.25, + "log.remoteUser": "alice", + "log.severity": "4", + "log.srcInterface": "inside", + "target.ip": "203.0.113.80", + "target.port": 25, + "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", + "action": "CoA: Non-HTTP connection", + "severity": "medium" + }, + "alerts": [] + }, + "geo-302020-global": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.direction": "inbound", + "log.globalIp": "203.0.113.5", + "log.globalIpGeolocation": {}, + "log.globalPort": "0", + "log.icmpCode": "0", + "log.icmpType": "8", + "log.localIp": "asa01.example.com", + "log.messageId": 302020, + "log.msg": "Built inbound ICMP connection for faddr 198.51.100.7/0 gaddr 203.0.113.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": [] + }, + "header-bsd": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.connectionId": "connection 1", + "log.direction": "inbound", + "log.dstInterface": "inside", + "log.localIp": "asa01.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": [] + }, + "header-device-ipv4": { + "logObject": true, + "fields": { + "log.ciscoTime": "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": [] + }, + "header-device-ipv6": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.connectionId": "connection 1", + "log.direction": "inbound", + "log.dstInterface": "inside", + "log.localIp": "2001:db8::1", + "log.localIpGeolocation.asn": 64502, + "log.localIpGeolocation.aso": "FABRICATED-AS-64502", + "log.localIpGeolocation.city": "Fabricated City C", + "log.localIpGeolocation.country": "Fabricated Country C", + "log.localIpGeolocation.countryCode": "XC", + "log.localIpGeolocation.latitude": 1.5, + "log.localIpGeolocation.longitude": 2.5, + "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": [] + }, + "header-no-priority": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.connectionId": "connection 1", + "log.direction": "inbound", + "log.dstInterface": "inside", + "log.localIp": "asa01.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": [] + }, + "ips-108003-1": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.localIp": "asa01.example.com", + "log.messageId": 108003, + "log.msg": "Terminating ESMTP/SMTP connection; malicious pattern detected in the mail address from outside:198.51.100.7/25 to inside:192.0.2.10/51231. Data:bad-pattern", + "log.severity": "2", + "severity": "high" + }, + "alerts": [] + }, + "ips-108003-2": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.localIp": "asa01.example.com", + "log.messageId": 108003, + "log.msg": "Terminating ESMTP/SMTP connection; malicious pattern detected in the mail address from outside:198.51.100.7/25 to inside:192.0.2.10/51232. Data:bad-pattern", + "log.severity": "2", + "severity": "high" + }, + "alerts": [] + }, + "ips-108003-3": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.localIp": "asa01.example.com", + "log.messageId": 108003, + "log.msg": "Terminating ESMTP/SMTP connection; malicious pattern detected in the mail address from outside:198.51.100.7/25 to inside:192.0.2.10/51233. Data:bad-pattern", + "log.severity": "2", + "severity": "high" + }, + "alerts": [] + }, + "ips-108003-4": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.localIp": "asa01.example.com", + "log.messageId": 108003, + "log.msg": "Terminating ESMTP/SMTP connection; malicious pattern detected in the mail address from outside:198.51.100.7/25 to inside:192.0.2.10/51234. Data:bad-pattern", + "log.severity": "2", + "severity": "high" + }, + "alerts": [] + }, + "ips-108003-5": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.localIp": "asa01.example.com", + "log.messageId": 108003, + "log.msg": "Terminating ESMTP/SMTP connection; malicious pattern detected in the mail address from outside:198.51.100.7/25 to inside:192.0.2.10/51235. Data:bad-pattern", + "log.severity": "2", + "severity": "high" + }, + "alerts": [] + }, + "ips-108003-6": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.localIp": "asa01.example.com", + "log.messageId": 108003, + "log.msg": "Terminating ESMTP/SMTP connection; malicious pattern detected in the mail address from outside:198.51.100.7/25 to inside:192.0.2.10/51236. Data:bad-pattern", + "log.severity": "2", + "severity": "high" + }, + "alerts": [] + }, + "unparsed-ftd-prefix": { + "logObject": false, + "fields": {}, + "alerts": [] + }, + "unparsed-linux-sshd": { + "logObject": false, + "fields": {}, + "alerts": [] + }, + "unparsed-no-device-id": { + "logObject": false, + "fields": {}, + "alerts": [] + }, + "unparsed-no-timestamp": { + "logObject": false, + "fields": {}, + "alerts": [] + }, + "unparsed-rfc5424": { + "logObject": false, + "fields": {}, + "alerts": [] + }, + "vpn-109034-text": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.localIp": "asa01.example.com", + "log.messageId": 109034, + "log.msg": "Authentication failed for network user alice from 198.51.100.7/51234 to 192.0.2.10/443", + "log.severity": "3", + "severity": "high" + }, + "alerts": [] + }, + "vpn-113015-alice": { + "logObject": true, + "fields": { + "log.ciscoResult": "Rejected", + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.localIp": "asa01.example.com", + "log.messageId": 113015, + "log.msg": "AAA user authentication Rejected : reason = Invalid password : local database : user = alice : user IP = 198.51.100.7", + "log.reason": "Invalid password", + "log.severity": "6", + "target.ip": "198.51.100.7", + "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.user": "alice", + "actionResult": "denied", + "action": "AAA user authentication Rejected", + "severity": "low" + }, + "alerts": [] + }, + "vpn-113015-bob": { + "logObject": true, + "fields": { + "log.ciscoResult": "Rejected", + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.localIp": "asa01.example.com", + "log.messageId": 113015, + "log.msg": "AAA user authentication Rejected : reason = Invalid password : local database : user = bob : user IP = 198.51.100.8", + "log.reason": "Invalid password", + "log.severity": "6", + "target.ip": "198.51.100.8", + "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.user": "bob", + "actionResult": "denied", + "action": "AAA user authentication Rejected", + "severity": "low" + }, + "alerts": [] + }, + "vpn-611102-text": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.localIp": "asa01.example.com", + "log.messageId": 611102, + "log.msg": "User authentication failed: IP address: 198.51.100.7, Uname: alice", + "log.severity": "6", + "severity": "low" + }, + "alerts": [] + } + } +} diff --git a/plugins/alerts/testdata/cisco-asa/geolocation-data/asn-blocks-v4.csv b/plugins/alerts/testdata/cisco-asa/geolocation-data/asn-blocks-v4.csv new file mode 100644 index 000000000..8c9cc44d5 --- /dev/null +++ b/plugins/alerts/testdata/cisco-asa/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-asa/geolocation-data/asn-blocks-v6.csv b/plugins/alerts/testdata/cisco-asa/geolocation-data/asn-blocks-v6.csv new file mode 100644 index 000000000..785fac510 --- /dev/null +++ b/plugins/alerts/testdata/cisco-asa/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-asa/geolocation-data/blocks-v4.csv b/plugins/alerts/testdata/cisco-asa/geolocation-data/blocks-v4.csv new file mode 100644 index 000000000..f78499b6f --- /dev/null +++ b/plugins/alerts/testdata/cisco-asa/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-asa/geolocation-data/blocks-v6.csv b/plugins/alerts/testdata/cisco-asa/geolocation-data/blocks-v6.csv new file mode 100644 index 000000000..77cfed2c7 --- /dev/null +++ b/plugins/alerts/testdata/cisco-asa/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-asa/geolocation-data/locations-en.csv b/plugins/alerts/testdata/cisco-asa/geolocation-data/locations-en.csv new file mode 100644 index 000000000..590e302c2 --- /dev/null +++ b/plugins/alerts/testdata/cisco-asa/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-asa/patterns.yaml b/plugins/alerts/testdata/cisco-asa/patterns.yaml new file mode 100644 index 000000000..eb37af3be --- /dev/null +++ b/plugins/alerts/testdata/cisco-asa/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-asa/raw.json b/plugins/alerts/testdata/cisco-asa/raw.json new file mode 100644 index 000000000..f74c97b76 --- /dev/null +++ b/plugins/alerts/testdata/cisco-asa/raw.json @@ -0,0 +1,46 @@ +{ + "provenance": "FABRICATED. Cisco's syslog documentation could not be read (the site refused automated access) and no Cisco ASA record was available, so every line follows the patterns in filters/cisco/asa.yml and none is claimed to be a documented Cisco format. Addresses are RFC 5737 and RFC 3849 documentation addresses; device and user names are examples.", + "cases": { + "header-bsd": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-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)", + "header-no-priority": "Sep 23 2026 10:00:00 asa01.example.com : %ASA-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)", + "header-device-ipv4": "<166>Sep 23 2026 10:00:00 192.0.2.1 : %ASA-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)", + "header-device-ipv6": "<166>Sep 23 2026 10:00:00 2001:db8::1 : %ASA-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)", + "unparsed-no-timestamp": "<166>%ASA-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)", + "unparsed-rfc5424": "<166>1 2026-09-23T10:00:00Z asa01.example.com - - - - %ASA-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)", + "unparsed-ftd-prefix": "<166>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)", + "unparsed-no-device-id": "<166>Sep 23 2026 10:00:00: %ASA-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)", + "unparsed-linux-sshd": "<13>Sep 23 10:00:00 host01.example.com sshd[123]: Accepted password for alice from 198.51.100.7 port 22 ssh2", + "geo-302020-global": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-6-302020: Built inbound ICMP connection for faddr 198.51.100.7/0 gaddr 203.0.113.5/0 laddr 192.0.2.10/0 type 8 code 0", + "geo-113042-remote": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-4-113042: CoA: Non-HTTP connection from inside:192.0.2.10/51234 to outside:203.0.113.80/25 for user alice at 198.51.100.7 denied due to URL filtering", + "302013-outbound": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-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-probe": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-6-302013: Built inbound Probe TCP connection 8 for outside:198.51.100.7/51234 (198.51.100.7/51234) to inside:192.0.2.10/443 (203.0.113.5/443)", + "302015-outbound": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-6-302015: Built outbound UDP connection 11 for outside:198.51.100.53/53 (198.51.100.53/53) to inside:192.0.2.10/51000 (203.0.113.5/51000)", + "302304-teardown": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-6-302304: Teardown TCP state-bypass connection 5 from outside:198.51.100.7/443 to inside:192.0.2.10/51234 00:00:05, 1024, TCP FINs", + "302303-built": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-6-302303: Built TCP state-bypass connection 4 from outside:198.51.100.7/443 (198.51.100.7/443) to inside:192.0.2.10/51234 (192.0.2.10/51234)", + "305011-built": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-6-305011: Built dynamic TCP translation from inside:192.0.2.10/51234 to outside:203.0.113.5/40001", + "305012-teardown": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-6-305012: Teardown dynamic TCP translation from inside:192.0.2.10/51234 to outside:203.0.113.5/40001 duration 00:00:30", + "305012-one-digit-hour": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-6-305012: Teardown dynamic TCP translation from inside:192.0.2.10/51234 to outside:203.0.113.5/40001 duration 0:00:30", + "302017-gre": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-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)", + "302018-gre": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-6-302018: Teardown 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) duration 00:01:02 bytes 2048 (erin)", + "106102-permitted": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-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]", + "106102-permitted-arrow": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-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]", + "106103-permitted": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-5-106103: access-list outside_in permitted udp for user alice outside/198.51.100.7 51234 inside/192.0.2.10 53 hit-cnt 1 first hit [0x1a2b, 0x0]", + "106102-denied": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-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-denied-arrow": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-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]", + "113009-with-equals": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-6-113009: AAA retrieved default group policy (DfltGrpPolicy) for user = alice", + "113011-with-equals": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-6-113011: AAA retrieved user specific group policy (GP1) for user = alice", + "113009-without-equals": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-6-113009: AAA retrieved default group policy (DfltGrpPolicy) for user alice", + "botnet-338001": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-4-338001: Dynamic filter monitored blacklisted TCP traffic from outside:198.51.100.7/51234 (198.51.100.7/51234) to inside:192.0.2.10/443 (192.0.2.10/443), source malicious address resolved from local or dynamic list: bad.example.invalid, threat-level: very-high, category: botnet", + "botnet-338002": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-4-338002: Dynamic filter monitored blacklisted TCP traffic from inside:192.0.2.10/51234 (192.0.2.10/51234) to outside:198.51.100.7/443 (198.51.100.7/443), destination malicious address resolved from local or dynamic list: bad.example.invalid, threat-level: very-high, category: botnet", + "ips-108003-1": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-2-108003: Terminating ESMTP/SMTP connection; malicious pattern detected in the mail address from outside:198.51.100.7/25 to inside:192.0.2.10/51231. Data:bad-pattern", + "ips-108003-2": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-2-108003: Terminating ESMTP/SMTP connection; malicious pattern detected in the mail address from outside:198.51.100.7/25 to inside:192.0.2.10/51232. Data:bad-pattern", + "ips-108003-3": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-2-108003: Terminating ESMTP/SMTP connection; malicious pattern detected in the mail address from outside:198.51.100.7/25 to inside:192.0.2.10/51233. Data:bad-pattern", + "ips-108003-4": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-2-108003: Terminating ESMTP/SMTP connection; malicious pattern detected in the mail address from outside:198.51.100.7/25 to inside:192.0.2.10/51234. Data:bad-pattern", + "ips-108003-5": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-2-108003: Terminating ESMTP/SMTP connection; malicious pattern detected in the mail address from outside:198.51.100.7/25 to inside:192.0.2.10/51235. Data:bad-pattern", + "ips-108003-6": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-2-108003: Terminating ESMTP/SMTP connection; malicious pattern detected in the mail address from outside:198.51.100.7/25 to inside:192.0.2.10/51236. Data:bad-pattern", + "vpn-113015-alice": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-6-113015: AAA user authentication Rejected : reason = Invalid password : local database : user = alice : user IP = 198.51.100.7", + "vpn-113015-bob": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-6-113015: AAA user authentication Rejected : reason = Invalid password : local database : user = bob : user IP = 198.51.100.8", + "vpn-109034-text": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-3-109034: Authentication failed for network user alice from 198.51.100.7/51234 to 192.0.2.10/443", + "vpn-611102-text": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-6-611102: User authentication failed: IP address: 198.51.100.7, Uname: alice" + } +} diff --git a/plugins/alerts/testdata/cisco-asa/replay.py b/plugins/alerts/testdata/cisco-asa/replay.py new file mode 100644 index 000000000..6568f21bc --- /dev/null +++ b/plugins/alerts/testdata/cisco-asa/replay.py @@ -0,0 +1,206 @@ +"""Replay fabricated Cisco ASA 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 three Cisco ASA rules, runs the playground once, and checks every +event against expected.json and every local alert against its expected rule. Two of the +rules have history searches. Their OpenSearch address is a closed local port: no fixture +may reach a history search, and an attempt would fail and be reported. Every input is +fabricated. See filters/audits/cisco-asa.md. +""" +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", "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") +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", "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="asa-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; nothing listens here, so a history search would fail. + "org.opensearch": {"opensearch": f"http://127.0.0.1:{port}"}, + }, + } + (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/asa.yml" + shutil.copy2(filter_path, work / "pipeline/filters/asa.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/asa").glob("*.y*ml")) + require(len(rule_paths) == 3, f"Expected 3 Cisco ASA rules, found {len(rule_paths)}") + stems = {} + 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 + 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-asa-{name}" + inputs[event_id] = (name, cases[name]) + event = {"id": event_id, "dataType": "firewall-cisco-asa", "dataSource": "fixture-asa", + "@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", + "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}") + 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, no Circuit Breaker and no history search attempted") + + +if __name__ == "__main__": + main() From 46f9cb1cb561c4f8e19b06ab7ff757d86f352e7a Mon Sep 17 00:00:00 2001 From: Ricardo Valdes Date: Wed, 23 Sep 2026 23:51:29 -0400 Subject: [PATCH 4/8] docs(cisco-asa): add filter and rule audit Evidence basis (no Cisco ASA record on any searchable instance, Cisco's documentation unavailable), the basis and proof of each change, the playground, SDK and Go test results, the deferred items with what would unblock each, and the known limits. Co-Authored-By: Claude Opus 5.5 --- filters/audits/cisco-asa.md | 207 ++++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 filters/audits/cisco-asa.md diff --git a/filters/audits/cisco-asa.md b/filters/audits/cisco-asa.md new file mode 100644 index 000000000..46967c3ef --- /dev/null +++ b/filters/audits/cisco-asa.md @@ -0,0 +1,207 @@ +# Cisco ASA v11 filter and rule review + +The Cisco ASA filter (`filters/cisco/asa.yml`) had defects that its own patterns, the event +engine and the SDK make visible without any Cisco record. A line that no header pattern +accepted was stored with 519 CEL errors, geolocation replaced every non-private address in +16 `log.*` fields with an object, permitted hits of 106102/106103 were stored as denied, and +steps for 302013, 302304, 305011/305012, 302017 and 113009/113011 never matched or damaged +their own output. On the rule side, the +intrusion prevention rule disabled itself after five 108003 events, and the VPN rule read a +field nothing writes. This revision fixes those points and nothing else. The schema is +ThreatWinds go-sdk **v1.1.33**, as pinned by `plugins/alerts/go.mod`. + +## Evidence basis + +- **No Cisco ASA records.** None of the 29 v11 instances that could be searched holds a + `firewall-cisco-asa` record in any retained index; two instances could not be searched. A + full-text search of the v11 log indices for the words `asa` and `ftd` found no ASA syslog + under another data type either. Cisco Firepower Threat Defense records stored under + another data type use a different message catalog and were not used here. +- **Cisco documentation was not available.** Cisco's documentation site refused every + automated request (HTTP 403) from the allowed fetch tool, so no fixture is taken from + Cisco's documentation, and nothing that depends on Cisco's wording, message layouts, which + address is the source, or what a result means is changed. +- **What the corrections rest on.** Each change below names its basis: + - *the filter's own patterns*: what a step can receive follows from the earlier steps of + this filter; + - *the engine*: the public EventProcessor at commit + `497bf53dbd1ae096f7b2dbc7bce77a6bf9f22ce1`. Its + [parser](https://github.com/utmstack/EventProcessor/blob/497bf53dbd1ae096f7b2dbc7bce77a6bf9f22ce1/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/497bf53dbd1ae096f7b2dbc7bce77a6bf9f22ce1/plugins/grok/main.go) + writes nothing unless every pattern matched, and stops when the text runs out; its + [CEL plugin](https://github.com/utmstack/EventProcessor/blob/497bf53dbd1ae096f7b2dbc7bce77a6bf9f22ce1/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.33 + [`plugins/cel.go`](https://github.com/threatwinds/go-sdk/blob/v1.1.33/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`, `greaterOrEqual` and + `lessOrEqual` return false for a missing path; + [`plugins/rules.go`](https://github.com/threatwinds/go-sdk/blob/v1.1.33/plugins/rules.go) + returns an error when a `{{.field}}` history placeholder cannot be resolved. + +## Filter changes (version 3.1.0) + +| Change | Why | Basis | Proof | +|---|---|---|---| +| Rewrite the 519 `where` clauses that compared `log.messageId` or `log.severity` directly (for example `log.messageId==106001`) with `equals`, `greaterOrEqual` and `lessOrEqual`, and write the step added below the same way (520 clauses). | A line that no header pattern accepts has no `log` object, so every one of those clauses failed to compile, its step was skipped and an error was stored: 519 errors, about 145 KB, per event. | Engine and SDK | Playground: 12 such lines went from 519 errors each to none; every parsed line is unchanged. Go test: the 524 clauses that use these helpers on these fields (the 520 rewritten and 4 that already did) give the same result as the direct comparison on 2,510 events that have a `log` object, and false without an error when there is none. Earlier measurement: +0.3 ms per parsed event. | +| 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 for every non-private address, so one field was text in some events and an object in others. In a local OpenSearch 2.13.0 test with default dynamic mapping, the old output lost one of two documents in each of three scenarios; the new output stored 4 of 4 in both orders. | Engine (this repository's geolocation plugin) | Playground: 42 address fields replaced before, none after; the sibling keys hold the geolocation. Go test: no geolocation destination lies under a field that holds a value. | +| 106102/106103: run the `denied` add before the `accepted` add. The values are unchanged. | The second add's condition was also true on the `accepted` the first add had just written, so every permitted hit was stored as `denied`. | The filter's own conditions, evaluated with the SDK | Playground: 3 permitted lines change from `denied` to `accepted`; denied lines stay `denied`. | +| 302013: drop the direction grok's last pattern. | Its source holds only `Built ` (or `Built Probe`), so unless the message said `Probe` the last pattern met empty text and nothing was written. | Filter patterns and engine | `log.direction` is set on every 302013 line; the `Probe` form is unchanged. | +| 302304: the protocol grok reads `Teardown`. | The only step that produces `action` for 302304 starts it with `Teardown`; the grok expected `Built` and never matched. 302306 already does this. | Filter patterns | `protocol` is `TCP` on 302304 lines. | +| 305011/305012: the protocol grok writes its first match to `log.irrelevant` instead of `action`. | It overwrote the full phrase the main grok had captured (`Built dynamic TCP translation`) with its first words (`Built dynamic`). | Filter patterns | `action` keeps the full phrase. | +| 302017: drop the firewall-user grok's last pattern and remove the leading `(` from `target.user`. | The grok's last pattern always met empty text, so `log.firewallUserTo` was never set; the existing trims removed `)` and a trailing `(`, leaving `(name`. | Filter patterns and engine | `target.user` is `erin` and `log.firewallUserTo` is `dave` on the fabricated line. Which side that user belongs to is unchanged. | +| 113009/113011: run the second grok variant only when the first set no user (`&& !exists("origin.user")`). | Every text the first variant matches also matches the second, which then stored `origin.user` as `= alice` and put the parentheses back into `log.policy`. | Filter patterns | `origin.user` is `alice` and `log.policy` is `DfltGrpPolicy`; the form without `=` still works. | + +The `actionResult` values are unchanged: a separate correction owns them (see Deferred). +`filters/cisco/firepower.yml` has the same 16 geolocation steps and belongs to its own review. + +## Rule changes + +Names, thresholds, windows, impact, adversary side, grouping and MITRE labels are unchanged. +`botnet_traffic_detection` is unchanged. + +| Rule | Change | Why | Basis | +|---|---|---|---| +| `ips_signature_matches` (v1.0.1) | Wrap the condition in `exists("origin.ip") && (...)`; remove the `log.action` branch. The `log.message` text branches stay as they are. | The only branch that can match today is message 108003, which this filter does not parse, so the event has no `origin.ip`. The history placeholder `{{.origin.ip}}` then failed on every such event, and after five the rule was disabled with a Circuit Breaker alert. No step writes `log.action`. | SDK, engine and filter | +| `multiple_failed_vpn_attempts` (v1.0.1) | Read `log.msg` instead of `log.message`. | The header patterns write the message body to `log.msg`; nothing writes `log.message`. This has no effect until 113015 puts its source address in `origin.ip` (Deferred D01). | Filter | + +## Validation + +**Fabricated regression, committed.** `plugins/alerts/testdata/cisco-asa/` holds 41 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`. The lines follow the filter's own patterns; they are not claimed to be Cisco's +format. Addresses are RFC 5737 and RFC 3849 documentation addresses, and device and user +names are examples. The geolocation data covers two IPv4 documentation ranges and the IPv6 +documentation range with documentation AS numbers and invented places; the third IPv4 range +is left out on purpose. + +**Playground.** A clean build of the EventProcessor commit above ran the original and the +corrected filter on the same 80 fabricated lines, a larger private set from which the +committed lines were derived. +Its parser and writer plugins link go-sdk v1.1.26 and its CEL plugin v1.1.34; the +geolocation plugin was built from this repository. + +| Run | Result | +|---|---| +| Original filter, 80 lines | 80 events. The 12 lines no header pattern accepts carry 519 errors each (6,228 in all); 42 address fields hold an object instead of the address. | +| Corrected filter, 80 lines | 80 events, no errors. 84 of 84 planned field assertions pass for both runs; 36 events are identical and every other difference is one of the changes above. | +| Corrected filter and all three rules, 12 lines (338001, 338002, six 108003, two 113015, one 302013, one non-ASA line) | 12 events without errors. Two botnet alerts, on 338001 and 338002, without addresses; no intrusion prevention, VPN or Circuit Breaker alert; no compile, rule or history search error. No OpenSearch was running, and no line reached a history search. | +| Committed `replay.py`, 41 lines | 41 events without errors, every stored field as recorded in `expected.json`. Two alerts, both from the botnet rule, on 338001 and 338002; no Circuit Breaker alert and no history search attempted. | + +With the original filter and rules and the same 12 lines, an earlier run of the same build had +produced 6 failed history searches and one Circuit Breaker alert for the intrusion +prevention rule. + +**History searches.** The history blocks of both edited rules are unchanged. Their queries +were exercised only in an earlier local test with go-sdk v1.1.33 against a disposable +OpenSearch 2.13.0 (36 of 36 checks: thresholds, windows, other addresses, the OR branches and +the 108003 case without an address). No production cluster was queried for that. + +**SDK predicate checks.** The go-sdk v1.1.33 rule replay evaluated the original and edited +conditions of both rules over the playground events above and 18 fabricated normalized +events. The edited intrusion prevention condition matches only the normalized 108003 event +that has `origin.ip`, so its history placeholder always resolves; the original condition also +matched all 13 108003 events from the playground runs, none of which has an address, so each +would have failed its history search. The edited VPN condition matches the three normalized +failures that have an address; on the playground events neither version matches. 90 of 90 +and 65 of 65 checks passed. + +**Go tests.** `cisco_asa_filter_test.go` has seven tests. They check that no `where` clause +compares `log.*` directly and that each helper clause keeps its old 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.33, gives the playground's +result for every stored field of the 41 lines except the geolocation ones, including a +positive and a near-miss line for each step change; the reordered and guarded `where` clauses +in filter order; the rules' names, metadata, impact, grouping and history searches; 14 +synthetic rule cases; and that whenever a rule with a history search matches any event, its +placeholders resolve. All seven fail against the original filter and rules and pass against +this revision. 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. + +## Deferred + +Each of these needs Cisco's documentation or real records, or an owner decision, and is +unchanged here. + +| Id | What | What would unblock it | +|---|---|---| +| D01 | 113015/113017: the `user IP` address goes to `target.ip`; `origin.ip` is never set (113005 and 113016 put the same token in `origin.ip`). Also the quotes around user names and the reason words in the VPN rule. This revives the VPN rule. | Cisco's explanation of 113015 and 113017, or one real 113015 record from a device whose login source is known. Ship this first. | +| D02 | Parse 108003, 338001, 338002, 113021, 109034 and 611102, which the rules name but the filter does not parse. | The layout and field meaning of each of the six messages (for 338001/338002, which address is the listed one), plus at least one real record of each. | +| D03 | The botnet and intrusion prevention text branches (`log.message`), their case and the `IPS` inside `IPSEC`. They cannot match and cost nothing until changed. | Cisco's wording for the Dynamic Filter and IPS messages, or real records with those phrases. | +| D04 | The intrusion prevention history counts any event from the address, not repeated matches. | An owner decision; it has no effect until D02. | +| D05 | Header forms the filter rejects: no timestamp, no device-id, no year, RFC 3339/5424 time, a space-padded day, `%FTD-`, an empty body. They are no longer stored with 519 errors. | Cisco's documentation of the timestamp, device-id and RFC 5424 logging options and their defaults, or real records. | +| D06 | Format variants that make a whole message pattern fail: one-digit or over-24-hour durations, 302014 without a reason, 106001 with several TCP flags, hexadecimal sequence numbers in 402114-402120, an IPv6 AAA server in 113004/113005/113016, the 302003/302004 port form, the `(user= name)` label in 402116/402118/402119, the 302305 trailing user, 113009/113011 without `=`. | Cisco's message formats or real records of each. | +| D07 | Which address is `origin` and which is `target` (connection messages, 109101-109103, 611307-611315, the 305010-305012 mapped address, trailing users) and identity values left under `log`. All three rules report the `origin` side as the adversary. | Cisco's field definitions, or real records from a device with a known layout. | +| D08 | `actionResult` values: 109102/109103 store `accepted` although the filter's own text says they failed; `accepted` on events that are not successes; `failure` is never written. Owned by the separate action-result correction, which must not map 109102/109103 to success. | Cisco's meaning of each message's outcome. | +| D09 | Byte counters: the teardown byte count in `origin.bytesSent`, 113019 `Bytes xmt`/`Bytes rcv`, fragment sizes of 106020 and 402118. | Cisco's definition of each counter. | +| D10 | `deviceTime` from the header time (`log.ciscoTime`). | The time zone of the header timestamp, from Cisco's documentation or a device with a known zone. | +| D11 | Standard values: `severity` high/medium/low and no case for level 0, `protocol` `NAT` for 611301/611303/611304 and mixed protocol case, the `action` wording. | A platform decision with a review of dashboards, saved searches and rules, and Cisco's level definitions. | +| D12 | Redundant or overlapping steps, the reasons and 113034-113039 text deleted with `log.rest`, the parentheses kept by the second 113009/113011 variant. | Nothing external; can ship later with fabricated lines (naming an action for 113034-113039 needs Cisco's meaning). | +| D13 | New rules for parsed attack-type messages that no rule reads (733100-733103, 400000-400050, 106017, 106018, 106020, 106021, 201003, 407002, 209003, 405001, 405002, 322001-322003, 406001, 406002, 605004, 710003, 113005/113016/113017, 316001, 719024). | Cisco's message explanations, and real records to size the noise and check attribution. | +| D14 | Address-only patterns for `origin.ip`/`target.ip`: 104 patterns also accept a host name and 19 accept any text. | Cisco's documentation of name substitution in syslog, or real records. | + +MITRE ATT&CK v19.2 still lists T1071, T1190 and T1110 with the tactics the three rules use, +so no label changes. + +## Known limits + +- Every input is fabricated. No real Cisco ASA record and no Cisco documentation were + available, so the fixtures prove the filter's behaviour on lines shaped by its own + patterns, not what Cisco devices send. +- The playground's parser and writer plugins link go-sdk v1.1.26 and its CEL plugin v1.1.34; + the alerts module pins v1.1.33. The CEL helper source (`plugins/cel.go`, + `plugins/cel_overloads.go`) is identical in v1.1.26 and v1.1.33, and the predicates were + also checked with v1.1.33. Neither build is asserted to match a customer deployment. +- The playground's alert writer only records alerts. Indexing, grouping, deduplication, + notifications and production alerts were not tested. +- The index rejection behind the geolocation change was measured on a local OpenSearch 2.13.0 + with default dynamic mapping. Production clusters report 7.10.2 compatibility and were only + read, never written. +- The playground run has no positive control for the intrusion prevention and VPN rules, + because this filter cannot give their messages an `origin.ip` yet (D01, D02). Their positive + cases are the SDK predicate checks and the earlier local history test. +- 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 41 lines, but `replay.py` is the check + that runs the engine. +- `equals("log.severity", "4")` compares numbers, like the neighbouring `oneOf` severity + clauses, so severity text such as `04` or `+4` now counts as 4; the old clause accepted only + `4`. No fixture uses such text. +- When a 305012 duration has a one-digit hour (D06), `action` is now absent instead of the + partial `Teardown dynamic`. +- Lines with a rejected header form stay unparsed (D05); they only lose the 519 errors. +- The 16 `log.*` geolocation fields change name. No rule or dashboard reads them, and none of + the searched production indices held the old nested fields. +- 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 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-asa/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 minutes. The Go suite alone does not run the +engine. From f3c858de6c30fc8569ef97c235cd561b7d2414c9 Mon Sep 17 00:00:00 2001 From: Ricardo Valdes Date: Thu, 24 Sep 2026 15:59:41 -0400 Subject: [PATCH 5/8] docs(cisco-asa): record re-validation on the latest v11, go-sdk and engine Official v11 d2479c1a (go-sdk v1.1.36 in plugins/alerts) and EventProcessor main 8a3ade7 (every playground plugin on v1.1.36). Field names now keep underscores and regexMatch matches strings only; neither changes this draft, because none of the 129 names the filter writes contains an underscore and every text-search call reads a string field. Full plugins/alerts suite: 50 pass, 11 skip, 0 fail. replay.py: 41 events, 2 botnet alerts, no Circuit Breaker, no history search. The 80 private lines give the same per-line results as the original review; the rule run passes 24 of 24 checks; the original rules still trip the Circuit Breaker. v1.1.36 predicate checks: 90 of 90 and 65 of 65. Co-Authored-By: Claude Opus 5.5 --- filters/audits/cisco-asa.md | 64 +++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/filters/audits/cisco-asa.md b/filters/audits/cisco-asa.md index 46967c3ef..11e959810 100644 --- a/filters/audits/cisco-asa.md +++ b/filters/audits/cisco-asa.md @@ -8,7 +8,10 @@ steps for 302013, 302304, 305011/305012, 302017 and 113009/113011 never matched their own output. On the rule side, the intrusion prevention rule disabled itself after five 108003 events, and the VPN rule read a field nothing writes. This revision fixes those points and nothing else. The schema is -ThreatWinds go-sdk **v1.1.33**, as pinned by `plugins/alerts/go.mod`. +ThreatWinds go-sdk **v1.1.36**, as pinned by `plugins/alerts/go.mod` since official `v11` +(`d2479c1a`) was merged into this branch. The review itself used v1.1.33, whose +`plugins.proto`, `plugins/cel.go` and `plugins/rules.go` are identical. The draft was checked +again on the latest versions; see [Re-validation on the latest versions](#re-validation-on-the-latest-versions). ## Evidence basis @@ -32,10 +35,13 @@ ThreatWinds go-sdk **v1.1.33**, as pinned by `plugins/alerts/go.mod`. writes nothing unless every pattern matched, and stops when the text runs out; its [CEL plugin](https://github.com/utmstack/EventProcessor/blob/497bf53dbd1ae096f7b2dbc7bce77a6bf9f22ce1/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` + `Circuit Breaker: ` alert. The latest EventProcessor, `main` at + `8a3ade72bd9d12db21f6b273200588fb49540f14`, changes only these plugins' go-sdk version + (to v1.1.36) and how the CEL plugin reads its OpenSearch address, so this behaviour is the + same there. 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.33 + - *the SDK*: go-sdk v1.1.33 (both files below are identical in v1.1.36) [`plugins/cel.go`](https://github.com/threatwinds/go-sdk/blob/v1.1.33/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`, `greaterOrEqual` and @@ -69,8 +75,48 @@ Names, thresholds, windows, impact, adversary side, grouping and MITRE labels ar | `ips_signature_matches` (v1.0.1) | Wrap the condition in `exists("origin.ip") && (...)`; remove the `log.action` branch. The `log.message` text branches stay as they are. | The only branch that can match today is message 108003, which this filter does not parse, so the event has no `origin.ip`. The history placeholder `{{.origin.ip}}` then failed on every such event, and after five the rule was disabled with a Circuit Breaker alert. No step writes `log.action`. | SDK, engine and filter | | `multiple_failed_vpn_attempts` (v1.0.1) | Read `log.msg` instead of `log.message`. | The header patterns write the message body to `log.msg`; nothing writes `log.message`. This has no effect until 113015 puts its source address in `origin.ip` (Deferred D01). | Filter | +## Re-validation on the latest versions + +On 2026-09-24 official `v11` moved to `d2479c1a3705eec6a00016689c2bf5fbcc1814f2`, whose +`plugins/alerts` pins go-sdk v1.1.36, and EventProcessor `main` moved to +`8a3ade72bd9d12db21f6b273200588fb49540f14`, whose playground and parser, writer and CEL +plugins all link go-sdk v1.1.36. `v11` was merged into this branch. No file overlaps this +draft, so nothing conflicted. The geolocation plugin was built from `v11` `d2479c1a`, which +this branch now carries unchanged; its own `go.mod` pins go-sdk v1.1.34. + +What changed in the SDK, and what it means here: + +- Since v1.1.35, `utils.SanitizeField` keeps `_` in the field names that the `json` + (top-level keys), `kv`, `grok`, `csv`, `xml`, `add` and `rename` plugins write. Other + characters are still removed. This filter has no `json`, `kv` or `csv` step, and none of + the 129 names it writes contains `_` or another removed character. So every stored name + stays the same; the per-line comparison below confirms it. +- v1.1.36 makes `regexMatch` match string values only again. Since v1.1.34, `contains`, + `containsAll`, `startsWith` and `endsWith` also search the JSON text of objects and lists. + Every such call in this filter and its rules reads a text field (`log.message`, `log.msg`, + `log.reason`, `action`), so no result changes. `plugins.proto`, `plugins/cel.go` and + `plugins/rules.go` are identical in v1.1.33 and v1.1.36. +- No filter, rule or fixture needed a change. + +| Check on the latest versions | Result | +|---|---| +| Full `plugins/alerts` suite, go-sdk v1.1.36 | 50 tests pass, 11 skip, none fail (2,266 passing results with subtests). The seven Cisco ASA tests pass; the Go model of the step plugins uses the SDK's own `SanitizeField`, so it follows v1.1.36. The skipped tests need other technologies' private evidence and skip on the base commit too. | +| `replay.py` on EventProcessor 8a3ade7 | 41 events without errors, every stored field as in `expected.json`. Two alerts, both from the botnet rule, on 338001 and 338002; no Circuit Breaker alert and no history search attempted. | +| The 80 private fabricated lines, original and corrected filter | Original: 80 events, 6,228 errors (519 on each of 12 lines), 42 address fields replaced by an object. Corrected: 80 events, no errors, none replaced. 84 of 84 planned field assertions pass for both. Every line's stored fields, types and error counts equal the original review's results, for both filters. | +| Corrected filter and all three rules, the same 12 lines | 12 events without errors; two botnet alerts, on 338001 and 338002; no intrusion prevention, VPN or Circuit Breaker alert; no compile, rule or history search error. 24 of 24 checks pass. | +| Original filter and rules, the same 12 lines | Six failed history searches (`expression value cannot be nil after placeholder resolution`) and one `Circuit Breaker` alert for the intrusion prevention rule, as in the original review. | +| go-sdk v1.1.36 rule replay | The same predicate checks as the original review, now with the latest playground output: 90 of 90 and 65 of 65 pass. | + +At 8a3ade7 the CEL plugin reads its OpenSearch address from separate `host`, `port`, `user` +and `password` settings. `replay.py` still gives one URL, so the client gets an empty host +and connects to port 443 on the test computer, where nothing listened. Any history search +therefore still fails and is reported; none was attempted with the corrected rules. + ## Validation +These are the original review's results, on EventProcessor `497bf53` and go-sdk v1.1.33. +The section above repeats them on the latest versions. + **Fabricated regression, committed.** `plugins/alerts/testdata/cisco-asa/` holds 41 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 @@ -154,10 +200,10 @@ so no label changes. - Every input is fabricated. No real Cisco ASA record and no Cisco documentation were available, so the fixtures prove the filter's behaviour on lines shaped by its own patterns, not what Cisco devices send. -- The playground's parser and writer plugins link go-sdk v1.1.26 and its CEL plugin v1.1.34; - the alerts module pins v1.1.33. The CEL helper source (`plugins/cel.go`, - `plugins/cel_overloads.go`) is identical in v1.1.26 and v1.1.33, and the predicates were - also checked with v1.1.33. Neither build is asserted to match a customer deployment. +- The latest check used EventProcessor `8a3ade7`, whose playground and plugins link go-sdk + v1.1.36, the version the alerts module now pins; the predicates were also checked with + v1.1.36. The original review used `497bf53` (parser and writer plugins v1.1.26, CEL plugin + v1.1.34) and v1.1.33 predicates. Neither build is asserted to match a customer deployment. - The playground's alert writer only records alerts. Indexing, grouping, deduplication, notifications and production alerts were not tested. - The index rejection behind the geolocation change was measured on a local OpenSearch 2.13.0 @@ -183,8 +229,8 @@ so no label changes. ## Reproduce -Build the EventProcessor commit above without changing its dependencies. With `EP` set to -that checkout's absolute path: +Build EventProcessor `8a3ade72bd9d12db21f6b273200588fb49540f14` (the latest check) without +changing its dependencies. With `EP` set to that checkout's absolute path: ```sh mkdir -p "$EP/test-bin" "$EP/test-plugins" From af311e84e7e97e46ffde694dfe094d2036c75311 Mon Sep 17 00:00:00 2001 From: Ricardo Valdes Date: Thu, 24 Sep 2026 17:07:28 -0400 Subject: [PATCH 6/8] docs(cisco-asa): name the newest engine image behind the re-validation build The newest published engine image, eventprocessor:v11.2.14 (built 2026-09-24 19:13 UTC), carries EventProcessor revision 8a3ade7 with go-sdk v1.1.36, built with go1.26.8 for linux/amd64. The local build used for the re-validation is the same source compiled natively for darwin/arm64 with go1.25.7. Co-Authored-By: Claude Opus 5.5 --- filters/audits/cisco-asa.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/filters/audits/cisco-asa.md b/filters/audits/cisco-asa.md index 11e959810..b348554ec 100644 --- a/filters/audits/cisco-asa.md +++ b/filters/audits/cisco-asa.md @@ -84,6 +84,14 @@ plugins all link go-sdk v1.1.36. `v11` was merged into this branch. No file over draft, so nothing conflicted. The geolocation plugin was built from `v11` `d2479c1a`, which this branch now carries unchanged; its own `go.mod` pins go-sdk v1.1.34. +The newest published engine image, `ghcr.io/utmstack/utmstack/eventprocessor:v11.2.14` +(built 2026-09-24 19:13 UTC on base image `eventprocessor/base:1.1.7`), embeds Go build +information showing that its playground and plugin binaries come from the same +EventProcessor revision `8a3ade7` with go-sdk v1.1.36, built with go1.26.8 for linux/amd64. +The local build used here is that source revision compiled natively for darwin/arm64 with +go1.25.7; only the Go toolchain and platform differ. The image's geolocation plugin also +comes from `v11` `d2479c1a` with go-sdk v1.1.34, like the one used here. + What changed in the SDK, and what it means here: - Since v1.1.35, `utils.SanitizeField` keeps `_` in the field names that the `json` From 6eb6b4f6457cd85815a32ea947a7612c55cce986 Mon Sep 17 00:00:00 2001 From: Ricardo Valdes Date: Thu, 24 Sep 2026 20:14:39 -0400 Subject: [PATCH 7/8] fix(cisco-asa): 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/asa.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-asa (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-C9, F-C10). TestCiscoASAGrokPatternsNeverMatchEmpty 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. Two test comments now name the pinned go-sdk v1.1.36. Co-Authored-By: Claude Opus 5.5 --- filters/cisco/asa.yml | 17 +- plugins/alerts/cisco_asa_filter_test.go | 46 ++++- .../alerts/testdata/cisco-asa/expected.json | 189 +++++++++++++++++- plugins/alerts/testdata/cisco-asa/raw.json | 7 +- 4 files changed, 249 insertions(+), 10 deletions(-) diff --git a/filters/cisco/asa.yml b/filters/cisco/asa.yml index 27f97fa32..7752c28f1 100644 --- a/filters/cisco/asa.yml +++ b/filters/cisco/asa.yml @@ -1,4 +1,4 @@ -# CISCO ASA filter, version 3.1.0 +# CISCO ASA filter, version 3.1.1 # Based on examples provided and https://www.cisco.com/c/en/us/td/docs/security/asa/syslog/b_syslog.html # Filter Input requirements -> Syslog # @@ -1315,14 +1315,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: @@ -2481,7 +2486,7 @@ pipeline: - fieldName: log.irrelevant pattern: '\(' - fieldName: log.mappedIpFrom - pattern: '{{.data}}' + pattern: '[^/()]+' - fieldName: log.irrelevant pattern: '\)' source: log.tmpMappedIpPortFrom @@ -2506,7 +2511,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_asa_filter_test.go b/plugins/alerts/cisco_asa_filter_test.go index 93738cbb9..4d099afcd 100644 --- a/plugins/alerts/cisco_asa_filter_test.go +++ b/plugins/alerts/cisco_asa_filter_test.go @@ -26,7 +26,7 @@ import ( // documentation site refused automated access and no Cisco ASA record was available, so the // lines follow the filter's own patterns, use RFC 5737 and RFC 3849 documentation addresses // and example names, and none is claimed to be a documented Cisco format. These tests use -// the pinned go-sdk v1.1.33 for YAML decoding, CEL and Event conversion. They do not run the +// the pinned go-sdk (v1.1.36) for YAML decoding, CEL and Event conversion. They do not run the // EventProcessor: asaModel mirrors its step plugins, and testdata/cisco-asa/replay.py runs // the same lines through the public playground. See filters/audits/cisco-asa.md. @@ -674,6 +674,16 @@ var asaChangeCases = []struct { {"F-C8", "113011-with-equals", "origin.user", "alice"}, {"F-C8", "113011-with-equals", "log.policy", "GP1"}, {"F-C8 near miss", "113009-without-equals", "origin.user", "alice"}, + {"F-C9", "302003-hostname", "origin.ip", "host-b.example.com"}, + {"F-C9", "302003-hostname", "target.ip", "198.51.100.7"}, + {"F-C9", "302003-hostname", "log.localAddress", "host-b.example.com"}, + {"F-C9 near miss", "302003-ip", "log.localAddress", "192.0.2.10"}, + {"F-C9 near miss", "302004-to", "log.localAddress", "192.0.2.10"}, + {"F-C10", "302024-mapped-no-port", "log.mappedIpFrom", "198.51.100.7"}, + {"F-C10", "302024-mapped-no-port", "log.mappedIpTo", "203.0.113.5"}, + {"F-C10", "302024-mapped-no-port", "log.mappedPortFrom", nil}, + {"F-C10 near miss", "302022-mapped-port", "log.mappedIpFrom", "198.51.100.7"}, + {"F-C10 near miss", "302022-mapped-port", "log.mappedPortFrom", "443"}, } // Every fabricated line through the model: the named change cases, no where errors, and every @@ -722,6 +732,38 @@ func TestCiscoASAExtractionModel(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 TestCiscoASAGrokPatternsNeverMatchEmpty(t *testing.T) { + m := asaNewModel(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 reordered and guarded steps, evaluated with the SDK in filter order. func TestCiscoASAStepPredicates(t *testing.T) { steps := asaPipeline(t).Steps @@ -909,7 +951,7 @@ var asaRuleCases = []struct { {"botnet_traffic_detection", "unlisted 338003", `"log":{"messageId":338003}`, false}, } -// SDK v1.1.33 CEL on synthetic normalized events for the changed rule conditions. +// SDK CEL (v1.1.36) on synthetic normalized events for the changed rule conditions. func TestCiscoASARulePredicates(t *testing.T) { rules := asaLoadRules(t) cache := plugins.NewCELCache("cisco-asa-rules") diff --git a/plugins/alerts/testdata/cisco-asa/expected.json b/plugins/alerts/testdata/cisco-asa/expected.json index 975d3b493..a913a4a8f 100644 --- a/plugins/alerts/testdata/cisco-asa/expected.json +++ b/plugins/alerts/testdata/cisco-asa/expected.json @@ -1,5 +1,5 @@ { - "provenance": "Expected results for the FABRICATED lines in raw.json, recorded from the public EventProcessor playground (commit 497bf53dbd1ae096f7b2dbc7bce77a6bf9f22ce1) with this filter, the three rules, patterns.yaml and the fabricated geolocation data, after the declared per-change expectations were checked. fields excludes the envelope keys, deviceTime and tenantName.", + "provenance": "Expected results for the FABRICATED lines in raw.json, recorded from the public EventProcessor playground (commit 497bf53dbd1ae096f7b2dbc7bce77a6bf9f22ce1; the five 302003/302004/302022/302024 lines were added from commit 8a3ade72bd9d12db21f6b273200588fb49540f14, which gives these results for all 46 lines) with this filter, the three rules, 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": { @@ -1116,6 +1116,193 @@ "severity": "low" }, "alerts": [] + }, + "302003-hostname": { + "logObject": true, + "fields": { + "log.ciscoTime": "Sep 23 2026 10:00:00", + "log.foreignAddress": "198.51.100.7", + "log.localAddress": "host-b.example.com", + "log.localIp": "asa01.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": "Sep 23 2026 10:00:00", + "log.foreignAddress": "198.51.100.7", + "log.localAddress": "192.0.2.10", + "log.localIp": "asa01.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": "Sep 23 2026 10:00:00", + "log.foreignAddress": "198.51.100.7", + "log.localAddress": "192.0.2.10", + "log.localIp": "asa01.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": "Sep 23 2026 10:00:00", + "log.dstInterface": "inside", + "log.localIp": "asa01.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": "Sep 23 2026 10:00:00", + "log.dstInterface": "inside", + "log.localIp": "asa01.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-asa/raw.json b/plugins/alerts/testdata/cisco-asa/raw.json index f74c97b76..9b056d975 100644 --- a/plugins/alerts/testdata/cisco-asa/raw.json +++ b/plugins/alerts/testdata/cisco-asa/raw.json @@ -41,6 +41,11 @@ "vpn-113015-alice": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-6-113015: AAA user authentication Rejected : reason = Invalid password : local database : user = alice : user IP = 198.51.100.7", "vpn-113015-bob": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-6-113015: AAA user authentication Rejected : reason = Invalid password : local database : user = bob : user IP = 198.51.100.8", "vpn-109034-text": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-3-109034: Authentication failed for network user alice from 198.51.100.7/51234 to 192.0.2.10/443", - "vpn-611102-text": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-6-611102: User authentication failed: IP address: 198.51.100.7, Uname: alice" + "vpn-611102-text": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-6-611102: User authentication failed: IP address: 198.51.100.7, Uname: alice", + "302003-ip": "<166>Sep 23 2026 10:00:00 asa01.example.com : %ASA-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>Sep 23 2026 10:00:00 asa01.example.com : %ASA-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>Sep 23 2026 10:00:00 asa01.example.com : %ASA-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>Sep 23 2026 10:00:00 asa01.example.com : %ASA-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>Sep 23 2026 10:00:00 asa01.example.com : %ASA-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 2cf17e58b18e32790ee0d6f06fe01d86303aa603 Mon Sep 17 00:00:00 2001 From: Ricardo Valdes Date: Thu, 24 Sep 2026 20:14:39 -0400 Subject: [PATCH 8/8] docs(cisco-asa): record the empty-match correction Adds the version 3.1.1 section: what the grok plugin does with an empty match, the three steps and their fix, and the checks on EventProcessor 8a3ade7. replay.py gives 46 events and the same two botnet alerts, the 80 private fabricated lines are unchanged, the go-sdk v1.1.36 rule replay passes 13 of 13 checks and the earlier predicate checks 90 of 90 and 65 of 65, 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-asa.md | 46 +++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/filters/audits/cisco-asa.md b/filters/audits/cisco-asa.md index b348554ec..fa1c96b72 100644 --- a/filters/audits/cisco-asa.md +++ b/filters/audits/cisco-asa.md @@ -12,6 +12,8 @@ ThreatWinds go-sdk **v1.1.36**, as pinned by `plugins/alerts/go.mod` since offic (`d2479c1a`) was merged into this branch. The review itself used v1.1.33, whose `plugins.proto`, `plugins/cel.go` and `plugins/rules.go` are identical. The draft was checked again on the latest versions; see [Re-validation on the latest versions](#re-validation-on-the-latest-versions). +A later check of every grok pattern found three more 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 @@ -120,13 +122,48 @@ and `password` settings. `replay.py` still gives one URL, so the client gets an and connects to port 443 on the test computer, where nothing listened. Any history search therefore still fails and is reported; none was attempted with the corrected rules. +## 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,214 grok patterns of version 3.1.0 were expanded with `patterns.yaml` and tried +on their own. Three of them, in three steps, had this problem: + +| Change | Why | Proof on the engine | +|---|---|---| +| 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. | `302003-hostname` kept only `actionResult`; now it has `action`, both addresses and both ports. `302003-ip` and `302004-to` are unchanged. | +| 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. | `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 164 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. A step whose text runs out before +its last pattern still fails, for that reason; the known case, 302014 without a reason, is D06. + +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. Address-only patterns stay +deferred (D14). `filters/cisco/firepower.yml` has the same three steps and 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 46 lines | 46 events without errors, every stored field as in `expected.json`; the 41 earlier lines are unchanged. Two alerts, both from the botnet rule, on 338001 and 338002; no Circuit Breaker alert and no history search attempted. | +| The 80 private fabricated lines, version 3.1.1 | 80 events, each identical to the version 3.1.0 run on the same engine, including the two 302004 lines. | +| go-sdk v1.1.36 rule replay | Over the 46 events, the botnet rule matches the two botnet lines and nothing else, and the other two rules match nothing; no error and no unresolved placeholder (13 of 13 checks). The earlier predicate checks, repeated with the new events: 90 of 90 and 65 of 65. | +| Full `plugins/alerts` suite | 51 tests pass, 11 skip, none fail (2,267 passing results with subtests). | +| New Go checks, on versions 3.1.0 and 3.1.1 | `TestCiscoASAGrokPatternsNeverMatchEmpty` 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-C9, F-C10). On 3.1.0 both fail, naming the three patterns and the missing fields; on 3.1.1 both pass. | + ## Validation These are the original review's results, on EventProcessor `497bf53` and go-sdk v1.1.33. The section above repeats them on the latest versions. -**Fabricated regression, committed.** `plugins/alerts/testdata/cisco-asa/` holds 41 invented -raw lines (`raw.json`), their expected fields and alerts (`expected.json`), the 13 shared +**Fabricated regression, committed.** `plugins/alerts/testdata/cisco-asa/` holds 46 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`. The lines follow the filter's own patterns; they are not claimed to be Cisco's @@ -176,7 +213,8 @@ in filter order; the rules' names, metadata, impact, grouping and history search synthetic rule cases; and that whenever a rule with a history search matches any event, its placeholders resolve. All seven fail against the original filter and rules and pass against this revision. 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. +that need other technologies' private evidence skip, as they do on the base commit. The +empty-match correction later added an eighth test; see that section. ## Deferred @@ -221,7 +259,7 @@ so no label changes. because this filter cannot give their messages an `origin.ip` yet (D01, D02). Their positive cases are the SDK predicate checks and the earlier local history test. - 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 41 lines, but `replay.py` is the check + playground on every non-geolocation field of the 46 lines, but `replay.py` is the check that runs the engine. - `equals("log.severity", "4")` compares numbers, like the neighbouring `oneOf` severity clauses, so severity text such as `04` or `+4` now counts as 4; the old clause accepted only