2017-02-13 23:14:40 +01:00
# Output backends for sigmac
2018-07-24 00:01:16 +02:00
# Copyright 2018 Thomas Patzke
2017-12-07 21:55:43 +01:00
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2017-02-13 23:14:40 +01:00
2017-02-22 22:47:12 +01:00
import re
2021-10-03 17:37:05 -07:00
import sigma
2020-05-08 13:41:52 +03:00
from functools import wraps
2018-07-20 23:30:32 +02:00
from . base import SingleTextQueryBackend
from . exceptions import NotSupportedError
2020-10-06 15:07:52 +03:00
from . . parser . modifiers . base import SigmaTypeModifier
2017-02-13 23:14:40 +01:00
2020-05-08 13:41:52 +03:00
def wrapper ( method ) :
@wraps ( method )
def _impl ( self , method_args ) :
key , value , * _ = method_args
if ' .keyword ' in key :
key = key . split ( ' .keyword ' ) [ 0 ]
if key not in self . skip_fields :
method_output = method ( self , method_args )
return method_output
else :
return
return _impl
2018-05-21 23:07:47 +02:00
class WindowsDefenderATPBackend ( SingleTextQueryBackend ) :
2020-05-08 13:41:52 +03:00
""" Converts Sigma rule into Microsoft Defender ATP Hunting Queries. """
identifier = " mdatp "
2018-05-21 23:07:47 +02:00
active = True
2019-04-22 23:40:21 +02:00
config_required = False
2018-05-21 23:07:47 +02:00
2021-04-01 16:01:38 +01:00
reEscape = re . compile ( ' (?: \\ \\ )?( " ) ' )
2018-05-21 23:07:47 +02:00
reClear = None
andToken = " and "
orToken = " or "
notToken = " not "
subExpression = " ( %s ) "
listExpression = " ( %s ) "
listSeparator = " , "
valueExpression = " \" %s \" "
nullExpression = " isnull( %s ) "
notNullExpression = " isnotnull( %s ) "
mapExpression = " %s == %s "
mapListsSpecialHandling = True
mapListValueExpression = " %s in %s "
2021-04-01 16:01:38 +01:00
2020-05-08 13:41:52 +03:00
skip_fields = {
" Description " ,
" _exists_ " ,
" FileVersion " ,
" Product " ,
" Company " ,
2020-10-21 12:16:17 +02:00
" IMPHASH " ,
2020-05-08 13:41:52 +03:00
}
2018-05-21 23:07:47 +02:00
def __init__ ( self , * args , * * kwargs ) :
""" Initialize field mappings """
super ( ) . __init__ ( * args , * * kwargs )
self . fieldMappings = { # mapping between Sigma and ATP field names
2020-05-02 14:31:02 +01:00
# Supported values:
# (field name mapping, value mapping): distinct mappings for field name and value, may be a string (direct mapping) or function maps name/value to ATP target value
# (mapping function,): receives field name and value as parameter, return list of 2 element tuples (destination field name and value)
# (replacement, ): Replaces field occurrence with static string
" DeviceProcessEvents " : {
" AccountName " : ( self . id_mapping , self . default_value_mapping ) ,
" CommandLine " : ( " ProcessCommandLine " , self . default_value_mapping ) ,
" DeviceName " : ( self . id_mapping , self . default_value_mapping ) ,
" EventType " : ( " ActionType " , self . default_value_mapping ) ,
2021-02-04 11:54:29 +00:00
" FileName " : ( self . id_mapping , self . default_value_mapping ) ,
2020-05-02 14:31:02 +01:00
" Image " : ( " FolderPath " , self . default_value_mapping ) ,
2020-10-21 12:16:17 +02:00
" ImagePath " : ( " FolderPath " , self . default_value_mapping ) ,
2020-05-02 14:31:02 +01:00
" ImageLoaded " : ( " FolderPath " , self . default_value_mapping ) ,
" LogonType " : ( self . id_mapping , self . logontype_mapping ) ,
" NewProcessName " : ( " FolderPath " , self . default_value_mapping ) ,
2021-09-10 15:51:32 -07:00
" OriginalFileName " : ( " ProcessVersionInfoOriginalFileName " , self . default_value_mapping ) ,
2021-02-04 11:54:29 +00:00
" ParentCommandLine " : ( " InitiatingProcessCommandLine " , self . default_value_mapping ) ,
2020-10-21 12:16:17 +02:00
" ParentName " : ( " InitiatingProcessFileName " , self . default_value_mapping ) ,
" ParentProcessName " : ( " InitiatingProcessFileName " , self . default_value_mapping ) ,
2021-04-01 16:01:38 +01:00
" ParentImage " : ( " InitiatingProcessFolderPath " , self . default_value_mapping ) ,
2020-05-02 14:31:02 +01:00
" SourceImage " : ( " InitiatingProcessFolderPath " , self . default_value_mapping ) ,
2021-04-01 16:01:38 +01:00
" TargetImage " : ( " FolderPath " , self . default_value_mapping ) ,
2020-05-02 14:31:02 +01:00
" User " : ( self . decompose_user , ) ,
} ,
" DeviceEvents " : {
2021-02-04 11:54:29 +00:00
" AccountName " : ( self . id_mapping , self . default_value_mapping ) ,
" CommandLine " : ( " ProcessCommandLine " , self . default_value_mapping ) ,
" DestinationHostname " : ( " RemoteUrl " , self . default_value_mapping ) ,
" DestinationIp " : ( " RemoteIP " , self . default_value_mapping ) ,
2021-04-01 16:01:38 +01:00
" DestinationPort " : ( " RemotePort " , self . porttype_mapping ) ,
2021-02-04 11:54:29 +00:00
" EventType " : ( " ActionType " , self . default_value_mapping ) ,
" FileName " : ( self . id_mapping , self . default_value_mapping ) ,
" ParentCommandLine " : ( " InitiatingProcessCommandLine " , self . default_value_mapping ) ,
" ParentProcessName " : ( " InitiatingProcessParentFileName " , self . default_value_mapping ) ,
2021-04-01 16:01:38 +01:00
" ProcessName " : ( " InitiatingProcessFileName " , self . default_value_mapping ) ,
2021-06-03 21:43:52 +02:00
" ServiceFileName " : ( " FileName " , self . default_value_mapping ) ,
2021-02-04 11:54:29 +00:00
" SourceIp " : ( " LocalIP " , self . default_value_mapping ) ,
2021-04-01 16:01:38 +01:00
" SourcePort " : ( " LocalPort " , self . porttype_mapping ) ,
2020-05-02 14:31:02 +01:00
" TargetFilename " : ( " FolderPath " , self . default_value_mapping ) ,
2021-02-04 11:54:29 +00:00
" TargetObject " : ( " RegistryKey " , self . default_value_mapping ) ,
2021-04-01 16:01:38 +01:00
" TargetImage " : ( " FolderPath " , self . default_value_mapping ) ,
2020-05-02 14:46:55 +01:00
" Image " : ( " InitiatingProcessFolderPath " , self . default_value_mapping ) ,
2020-05-02 14:31:02 +01:00
" User " : ( self . decompose_user , ) ,
} ,
2021-04-01 16:01:38 +01:00
" DeviceRegistryEvents " : {
2021-02-04 11:54:29 +00:00
" DataType " : ( " RegistryValueType " , self . default_value_mapping ) ,
2020-05-02 14:31:02 +01:00
" Details " : ( " RegistryValueData " , self . default_value_mapping ) ,
2020-06-30 14:49:29 +01:00
" EventType " : ( " ActionType " , self . default_value_mapping ) ,
2021-04-01 16:01:38 +01:00
" Image " : ( " InitiatingProcessFolderPath " , self . default_value_mapping ) ,
" CommandLine " : ( " InitiatingProcessCommandLine " , self . default_value_mapping ) ,
2021-02-04 11:54:29 +00:00
" ObjectValueName " : ( " RegistryValueName " , self . default_value_mapping ) ,
2021-04-01 16:01:38 +01:00
" ProcessName " : ( " InitiatingProcessFileName " , self . default_value_mapping ) ,
2020-10-21 12:16:17 +02:00
" ParentName " : ( " InitiatingProcessParentFileName " , self . default_value_mapping ) ,
" ParentProcessName " : ( " InitiatingProcessParentFileName " , self . default_value_mapping ) ,
2021-02-04 11:54:29 +00:00
" TargetObject " : ( " RegistryKey " , self . default_value_mapping ) ,
2020-05-02 14:31:02 +01:00
" User " : ( self . decompose_user , ) ,
} ,
" DeviceFileEvents " : {
2021-02-04 11:54:29 +00:00
" FileName " : ( self . id_mapping , self . default_value_mapping ) ,
" OriginIp " : ( " FileOriginIp " , self . default_value_mapping ) ,
" OriginReferrer " : ( " FileOriginReferrerUrl " , self . default_value_mapping ) ,
2021-04-01 16:01:38 +01:00
" OriginUrl " : ( " FileOriginUrl " , self . default_value_mapping ) ,
2020-05-02 14:31:02 +01:00
" Image " : ( " InitiatingProcessFolderPath " , self . default_value_mapping ) ,
2021-04-01 16:01:38 +01:00
" CommandLine " : ( " InitiatingProcessCommandLine " , self . default_value_mapping ) ,
" ProcessName " : ( " InitiatingProcessFileName " , self . default_value_mapping ) ,
2020-10-21 12:16:17 +02:00
" ParentName " : ( " InitiatingProcessParentFileName " , self . default_value_mapping ) ,
" ParentProcessName " : ( " InitiatingProcessParentFileName " , self . default_value_mapping ) ,
2021-04-01 16:01:38 +01:00
" TargetFilename " : ( " FolderPath " , self . default_value_mapping ) ,
2020-05-02 14:31:02 +01:00
" User " : ( self . decompose_user , ) ,
} ,
2021-04-01 16:01:38 +01:00
" DeviceNetworkEvents " : {
2021-02-04 11:54:29 +00:00
" DestinationHostname " : ( " RemoteUrl " , self . default_value_mapping ) ,
2020-05-02 14:31:02 +01:00
" DestinationIp " : ( " RemoteIP " , self . default_value_mapping ) ,
" DestinationIsIpv6 " : ( " RemoteIP has \" : \" " , ) ,
2021-04-01 16:01:38 +01:00
" DestinationPort " : ( " RemotePort " , self . porttype_mapping ) ,
2021-02-04 11:54:29 +00:00
" DeviceName " : ( self . id_mapping , self . default_value_mapping ) ,
2020-06-30 14:49:29 +01:00
" EventType " : ( " ActionType " , self . default_value_mapping ) ,
2020-05-02 14:31:02 +01:00
" Image " : ( " InitiatingProcessFolderPath " , self . default_value_mapping ) ,
2021-04-01 16:01:38 +01:00
" CommandLine " : ( " InitiatingProcessCommandLine " , self . default_value_mapping ) ,
2021-02-04 11:54:29 +00:00
" Initiated " : ( " RemotePort " , self . default_value_mapping ) ,
2021-04-01 16:01:38 +01:00
" ProcessName " : ( " InitiatingProcessFileName " , self . default_value_mapping ) ,
2021-02-04 11:54:29 +00:00
" Protocol " : ( " RemoteProtocol " , self . default_value_mapping ) ,
" SourceIp " : ( " LocalIP " , self . default_value_mapping ) ,
2021-04-01 16:01:38 +01:00
" SourcePort " : ( " LocalPort " , self . porttype_mapping ) ,
2020-05-02 14:31:02 +01:00
" User " : ( self . decompose_user , ) ,
} ,
" DeviceImageLoadEvents " : {
2021-02-04 11:54:29 +00:00
" DeviceName " : ( self . id_mapping , self . default_value_mapping ) ,
2021-04-01 16:01:38 +01:00
" EventType " : ( " ActionType " , self . default_value_mapping ) ,
2021-02-04 11:54:29 +00:00
" FileName " : ( self . id_mapping , self . default_value_mapping ) ,
2020-05-02 14:31:02 +01:00
" Image " : ( " InitiatingProcessFolderPath " , self . default_value_mapping ) ,
2021-03-18 15:49:25 -07:00
" ImageLoaded " : ( " FolderPath " , self . default_value_mapping ) ,
2021-08-12 18:06:10 +01:00
" CommandLine " : ( " InitiatingProcessCommandLine " , self . default_value_mapping ) ,
2021-02-04 11:54:29 +00:00
" ParentProcessName " : ( " InitiatingProcessParentFileName " , self . default_value_mapping ) ,
" ProcessName " : ( " InitiatingProcessFileName " , self . default_value_mapping ) ,
" TargetImage " : ( " FolderPath " , self . default_value_mapping ) ,
2020-05-02 14:31:02 +01:00
" User " : ( self . decompose_user , ) ,
}
}
2020-10-21 10:12:17 +03:00
self . current_table = " "
2018-05-21 23:07:47 +02:00
2020-10-06 15:07:52 +03:00
def generateANDNode ( self , node ) :
generated = [ self . generateNode ( val ) for val in node ]
filtered = [ ]
for g in generated :
if g and g . startswith ( " ActionType " ) :
if not any ( [ i for i in filtered if i . startswith ( " ActionType " ) ] ) :
filtered . append ( g )
else :
continue
elif g is not None :
filtered . append ( g )
if filtered :
if self . sort_condition_lists :
filtered = sorted ( filtered )
return self . andToken . join ( filtered )
else :
return None
2018-05-21 23:07:47 +02:00
def id_mapping ( self , src ) :
""" Identity mapping, source == target field name """
return src
def default_value_mapping ( self , val ) :
2020-11-12 14:09:30 +01:00
op = " =~ "
2019-05-15 21:25:53 +02:00
if type ( val ) == str :
2021-04-01 16:01:38 +01:00
if " * " in val [ 1 : - 1 ] :
# value contains * inside string - use regex match
2019-05-15 21:25:53 +02:00
op = " matches regex "
val = re . sub ( ' ([ " .^$]| \\ \\ (?![*?])) ' , ' \\ \\ \ g<1> ' , val )
val = re . sub ( ' \\ * ' , ' .* ' , val )
val = re . sub ( ' \\ ? ' , ' . ' , val )
2021-04-01 16:01:38 +01:00
else :
# value possibly only starts and/or ends with *, use prefix/postfix match
2019-05-15 21:25:53 +02:00
if val . endswith ( " * " ) and val . startswith ( " * " ) :
op = " contains "
val = self . cleanValue ( val [ 1 : - 1 ] )
elif val . endswith ( " * " ) :
op = " startswith "
val = self . cleanValue ( val [ : - 1 ] )
elif val . startswith ( " * " ) :
op = " endswith "
val = self . cleanValue ( val [ 1 : ] )
2018-05-21 23:07:47 +02:00
return " %s \" %s \" " % ( op , val )
2021-04-01 16:01:38 +01:00
def porttype_mapping ( self , val ) :
return " %s \" %s \" " % ( " == " , val )
2018-05-21 23:07:47 +02:00
def logontype_mapping ( self , src ) :
""" Value mapping for logon events to reduced ATP LogonType set """
logontype_mapping = {
2020-05-02 14:31:02 +01:00
2 : " Interactive " ,
3 : " Network " ,
4 : " Batch " ,
5 : " Service " ,
7 : " Interactive " , # unsure
8 : " Network " ,
9 : " Interactive " , # unsure
10 : " Remote interactive (RDP) logons " , # really the value?
11 : " Interactive "
}
2018-05-21 23:07:47 +02:00
try :
return logontype_mapping [ int ( src ) ]
except KeyError :
raise NotSupportedError ( " Logon type %d unknown and can ' t be mapped " % src )
def decompose_user ( self , src_field , src_value ) :
""" Decompose domain \\ user User field of Sysmon events into ATP InitiatingProcessAccountDomain and InititatingProcessAccountName. """
reUser = re . compile ( " ^(.*?) \\ \\ (.*)$ " )
m = reUser . match ( src_value )
if m :
domain , user = m . groups ( )
2020-05-02 14:31:02 +01:00
return ( ( " InitiatingProcessAccountDomain " , self . default_value_mapping ( domain ) ) , ( " InititatingProcessAccountName " , self . default_value_mapping ( user ) ) )
2018-05-21 23:07:47 +02:00
else : # assume only user name is given if backslash is missing
2020-05-02 14:37:37 +01:00
return ( ( " InititatingProcessAccountName " , self . default_value_mapping ( src_value ) ) )
2018-05-21 23:07:47 +02:00
def generate ( self , sigmaparser ) :
2020-10-06 15:07:52 +03:00
self . tables = [ ]
try :
self . category = sigmaparser . parsedyaml [ ' logsource ' ] . setdefault ( ' category ' , None )
self . product = sigmaparser . parsedyaml [ ' logsource ' ] . setdefault ( ' product ' , None )
self . service = sigmaparser . parsedyaml [ ' logsource ' ] . setdefault ( ' service ' , None )
except KeyError :
self . category = None
self . product = None
self . service = None
2018-05-21 23:07:47 +02:00
2021-08-12 18:06:10 +01:00
if ( self . category , self . service ) == ( " process_creation " , None ) and self . product in [ ' windows ' , ' linux ' , ' macos ' ] :
2020-10-06 15:07:52 +03:00
self . tables . append ( " DeviceProcessEvents " )
self . current_table = " DeviceProcessEvents "
2020-10-21 12:16:17 +02:00
elif ( self . category , self . product , self . service ) == ( " registry_event " , " windows " , None ) :
self . tables . append ( " DeviceRegistryEvents " )
self . current_table = " DeviceRegistryEvents "
2021-08-12 18:06:10 +01:00
elif ( self . category , self . service ) == ( " file_event " , None ) and self . product in [ ' windows ' , ' linux ' , ' macos ' ] :
2020-10-21 12:16:17 +02:00
self . tables . append ( " DeviceFileEvents " )
self . current_table = " DeviceFileEvents "
2021-03-18 15:49:25 -07:00
elif ( self . category , self . product , self . service ) == ( " image_load " , " windows " , None ) :
self . tables . append ( " DeviceImageLoadEvents " )
self . current_table = " DeviceImageLoadEvents "
2021-08-12 18:06:10 +01:00
elif ( self . category , self . service ) == ( " network_connection " , None ) and self . product in [ ' windows ' , ' linux ' , ' macos ' ] :
2020-10-21 12:16:17 +02:00
self . tables . append ( " DeviceNetworkEvents " )
self . current_table = " DeviceNetworkEvents "
2019-08-20 14:33:08 -07:00
elif ( self . category , self . product , self . service ) == ( None , " windows " , " powershell " ) :
2020-10-06 15:07:52 +03:00
self . tables . append ( " DeviceEvents " )
self . current_table = " DeviceEvents "
2019-08-20 14:33:08 -07:00
self . orToken = " , "
2020-10-06 15:07:52 +03:00
elif ( self . category , self . product , self . service ) == ( None , " windows " , " security " ) :
self . tables . append ( " DeviceAlertEvents " )
self . current_table = " DeviceAlertEvents "
2019-01-14 23:54:05 +01:00
2018-09-06 00:31:40 +02:00
return super ( ) . generate ( sigmaparser )
2018-05-21 23:07:47 +02:00
def generateBefore ( self , parsed ) :
2020-10-06 15:07:52 +03:00
if not any ( self . tables ) :
2020-05-08 13:41:52 +03:00
raise NotSupportedError ( " No MDATP table could be determined from Sigma rule " )
2020-10-06 15:07:52 +03:00
# if self.tables in "DeviceEvents" and self.service == "powershell":
# return "%s | where tostring(extractjson('$.Command', AdditionalFields)) in~ " % self.tables
if len ( self . tables ) == 1 :
if self . tables [ 0 ] == " DeviceEvents " and self . service == " powershell " :
return " %s | where tostring(extractjson( ' $.Command ' , AdditionalFields)) in~ " % self . tables
return " %s | where " % self . tables [ 0 ]
else :
if " DeviceEvents " in self . tables and self . service == " powershell " :
return " union %s | where tostring(extractjson( ' $.Command ' , AdditionalFields)) in~ " % " , " . join ( self . tables )
return " union %s | where " % " , " . join ( self . tables )
def generateORNode ( self , node ) :
generated = super ( ) . generateORNode ( node )
if generated :
return " %s " % generated
return generated
2021-04-01 16:01:38 +01:00
def cleanValue ( self , val ) :
if self . reEscape :
val = self . reEscape . sub ( self . escapeSubst , val )
return val
2020-10-06 15:07:52 +03:00
def mapEventId ( self , event_id ) :
if self . product == " windows " :
if self . service == " sysmon " and event_id == 1 \
or self . service == " security " and event_id == 4688 : # Process Execution
self . tables . append ( " DeviceProcessEvents " )
self . current_table = " DeviceProcessEvents "
return None
elif self . service == " sysmon " and event_id == 3 : # Network Connection
self . tables . append ( " DeviceNetworkEvents " )
self . current_table = " DeviceNetworkEvents "
return None
elif self . service == " sysmon " and event_id == 7 : # Image Load
self . tables . append ( " DeviceImageLoadEvents " )
self . current_table = " DeviceImageLoadEvents "
return None
elif self . service == " sysmon " and event_id == 8 : # Create Remote Thread
self . tables . append ( " DeviceEvents " )
self . current_table = " DeviceEvents "
return " ActionType == \" CreateRemoteThreadApiCall \" "
elif self . service == " sysmon " and event_id == 11 : # File Creation
self . tables . append ( " DeviceFileEvents " )
self . current_table = " DeviceFileEvents "
return " ActionType == \" FileCreated \" "
elif self . service == " sysmon " and event_id == 23 : # File Deletion
self . tables . append ( " DeviceFileEvents " )
self . current_table = " DeviceFileEvents "
return " ActionType == \" FileDeleted \" "
elif self . service == " sysmon " and event_id == 12 : # Create/Delete Registry Value
self . tables . append ( " DeviceRegistryEvents " )
self . current_table = " DeviceRegistryEvents "
return None
elif self . service == " sysmon " and event_id == 13 \
or self . service == " security " and event_id == 4657 : # Set Registry Value
self . tables . append ( " DeviceRegistryEvents " )
self . current_table = " DeviceRegistryEvents "
return " ActionType == \" RegistryValueSet \" "
elif self . service == " security " and event_id == 4624 :
self . tables . append ( " DeviceLogonEvents " )
self . current_table = " DeviceLogonEvents "
return None
2021-06-03 21:43:52 +02:00
elif self . service == " system " and event_id == 7045 : # New Service Install
self . tables . append ( " DeviceEvents " )
self . current_table = " DeviceEvents "
return " ActionType == \" ServiceInstalled \" "
2020-10-06 15:07:52 +03:00
else :
if not self . tables :
raise NotSupportedError ( " No sysmon Event ID provided " )
else :
raise NotSupportedError ( " No mapping for Event ID %s " % event_id )
2018-05-21 23:07:47 +02:00
2020-05-08 13:41:52 +03:00
@wrapper
2018-05-21 23:07:47 +02:00
def generateMapItemNode ( self , node ) :
"""
ATP queries refer to event tables instead of Windows logging event identifiers. This method catches conditions that refer to this field
and creates an appropriate table reference.
"""
key , value = node
2020-10-06 15:07:52 +03:00
if key == " EventID " :
# EventIDs are not reflected in condition but in table selection
if isinstance ( value , str ) or isinstance ( value , int ) :
value = int ( value ) if isinstance ( value , str ) else value
return self . mapEventId ( value )
elif isinstance ( value , list ) :
return_payload = [ ]
for event_id in value :
res = self . mapEventId ( event_id )
if res :
return_payload . append ( res )
if len ( return_payload ) == 1 :
return return_payload [ 0 ]
elif not any ( return_payload ) :
2018-05-21 23:07:47 +02:00
return None
2020-05-02 14:31:02 +01:00
else :
2020-10-06 15:07:52 +03:00
return " ( %s ) " % self . generateORNode (
[ ( key , v ) for v in value ]
)
if type ( value ) == list : # handle map items with values list like multiple OR-chained conditions
return " ( %s ) " % self . generateORNode (
[ ( key , self . cleanValue ( v ) ) for v in value ]
)
2018-05-21 23:07:47 +02:00
elif type ( value ) in ( str , int ) : # default value processing
try :
2020-10-06 15:07:52 +03:00
mapping = self . fieldMappings [ self . current_table ] [ key ]
2018-05-21 23:07:47 +02:00
except KeyError :
2020-10-06 15:07:52 +03:00
raise NotSupportedError ( " No mapping defined for field ' %s ' in ' %s ' " % ( key , self . tables ) )
2018-05-21 23:07:47 +02:00
if len ( mapping ) == 1 :
mapping = mapping [ 0 ]
if type ( mapping ) == str :
return mapping
elif callable ( mapping ) :
2020-10-06 15:07:52 +03:00
conds = mapping ( key , self . cleanValue ( value ) )
2020-05-02 14:31:02 +01:00
return self . andToken . join ( [ " {} {} " . format ( * cond ) for cond in conds ] )
2018-05-21 23:07:47 +02:00
elif len ( mapping ) == 2 :
result = list ( )
2020-10-06 15:07:52 +03:00
for mapitem , val in zip ( mapping , node ) : # iterate mapping and mapping source value synchronously over key and value
2018-05-21 23:07:47 +02:00
if type ( mapitem ) == str :
result . append ( mapitem )
elif callable ( mapitem ) :
2020-10-06 15:07:52 +03:00
result . append ( mapitem ( self . cleanValue ( val ) ) )
2018-05-21 23:07:47 +02:00
return " {} {} " . format ( * result )
else :
raise TypeError ( " Backend does not support map values of type " + str ( type ( value ) ) )
2020-10-06 15:07:52 +03:00
elif isinstance ( value , SigmaTypeModifier ) :
try :
mapping = self . fieldMappings [ self . current_table ] [ key ]
except KeyError :
raise NotSupportedError ( " No mapping defined for field ' %s ' in ' %s ' " % ( key , self . tables ) )
return self . generateMapItemTypedNode ( mapping [ 0 ] , value )
2018-05-21 23:07:47 +02:00
return super ( ) . generateMapItemNode ( node )
2021-10-03 17:37:05 -07:00
def generateAggregation ( self , agg ) :
if agg == None :
return " "
if agg . aggfunc == sigma . parser . condition . SigmaAggregationParser . AGGFUNC_NEAR :
raise NotImplementedError ( " The ' near ' aggregation operator is not yet implemented for this backend " )
if agg . groupfield == None :
if agg . aggfunc_notrans == ' count ' :
if agg . aggfield == None :
return " | summarize val=count() | where val %s %s " % ( agg . cond_op , agg . condition )
else :
agg . aggfunc_notrans = ' dcount '
return " | summarize val= %s ( %s ) as val | where val %s %s " % ( agg . aggfunc_notrans , agg . aggfield or " " , agg . cond_op , agg . condition )
else :
if agg . aggfunc_notrans == ' count ' :
if agg . aggfield == None :
return " | summarize val=count() by %s | where val %s %s " % ( agg . groupfield , agg . cond_op , agg . condition )
else :
agg . aggfunc_notrans = ' dcount '
return " | summarize val= %s ( %s ) by %s | where val %s %s " % ( agg . aggfunc_notrans , agg . aggfield or " " , agg . groupfield or " " , agg . cond_op , agg . condition )