(g)ULP!
Loading...
Searching...
No Matches
mapping.helpers Namespace Reference

Functions

dict _get_mappings_internal (str mapping_file_path)
 
GulpMapping get_enriched_mapping_for_ingestion (ProcessingPipeline pipeline=None, str mapping_file_path=None, str mapping_id=None, str product=None)
 
ProcessingPipeline get_enriched_pipeline (ProcessingPipeline pipeline=None, str mapping_file_path=None, str mapping_id=None, str product=None, **kwargs)
 
GulpMapping get_mapping_from_file (str mapping_file_path, str mapping_id=None)
 
list[GulpMapping] get_mappings_from_file (str mapping_file_path)
 
 init (logging.Logger logger)
 

Variables

logging _logger = None
 

Function Documentation

◆ _get_mappings_internal()

dict _get_mappings_internal ( str mapping_file_path)
protected
Check for file existance and return json file as dict

Args:
    mapping_file_path (str): file to load mappings from

Raises:
    FileNotFoundError: file does not exist

Returns:
    dict: the loaded dictionary

Definition at line 25 of file helpers.py.

25async def _get_mappings_internal(mapping_file_path: str) -> dict:
26 """Check for file existance and return json file as dict
27
28 Args:
29 mapping_file_path (str): file to load mappings from
30
31 Raises:
32 FileNotFoundError: file does not exist
33
34 Returns:
35 dict: the loaded dictionary
36 """
37 l = _logger
38 if l is None:
39 l = logging.getLogger()
40
41 # read mappings from file path, if it exists
42 exists = await muty.file.exists_async(mapping_file_path)
43 if not exists:
44 l.warning("mapping file not found: %s" % (mapping_file_path))
45 raise FileNotFoundError("mapping file not found: %s" % (mapping_file_path))
46
47 # load the mapping file
48 l.debug("loading mapping file: %s" % (mapping_file_path))
49 buf = await muty.file.read_file_async(mapping_file_path)
50 js = json.loads(buf)
51 mappings = js["mappings"]
52 return mappings
53
Here is the caller graph for this function:

◆ get_enriched_mapping_for_ingestion()

GulpMapping get_enriched_mapping_for_ingestion ( ProcessingPipeline pipeline = None,
str mapping_file_path = None,
str mapping_id = None,
str product = None )
Retrieves an enriched mapping by merging a pipeline mapping and a file mapping.

NOTE: This is to be used solely by the INGESTION plugins.

Args:
    pipeline (ProcessingPipeline, optional): The processing pipeline containing the mapping. Defaults to None.
    mapping_file_path (str, optional): The file path of the mapping file. Defaults to None.
    mapping_id (str, optional): The ID of the mapping. Defaults to None.

Returns:
    GulpMapping: The enriched mapping, may be an empty GulpMapping if i.e. both pipeline and mapping_file_path are not provided.

Definition at line 171 of file helpers.py.

176) -> GulpMapping:
177 """
178 Retrieves an enriched mapping by merging a pipeline mapping and a file mapping.
179
180 NOTE: This is to be used solely by the INGESTION plugins.
181
182 Args:
183 pipeline (ProcessingPipeline, optional): The processing pipeline containing the mapping. Defaults to None.
184 mapping_file_path (str, optional): The file path of the mapping file. Defaults to None.
185 mapping_id (str, optional): The ID of the mapping. Defaults to None.
186
187 Returns:
188 GulpMapping: The enriched mapping, may be an empty GulpMapping if i.e. both pipeline and mapping_file_path are not provided.
189
190 """
191 l = _logger
192 if l is None:
193 l = logging.getLogger()
194
195 pipeline_mapping: GulpMapping = None
196 file_mapping: GulpMapping = None
197
198 if pipeline is None and mapping_file_path is None:
199 # return an empty mapping
200 return GulpMapping.from_dict({})
201
202 if mapping_file_path is not None:
203 # get mapping from file
204 try:
205 file_mapping = await get_mapping_from_file(
206 mapping_file_path, mapping_id=mapping_id
207 )
208 except Exception as ex:
209 l.exception(
210 "error loading mapping file: %s, ex=%s" % (mapping_file_path, ex)
211 )
212 if pipeline is None:
213 return GulpMapping.from_dict({})
214
215 if pipeline is not None:
216 # get mapping from pipeline, convert each FieldMappingTransformation item to GulpMapping
217 l.debug("turning provided pipeline to GulpMapping ...")
218 d: dict = {
219 "fields": {},
220 "options": (
221 file_mapping.options.to_dict()
222 if file_mapping is not None and file_mapping.options is not None
223 else None
224 ),
225 }
226 for item in pipeline.items:
227 if isinstance(item.transformation, FieldMappingTransformation):
228 for k, v in item.transformation.mapping.items():
229 dd = {"map_to": v}
230 d["fields"][k] = dd
231
232 pipeline_mapping = GulpMapping.from_dict(d)
233
234 if pipeline_mapping is None:
235 # return mapping from file
236 l.warning(
237 "no pipeline provided, returning file mapping: %s"
238 % (json.dumps(file_mapping.to_dict(), indent=2))
239 )
240
241 return file_mapping
242
243 if file_mapping is None:
244 l.warning(
245 "no file mapping provided, returning pipeline mapping: %s"
246 % (json.dumps(pipeline_mapping.to_dict(), indent=2))
247 )
248 return pipeline_mapping
249
250 # merge mapping into pipeline_mapping
251 l.debug("merging file mapping into pipeline mapping ...")
252 # l.debug("pipeline_mapping PRE=\n%s" % (json.dumps(pipeline_mapping.to_dict(), indent=2)))
253 # l.debug("file_mapping=%s" % (json.dumps(file_mapping.to_dict(), indent=2)))
254 for m, v in file_mapping.fields.items():
255 if m not in pipeline_mapping.fields.keys():
256 # this seems a pylint issue: https://github.com/pylint-dev/pylint/issues/2767 and related
257 # pylint: disable=unsupported-assignment-operation
258 pipeline_mapping.fields[m] = v
259 else:
260 # merge
261 pipeline_v: FieldMappingEntry = pipeline_mapping[m]
262 file_v: FieldMappingEntry = v
263 if file_v.is_variable_mapping:
264 # "map_to" is a list[list[str]]
265 # where each member of the inner list is a variable mapping with 3 strings(logsourcename, logsource, mapped)
266 # since we're calling this for ingestion only, we simply convert the affected "map_to" to a multiple string mapping, to map the field to multiple values
267 real_map_to = []
268 for vm in file_v.map_to:
269 # logsrc_field = vm[0]
270 # logsrc = vm[1]
271 mapped = vm[2]
272 real_map_to.append(mapped)
273 file_v.map_to = real_map_to
274
275 # depending if the source (file) and destination (pipeline) mapping are strings or lists, we need to merge them accordingly
276 if file_v.map_to is not None:
277 if isinstance(pipeline_v.map_to, list) and isinstance(file_v.map_to, list):
278 pipeline_v.map_to.extend(file_v.map_to)
279 elif isinstance(pipeline_v.map_to, str) and isinstance(file_v.map_to, str):
280 pipeline_v.map_to = [pipeline_v.map_to, file_v.map_to]
281 elif isinstance(pipeline_v.map_to, list) and isinstance(file_v.map_to, str):
282 pipeline_v.map_to.append(file_v.map_to)
283 elif isinstance(pipeline_v.map_to, str) and isinstance(file_v.map_to, list):
284 file_v.map_to.append(pipeline_v.map_to)
285 pipeline_v.map_to = file_v.map_to
286
287 # set other options from the file mapping
288 pipeline_v.is_timestamp = file_v.is_timestamp
289 pipeline_v.event_code = file_v.event_code
290
291 # l.debug("MERGED mappings: %s" % (json.dumps(pipeline_mapping.to_dict(), indent=2)))
292 merged_mapping = pipeline_mapping
293 return merged_mapping
294
295
Here is the call graph for this function:

◆ get_enriched_pipeline()

ProcessingPipeline get_enriched_pipeline ( ProcessingPipeline pipeline = None,
str mapping_file_path = None,
str mapping_id = None,
str product = None,
** kwargs )
Returns an enriched pysigma processing pipeline (base ProcessingPipeline mapping + file mapping) to be used to convert SIGMA RULES to ELASTICSEARCH DSL QUERY.

NOTE: This is to be used solely by the SIGMA plugins.

Args:
    pipeline (ProcessingPipeline): optional, the base processing pipeline to enrich (default: None, empty pipeline)
    mapping_file_path (str): optional, the path to the mapping file to load mappings from (default: None)
    mapping_id (str): optional, the mapping id to retrieve from the mapping file (default: None, first mapping only)
    product (str): optional, the product name to set in the resulting pipeline LogSourceCondition array (default: None, inferred from the file name if mapping_file_path is provided: /path/to/product.json -> product)
    kwargs: additional keyword arguments
Returns:
    ProcessingPipeline: The enriched processing pipeline.
    if no mapping_file_path, the original pipeline (or an empty pipeline if base is None) is returned.

Definition at line 296 of file helpers.py.

302) -> ProcessingPipeline:
303 """
304 Returns an enriched pysigma processing pipeline (base ProcessingPipeline mapping + file mapping) to be used to convert SIGMA RULES to ELASTICSEARCH DSL QUERY.
305
306 NOTE: This is to be used solely by the SIGMA plugins.
307
308 Args:
309 pipeline (ProcessingPipeline): optional, the base processing pipeline to enrich (default: None, empty pipeline)
310 mapping_file_path (str): optional, the path to the mapping file to load mappings from (default: None)
311 mapping_id (str): optional, the mapping id to retrieve from the mapping file (default: None, first mapping only)
312 product (str): optional, the product name to set in the resulting pipeline LogSourceCondition array (default: None, inferred from the file name if mapping_file_path is provided: /path/to/product.json -> product)
313 kwargs: additional keyword arguments
314 Returns:
315 ProcessingPipeline: The enriched processing pipeline.
316 if no mapping_file_path, the original pipeline (or an empty pipeline if base is None) is returned.
317 """
318 l = _logger
319 if l is None:
320 l = logging.getLogger()
321
322 if pipeline is None:
323 # use default pipeline as base
324 l.debug("no pipeline provided, using empty pipeline.")
325 pipeline = ProcessingPipeline()
326
327 if mapping_file_path is None:
328 # no file mapping provided, return the original pipeline
329 l.debug("no file mapping provided, using just the provided pipeline.")
330 return pipeline
331
332 try:
333 mapping = await get_mapping_from_file(mapping_file_path, mapping_id=mapping_id)
334 product = mapping_file_path.split("/")[-1].split(".")[0]
335 except:
336 l.exception("error loading mapping file: %s" % (mapping_file_path))
337 return pipeline
338
339 # enrich pipeline
340
341 # collect standard (single or multiple string) and variable mapping from FILE
342 std_mapping = {}
343 var_mapping = {}
344 for k, v in mapping.fields.items():
345 vv: FieldMappingEntry = v
346 if isinstance(vv.map_to, str):
347 # single, map k to vv.map_to
348 std_mapping[k] = [vv.map_to]
349 elif isinstance(vv.map_to, list):
350 if vv.is_variable_mapping:
351 # variable mapping, map k to vv.map_to which is a list of lists [logsource_field_name, logsource, mapped_field]
352 var_mapping[k] = vv.map_to
353 else:
354 # multiple, map k to vv.map_to which is a list of string
355 std_mapping[k] = vv.map_to
356
357 p_items: list[ProcessingItem] = []
358
359 # create processing items for each std_mapping
360 if len(std_mapping) > 0:
361 # use product only for rule conditions
362 rule_conditions: list[LogsourceCondition] = []
363 if product is not None:
364 rule_conditions = [LogsourceCondition(product=product)]
365
366 for k, v in std_mapping.items():
367 p = ProcessingItem(
368 identifier="gulp-field_mapping",
369 transformation=FieldMappingTransformation(std_mapping),
370 rule_conditions=rule_conditions,
371 )
372 p_items.append(p)
373
374 # create processing items for each variable mapping
375 if len(var_mapping) > 0:
376 # we will use the both product and logsource field/name for rule conditions
377 for k, v in var_mapping.items():
378 for m in v:
379 logsrc_field = m[0]
380 logsrc = m[1]
381 mapped = m[2]
382 p = ProcessingItem(
383 identifier="gulp-variable_field_mapping-%s-%s-%s"
384 % (k, logsrc_field, logsrc),
385 transformation=FieldMappingTransformation({k: mapped}),
386 rule_conditions=[
387 LogsourceCondition(
388 **{
389 "product": product,
390 logsrc_field: logsrc,
391 }
392 ),
393 ],
394 )
395 p_items.append(p)
396
397 # return the extended pipeline
398 pipeline.items.extend(p_items)
399 return pipeline
Here is the call graph for this function:

◆ get_mapping_from_file()

GulpMapping get_mapping_from_file ( str mapping_file_path,
str mapping_id = None )
    Retrieve the mapping from a file.

Args:
    mapping_file_path (str): The path to the mapping file.
    mapping_id (str): The mapping id to retrieve from the file (default: None, return first mapping only).

Returns:
    GulpMapping: the mapping

Raises:
    FileNotFoundError: if the mapping file does not exist
    ValueError: if the mapping_id is not found in the mapping file

NOTE: the following is an example of the mapping file format to indicate different mapping options and styles (single, multiple, variable mapping)

~~~json
    {
// an array of mapppings and related options
"mappings": [{
        "fields": {
            // source field
            "EntryNumber": {
                // map to single string
                "map_to": "event.sequence"
            }
        },
        "options": {
            // set "event.code" to "record" if this mapping is used
            "event_code": "record",
            // set "agent.type" to "mftecmd" if this mapping is used
            "agent_type": "mftecmd",
            // this is to identify this mapping in the whole file
            "mapping_id": "record"
        }
    },
    {
        "fields": {
            "SourceFile": {
                // multiple mapping
                // SourceFile will be mapped to both "file.path" and "file.name"
                "map_to": ["file.path", "file.name"]
            }
        },
        "options": {
            "mapping_id": "boot"
        }
    },
    {
        "fields": {
            "Name": {
                // variable mapping
                // Name will be mapped to "user.name" and "service.name"
                "map_to": [
                    ["service", "security", "user.name"],
                    ["service", "security", "service.name"]
                ],
                "is_variable_mapping": true
            }
        },
        "options": {
            "mapping_id": "j"
        }
    }
]}
~~~

Definition at line 81 of file helpers.py.

83) -> GulpMapping:
84 """
85 Retrieve the mapping from a file.
86
87 Args:
88 mapping_file_path (str): The path to the mapping file.
89 mapping_id (str): The mapping id to retrieve from the file (default: None, return first mapping only).
90
91 Returns:
92 GulpMapping: the mapping
93
94 Raises:
95 FileNotFoundError: if the mapping file does not exist
96 ValueError: if the mapping_id is not found in the mapping file
97
98 NOTE: the following is an example of the mapping file format to indicate different mapping options and styles (single, multiple, variable mapping)
99
100 ~~~json
101 {
102 // an array of mapppings and related options
103 "mappings": [{
104 "fields": {
105 // source field
106 "EntryNumber": {
107 // map to single string
108 "map_to": "event.sequence"
109 }
110 },
111 "options": {
112 // set "event.code" to "record" if this mapping is used
113 "event_code": "record",
114 // set "agent.type" to "mftecmd" if this mapping is used
115 "agent_type": "mftecmd",
116 // this is to identify this mapping in the whole file
117 "mapping_id": "record"
118 }
119 },
120 {
121 "fields": {
122 "SourceFile": {
123 // multiple mapping
124 // SourceFile will be mapped to both "file.path" and "file.name"
125 "map_to": ["file.path", "file.name"]
126 }
127 },
128 "options": {
129 "mapping_id": "boot"
130 }
131 },
132 {
133 "fields": {
134 "Name": {
135 // variable mapping
136 // Name will be mapped to "user.name" and "service.name"
137 "map_to": [
138 ["service", "security", "user.name"],
139 ["service", "security", "service.name"]
140 ],
141 "is_variable_mapping": true
142 }
143 },
144 "options": {
145 "mapping_id": "j"
146 }
147 }
148 ]}
149 ~~~
150 """
151 l = _logger
152 if l is None:
153 l = logging.getLogger()
154
155 mappings = await _get_mappings_internal(mapping_file_path)
156 if mapping_id is None:
157 l.warning("no mapping_id set, returning first element: %s" % (mappings[0]))
158 m = GulpMapping.from_dict(mappings[0])
159 return m
160
161 # get specific mapping
162 for m in mappings:
163 options = m.get("options", None)
164 if options is not None:
165 if options.get("mapping_id", None) == mapping_id:
166 l.debug("mapping found for mapping_id=%s: %s" % (mapping_id, m))
167 return GulpMapping.from_dict(m)
168 raise ValueError("mapping_id not found in the mapping file: %s" % (mapping_id))
169
170
Here is the call graph for this function:
Here is the caller graph for this function:

◆ get_mappings_from_file()

list[GulpMapping] get_mappings_from_file ( str mapping_file_path)
    Retrieve all mappings from a file.

Args:
    mapping_file_path (str): The path to the mapping file.

Returns:
    list[GulpMapping]: the mappings

Raises:
    FileNotFoundError: if the mapping file does not exist
    ValueError: if no mapping_id are found

Definition at line 54 of file helpers.py.

54async def get_mappings_from_file(mapping_file_path:str) -> list[GulpMapping]:
55 """
56 Retrieve all mappings from a file.
57
58 Args:
59 mapping_file_path (str): The path to the mapping file.
60
61 Returns:
62 list[GulpMapping]: the mappings
63
64 Raises:
65 FileNotFoundError: if the mapping file does not exist
66 ValueError: if no mapping_id are found
67 """
68 mappings=[]
69
70 l = _logger
71 if l is None:
72 l = logging.getLogger()
73
74 maps = await _get_mappings_internal(mapping_file_path)
75
76 for mapping in maps:
77 mappings.append(GulpMapping.from_dict(mapping))
78
79 return mappings
80
Here is the call graph for this function:

◆ init()

init ( logging.Logger logger)
Initializes the helper module

Args:
    logger (logging.Logger): The logger instance.

Definition at line 14 of file helpers.py.

14def init(logger: logging.Logger):
15 """
16 Initializes the helper module
17
18 Args:
19 logger (logging.Logger): The logger instance.
20
21 """
22 global _logger
23 _logger = logger
24

Variable Documentation

◆ _logger

logging _logger = None
protected

Definition at line 11 of file helpers.py.