stix-shifter-modules-sysdig 8.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stix_shifter_modules/sysdig/__init__.py +0 -0
- stix_shifter_modules/sysdig/configuration/config.json +603 -0
- stix_shifter_modules/sysdig/configuration/dialects.json +6 -0
- stix_shifter_modules/sysdig/configuration/lang_en.json +69 -0
- stix_shifter_modules/sysdig/entry_point.py +12 -0
- stix_shifter_modules/sysdig/stix_translation/__init__.py +0 -0
- stix_shifter_modules/sysdig/stix_translation/json/config_map.json +33 -0
- stix_shifter_modules/sysdig/stix_translation/json/from_stix_map.json +110 -0
- stix_shifter_modules/sysdig/stix_translation/json/operators.json +13 -0
- stix_shifter_modules/sysdig/stix_translation/json/stix_2_1/from_stix_map.json +110 -0
- stix_shifter_modules/sysdig/stix_translation/json/stix_2_1/to_stix_map.json +332 -0
- stix_shifter_modules/sysdig/stix_translation/json/to_stix_map.json +332 -0
- stix_shifter_modules/sysdig/stix_translation/json_to_stix_translator.py +529 -0
- stix_shifter_modules/sysdig/stix_translation/query_constructor.py +472 -0
- stix_shifter_modules/sysdig/stix_translation/query_translator.py +26 -0
- stix_shifter_modules/sysdig/stix_translation/transformers.py +66 -0
- stix_shifter_modules/sysdig/stix_transmission/__init__.py +0 -0
- stix_shifter_modules/sysdig/stix_transmission/api_client.py +37 -0
- stix_shifter_modules/sysdig/stix_transmission/connector.py +213 -0
- stix_shifter_modules/sysdig/stix_transmission/error_mapper.py +34 -0
- stix_shifter_modules_sysdig-8.0.2.dist-info/METADATA +148 -0
- stix_shifter_modules_sysdig-8.0.2.dist-info/RECORD +27 -0
- stix_shifter_modules_sysdig-8.0.2.dist-info/WHEEL +5 -0
- stix_shifter_modules_sysdig-8.0.2.dist-info/licenses/AUTHORS.md +23 -0
- stix_shifter_modules_sysdig-8.0.2.dist-info/licenses/LICENSE.md +219 -0
- stix_shifter_modules_sysdig-8.0.2.dist-info/licenses/NOTICE +32 -0
- stix_shifter_modules_sysdig-8.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
""" This Module will convert Stix Pattern to Sysdig data source supported query """
|
|
2
|
+
import re
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from os import path
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
from stix_shifter_utils.stix_translation.src.patterns.pattern_objects import ObservationExpression, \
|
|
8
|
+
ComparisonExpression, ComparisonComparators, Pattern, \
|
|
9
|
+
CombinedComparisonExpression, CombinedObservationExpression
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
START_STOP_PATTERN = r"(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z)"
|
|
14
|
+
MAC = '^(([0-9a-fA-F]{2}[:-]){5}([0-9a-fA-F]{2}))$'
|
|
15
|
+
TYPE_MAP_PATH = "json/config_map.json"
|
|
16
|
+
|
|
17
|
+
STOP_TIME = datetime.utcnow()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class StartStopQualifierValueException(Exception):
|
|
21
|
+
""" Start Stop qualifier exception """
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class FileNotFoundException(Exception):
|
|
26
|
+
""" file not found exception """
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class QueryStringPatternTranslator:
|
|
31
|
+
"""
|
|
32
|
+
translate stix pattern to native data source query language
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, pattern: Pattern, data_model_mapper, options):
|
|
36
|
+
logger.info("Sysdig Connector")
|
|
37
|
+
self.dmm = data_model_mapper
|
|
38
|
+
self.options = options
|
|
39
|
+
self.comparator_lookup = self.dmm.map_comparator()
|
|
40
|
+
self.type_map = self.load_json(TYPE_MAP_PATH)
|
|
41
|
+
self.translated_query = self.parse_expression(pattern)
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def load_json(rel_path_of_file):
|
|
45
|
+
"""
|
|
46
|
+
Consumes a json file and returns a dictionary
|
|
47
|
+
:param rel_path_of_file: str
|
|
48
|
+
:return: dict
|
|
49
|
+
"""
|
|
50
|
+
_json_path = path.dirname(path.realpath(__file__)) + "/" + rel_path_of_file
|
|
51
|
+
try:
|
|
52
|
+
if path.exists(_json_path):
|
|
53
|
+
with open(_json_path, encoding='utf-8') as f_obj:
|
|
54
|
+
return json.load(f_obj)
|
|
55
|
+
raise FileNotFoundException
|
|
56
|
+
except FileNotFoundException as ex:
|
|
57
|
+
raise FileNotFoundError(f'{rel_path_of_file} not found') from ex
|
|
58
|
+
|
|
59
|
+
def _format_set(self, values, mapped_field_type, mapped_fields_array):
|
|
60
|
+
"""
|
|
61
|
+
Formats value in the event of set operation
|
|
62
|
+
:param values
|
|
63
|
+
:param mapped_field_type: str
|
|
64
|
+
:param mapped_fields_array: list
|
|
65
|
+
:return formatted value
|
|
66
|
+
"""
|
|
67
|
+
gen = values.element_iterator()
|
|
68
|
+
formatted_value = ','.join(QueryStringPatternTranslator._escape_value(
|
|
69
|
+
self._format_value_type(value, mapped_field_type, mapped_fields_array), mapped_field_type)
|
|
70
|
+
for value in gen)
|
|
71
|
+
return f'({formatted_value})'
|
|
72
|
+
|
|
73
|
+
@staticmethod
|
|
74
|
+
def _format_equality(value, mapped_field_type):
|
|
75
|
+
"""
|
|
76
|
+
Formats value in the event of equality operation
|
|
77
|
+
:param value
|
|
78
|
+
:return formatted value
|
|
79
|
+
"""
|
|
80
|
+
return QueryStringPatternTranslator._escape_value(value, mapped_field_type)
|
|
81
|
+
|
|
82
|
+
@staticmethod
|
|
83
|
+
def _escape_value(value, mapped_field_type):
|
|
84
|
+
"""
|
|
85
|
+
adds escape characters to string type value
|
|
86
|
+
:param value
|
|
87
|
+
:return formatted value
|
|
88
|
+
"""
|
|
89
|
+
if mapped_field_type == "int":
|
|
90
|
+
return value
|
|
91
|
+
if isinstance(value, str):
|
|
92
|
+
value = f'\"{value}\"'
|
|
93
|
+
return str(value)
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def _field_severity(value):
|
|
97
|
+
"""
|
|
98
|
+
convert severity 1-100 to data source value 0-7
|
|
99
|
+
High: 0-3, Medium: 4-5, Low: 6, Info: 7
|
|
100
|
+
param value
|
|
101
|
+
return value(int)
|
|
102
|
+
examples:
|
|
103
|
+
severity is 100, after conversion is 0.
|
|
104
|
+
severity is 30, after conversion is 7.
|
|
105
|
+
Reference :
|
|
106
|
+
Sysdig Docs https://docs.sysdig.com/en/docs/sysdig-secure/secure-events/event-forwarding/#policy-event-severity
|
|
107
|
+
"""
|
|
108
|
+
value = int(value)
|
|
109
|
+
if 91 <= value <= 100:
|
|
110
|
+
value = 0
|
|
111
|
+
elif 81 <= value <= 90:
|
|
112
|
+
value = 1
|
|
113
|
+
elif 71 <= value <= 80:
|
|
114
|
+
value = 2
|
|
115
|
+
elif 61 <= value <= 70:
|
|
116
|
+
value = 3
|
|
117
|
+
elif 51 <= value <= 60:
|
|
118
|
+
value = 4
|
|
119
|
+
elif 41 <= value <= 50:
|
|
120
|
+
value = 5
|
|
121
|
+
elif 31 <= value <= 40:
|
|
122
|
+
value = 6
|
|
123
|
+
elif 1 <= value <= 30:
|
|
124
|
+
value = 7
|
|
125
|
+
else:
|
|
126
|
+
raise NotImplementedError('only 1-100 integer values are supported with this field')
|
|
127
|
+
return value
|
|
128
|
+
|
|
129
|
+
def _format_value_type(self, value, mapped_field_type, mapped_fields_array):
|
|
130
|
+
"""
|
|
131
|
+
check input value format that matches with the mapped field value type
|
|
132
|
+
:param value
|
|
133
|
+
:param mapped_field_type: str
|
|
134
|
+
:param mapped_fields_array: list
|
|
135
|
+
:return formatted value
|
|
136
|
+
"""
|
|
137
|
+
converted_value = str(value)
|
|
138
|
+
if mapped_field_type == "mac":
|
|
139
|
+
compile_mac_regex = re.compile(MAC)
|
|
140
|
+
if not compile_mac_regex.search(converted_value):
|
|
141
|
+
raise NotImplementedError(f'Invalid mac address - {converted_value} provided')
|
|
142
|
+
elif mapped_field_type == "int":
|
|
143
|
+
if not converted_value.isdigit():
|
|
144
|
+
raise NotImplementedError(f'string type input - {converted_value} is not supported for '
|
|
145
|
+
f'integer type fields')
|
|
146
|
+
if mapped_fields_array[0] == "severity":
|
|
147
|
+
value = self._field_severity(value)
|
|
148
|
+
converted_value = str(value)
|
|
149
|
+
return converted_value
|
|
150
|
+
|
|
151
|
+
def _check_value_comparator_support(self, comparator, mapped_field_type):
|
|
152
|
+
"""
|
|
153
|
+
checks the comparator and value support
|
|
154
|
+
:param comparator
|
|
155
|
+
:param mapped_field_type: str
|
|
156
|
+
"""
|
|
157
|
+
operator = self.comparator_lookup[str(comparator)]
|
|
158
|
+
|
|
159
|
+
if mapped_field_type == 'string' and comparator not in [ComparisonComparators.In,
|
|
160
|
+
ComparisonComparators.Equal,
|
|
161
|
+
ComparisonComparators.NotEqual
|
|
162
|
+
]:
|
|
163
|
+
raise NotImplementedError(f'{operator} operator is not supported for string type input')
|
|
164
|
+
if mapped_field_type == 'int' and comparator not in [ComparisonComparators.In,
|
|
165
|
+
ComparisonComparators.Equal,
|
|
166
|
+
ComparisonComparators.NotEqual,
|
|
167
|
+
ComparisonComparators.GreaterThan,
|
|
168
|
+
ComparisonComparators.GreaterThanOrEqual,
|
|
169
|
+
ComparisonComparators.LessThanOrEqual,
|
|
170
|
+
ComparisonComparators.LessThan]:
|
|
171
|
+
raise NotImplementedError(f'{operator} operator is not supported for integer type input')
|
|
172
|
+
|
|
173
|
+
@staticmethod
|
|
174
|
+
def _format_negate(comparator):
|
|
175
|
+
"""
|
|
176
|
+
returns negation of input operator
|
|
177
|
+
:param comparator:str
|
|
178
|
+
:return str
|
|
179
|
+
"""
|
|
180
|
+
negate_comparator = {
|
|
181
|
+
">": "<=",
|
|
182
|
+
">=": "<",
|
|
183
|
+
"<": ">=",
|
|
184
|
+
"<=": ">",
|
|
185
|
+
"=": "!=",
|
|
186
|
+
"in": "not in"
|
|
187
|
+
}
|
|
188
|
+
return negate_comparator[comparator]
|
|
189
|
+
|
|
190
|
+
@staticmethod
|
|
191
|
+
def _check_time_range_values(from_epoch, to_epoch):
|
|
192
|
+
"""
|
|
193
|
+
checks for valid start and stop time
|
|
194
|
+
:param from_epoch: int
|
|
195
|
+
:param to_epoch: int
|
|
196
|
+
"""
|
|
197
|
+
if from_epoch > to_epoch:
|
|
198
|
+
raise StartStopQualifierValueException('Start time should be lesser than Stop time')
|
|
199
|
+
|
|
200
|
+
no_of_days = (to_epoch - from_epoch)/86400000000000 # converting nano seconds to days
|
|
201
|
+
max_day = 14 # time range maximum up to 14 days
|
|
202
|
+
if max_day < no_of_days:
|
|
203
|
+
logger.warning('Sysdig connector query time range can be maximum up to 14 days')
|
|
204
|
+
|
|
205
|
+
@staticmethod
|
|
206
|
+
def _parse_time_range(qualifier, time_range):
|
|
207
|
+
"""
|
|
208
|
+
Converts qualifier timestamp to epoch
|
|
209
|
+
:param qualifier: str
|
|
210
|
+
:param time_range: int
|
|
211
|
+
return: list of converted epoch values
|
|
212
|
+
"""
|
|
213
|
+
try:
|
|
214
|
+
compile_timestamp_regex = re.compile(START_STOP_PATTERN)
|
|
215
|
+
if qualifier and compile_timestamp_regex.search(qualifier):
|
|
216
|
+
time_range_iterator = compile_timestamp_regex.finditer(qualifier)
|
|
217
|
+
time_range_list = [each.group() for each in time_range_iterator]
|
|
218
|
+
else:
|
|
219
|
+
start_time = STOP_TIME - timedelta(minutes=time_range)
|
|
220
|
+
converted_start_time = start_time.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
|
|
221
|
+
# limit 3 digit value for millisecond
|
|
222
|
+
converted_stop_time = STOP_TIME.strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z'
|
|
223
|
+
time_range_list = [converted_start_time, converted_stop_time]
|
|
224
|
+
except (KeyError, IndexError, TypeError) as ex:
|
|
225
|
+
raise ex
|
|
226
|
+
return time_range_list
|
|
227
|
+
|
|
228
|
+
@staticmethod
|
|
229
|
+
def get_epoch_time(timestamp):
|
|
230
|
+
"""
|
|
231
|
+
Converting timestamp (YYYY-MM-DDThh:mm:ss.000Z) to 13-digit Unix time (epoch + milliseconds)
|
|
232
|
+
:param timestamp: str, timestamp
|
|
233
|
+
:return: int, epoch time
|
|
234
|
+
"""
|
|
235
|
+
time_patterns = ['%Y-%m-%dT%H:%M:%SZ', '%Y-%m-%dT%H:%M:%S.%fZ']
|
|
236
|
+
epoch = datetime(1970, 1, 1)
|
|
237
|
+
for time_pattern in time_patterns:
|
|
238
|
+
try:
|
|
239
|
+
converted_time = int(
|
|
240
|
+
((datetime.strptime(timestamp, time_pattern) - epoch).total_seconds()) * 1000 * 1000000)
|
|
241
|
+
return converted_time
|
|
242
|
+
except ValueError:
|
|
243
|
+
pass
|
|
244
|
+
raise NotImplementedError("cannot convert the timestamp {} to epoch time".format(timestamp))
|
|
245
|
+
|
|
246
|
+
def _add_timestamp_to_query(self, query, qualifier):
|
|
247
|
+
"""
|
|
248
|
+
adds timestamp filter to Sysdig query
|
|
249
|
+
:param query: str
|
|
250
|
+
:param qualifier
|
|
251
|
+
:return str
|
|
252
|
+
"""
|
|
253
|
+
converted_timestamp = \
|
|
254
|
+
QueryStringPatternTranslator._parse_time_range(qualifier, self.options["time_range"])
|
|
255
|
+
from_epoch = self.get_epoch_time(converted_timestamp[0])
|
|
256
|
+
to_epoch = self.get_epoch_time(converted_timestamp[1])
|
|
257
|
+
QueryStringPatternTranslator._check_time_range_values(from_epoch, to_epoch)
|
|
258
|
+
final_query = f'from={from_epoch}&to={to_epoch}&filter={query}'
|
|
259
|
+
return final_query
|
|
260
|
+
|
|
261
|
+
def _check_mapped_field_type(self, mapped_field_array):
|
|
262
|
+
"""
|
|
263
|
+
Returns the type of mapped field array
|
|
264
|
+
:param mapped_field_array: list
|
|
265
|
+
:return: str
|
|
266
|
+
"""
|
|
267
|
+
mapped_field = mapped_field_array[0]
|
|
268
|
+
mapped_field_type = "string"
|
|
269
|
+
for key, value in self.type_map.items():
|
|
270
|
+
if mapped_field in value and key in ["int_supported_fields", "string_supported_fields",
|
|
271
|
+
"mac_supported_fields"]:
|
|
272
|
+
mapped_field_type = key.split('_')[0]
|
|
273
|
+
break
|
|
274
|
+
return mapped_field_type
|
|
275
|
+
|
|
276
|
+
@staticmethod
|
|
277
|
+
def _parse_mapped_fields(value, comparator, mapped_fields_array):
|
|
278
|
+
"""
|
|
279
|
+
parse mapped fields into query expression
|
|
280
|
+
:param value: str
|
|
281
|
+
:param comparator: str
|
|
282
|
+
:param mapped_fields_array: list
|
|
283
|
+
:return: str
|
|
284
|
+
"""
|
|
285
|
+
comparison_string = ""
|
|
286
|
+
mapped_fields_count = len(mapped_fields_array)
|
|
287
|
+
for mapped_field in mapped_fields_array:
|
|
288
|
+
if "in" in comparator:
|
|
289
|
+
comparison_string += f'{mapped_field} {comparator} {value}'
|
|
290
|
+
else:
|
|
291
|
+
comparison_string += f'{mapped_field}{comparator}{value}'
|
|
292
|
+
if mapped_fields_count > 1:
|
|
293
|
+
comparison_string += " OR "
|
|
294
|
+
mapped_fields_count -= 1
|
|
295
|
+
return comparison_string
|
|
296
|
+
|
|
297
|
+
def _lookup_comparison_operator(self, expression_operator):
|
|
298
|
+
"""
|
|
299
|
+
lookup operators support in Sysdig connector
|
|
300
|
+
:param expression_operator:enum object
|
|
301
|
+
:return str
|
|
302
|
+
"""
|
|
303
|
+
if str(expression_operator) not in self.comparator_lookup:
|
|
304
|
+
raise NotImplementedError(
|
|
305
|
+
f'Comparison operator {expression_operator.name} unsupported for Sysdig connector')
|
|
306
|
+
operator = self.comparator_lookup[str(expression_operator)]
|
|
307
|
+
return operator
|
|
308
|
+
|
|
309
|
+
def _eval_comparison_value(self, expression, mapped_field_type, mapped_fields_array):
|
|
310
|
+
"""
|
|
311
|
+
Function for parsing comparison expression value
|
|
312
|
+
:param expression: expression object
|
|
313
|
+
:param mapped_field_type:str
|
|
314
|
+
:param mapped_fields_array: list
|
|
315
|
+
:return: formatted expression value
|
|
316
|
+
"""
|
|
317
|
+
if expression.comparator == ComparisonComparators.In:
|
|
318
|
+
value = self._format_set(expression.value, mapped_field_type, mapped_fields_array)
|
|
319
|
+
elif expression.comparator not in [ComparisonComparators.Like, ComparisonComparators.Matches]:
|
|
320
|
+
value = self._format_value_type(expression.value, mapped_field_type, mapped_fields_array)
|
|
321
|
+
self._check_value_comparator_support(expression.comparator, mapped_field_type)
|
|
322
|
+
value = self._format_equality(value, mapped_field_type)
|
|
323
|
+
else:
|
|
324
|
+
raise NotImplementedError('Unknown comparator expression operator')
|
|
325
|
+
return value
|
|
326
|
+
|
|
327
|
+
def _eval_combined_comparison_exp(self, expression):
|
|
328
|
+
"""
|
|
329
|
+
Function for parsing combined comparison expression
|
|
330
|
+
:param expression: expression object
|
|
331
|
+
"""
|
|
332
|
+
operator = self._lookup_comparison_operator(expression.operator)
|
|
333
|
+
expression_01 = self._parse_expression(expression.expr1)
|
|
334
|
+
expression_02 = self._parse_expression(expression.expr2)
|
|
335
|
+
if not expression_01 or not expression_02:
|
|
336
|
+
return ''
|
|
337
|
+
if isinstance(expression.expr1, CombinedComparisonExpression):
|
|
338
|
+
expression_01 = f'{expression_01}'
|
|
339
|
+
if isinstance(expression.expr2, CombinedComparisonExpression):
|
|
340
|
+
expression_02 = f'{expression_02}'
|
|
341
|
+
|
|
342
|
+
query_string = f'{expression_01}{operator}{expression_02}'
|
|
343
|
+
return f'{query_string}'
|
|
344
|
+
|
|
345
|
+
def _eval_combined_observation_exp(self, expression, qualifier=None):
|
|
346
|
+
"""
|
|
347
|
+
Function for parsing combined observation expression
|
|
348
|
+
:param expression: expression object
|
|
349
|
+
:param qualifier: qualifier
|
|
350
|
+
"""
|
|
351
|
+
operator = self._lookup_comparison_operator(expression.operator)
|
|
352
|
+
expression_01 = self._parse_expression(expression.expr1, qualifier)
|
|
353
|
+
expression_02 = self._parse_expression(expression.expr2, qualifier)
|
|
354
|
+
query = ''
|
|
355
|
+
if expression_01 and expression_02:
|
|
356
|
+
query = f'{expression_01} {operator} {expression_02}'
|
|
357
|
+
|
|
358
|
+
elif expression_01:
|
|
359
|
+
query = f'{expression_01}'
|
|
360
|
+
elif expression_02:
|
|
361
|
+
query = f'{expression_02}'
|
|
362
|
+
return query
|
|
363
|
+
|
|
364
|
+
def _parse_expression(self, expression, qualifier=None):
|
|
365
|
+
"""
|
|
366
|
+
parse ANTLR pattern to Sysdig native query
|
|
367
|
+
:param expression: expression object, ANTLR parsed expression object
|
|
368
|
+
:param qualifier: str, default in None
|
|
369
|
+
:return str
|
|
370
|
+
"""
|
|
371
|
+
if isinstance(expression, ComparisonExpression): # Base Case
|
|
372
|
+
stix_objects = expression.object_path.split(':')
|
|
373
|
+
mapped_fields_array = self.dmm.map_field(stix_objects[0], stix_objects[1])
|
|
374
|
+
comparator = self._lookup_comparison_operator(expression.comparator)
|
|
375
|
+
if expression.negated:
|
|
376
|
+
comparator = QueryStringPatternTranslator._format_negate(comparator)
|
|
377
|
+
mapped_field_type = self._check_mapped_field_type(mapped_fields_array)
|
|
378
|
+
value = self._eval_comparison_value(expression, mapped_field_type, mapped_fields_array)
|
|
379
|
+
comparison_string = self._parse_mapped_fields(value, comparator, mapped_fields_array)
|
|
380
|
+
return comparison_string
|
|
381
|
+
|
|
382
|
+
elif isinstance(expression, CombinedComparisonExpression):
|
|
383
|
+
return self._eval_combined_comparison_exp(expression)
|
|
384
|
+
|
|
385
|
+
elif isinstance(expression, ObservationExpression):
|
|
386
|
+
query_string = self._parse_expression(expression.comparison_expression)
|
|
387
|
+
query_string = self._add_timestamp_to_query(query_string, qualifier)
|
|
388
|
+
return query_string
|
|
389
|
+
|
|
390
|
+
elif hasattr(expression, 'qualifier') and hasattr(expression, 'observation_expression'):
|
|
391
|
+
if isinstance(expression.observation_expression, CombinedObservationExpression):
|
|
392
|
+
operator = self._lookup_comparison_operator(expression.observation_expression.operator)
|
|
393
|
+
expression_01 = self._parse_expression(expression.observation_expression.expr1,
|
|
394
|
+
expression.qualifier)
|
|
395
|
+
expression_02 = self._parse_expression(expression.observation_expression.expr2,
|
|
396
|
+
expression.qualifier)
|
|
397
|
+
query_string = f'{expression_01} {operator} {expression_02}'
|
|
398
|
+
else:
|
|
399
|
+
query_string = self._parse_expression(expression.observation_expression,
|
|
400
|
+
expression.qualifier)
|
|
401
|
+
if qualifier is not None:
|
|
402
|
+
query_string = self._add_timestamp_to_query(query_string, qualifier)
|
|
403
|
+
return query_string
|
|
404
|
+
|
|
405
|
+
elif isinstance(expression, CombinedObservationExpression):
|
|
406
|
+
|
|
407
|
+
return self._eval_combined_observation_exp(expression, qualifier)
|
|
408
|
+
|
|
409
|
+
elif isinstance(expression, Pattern):
|
|
410
|
+
return self._parse_expression(expression.expression)
|
|
411
|
+
else:
|
|
412
|
+
raise RuntimeError(f'Unknown Recursion Case for expression={expression},'
|
|
413
|
+
f' type(expression)={type(expression)}')
|
|
414
|
+
|
|
415
|
+
def parse_expression(self, pattern: Pattern):
|
|
416
|
+
"""
|
|
417
|
+
Conversion of ANTLR pattern to Sysdig query
|
|
418
|
+
:param pattern: expression object, ANTLR parsed expression object
|
|
419
|
+
:return: str, native query
|
|
420
|
+
"""
|
|
421
|
+
return self._parse_expression(pattern)
|
|
422
|
+
|
|
423
|
+
@staticmethod
|
|
424
|
+
def format_multiple_observations(query):
|
|
425
|
+
"""
|
|
426
|
+
timestamp formatting for multiple observations
|
|
427
|
+
"""
|
|
428
|
+
split_queries = query.split(' or ')
|
|
429
|
+
query_dict = {}
|
|
430
|
+
for row in split_queries:
|
|
431
|
+
# making single time stamp instead of having multiple same time stamps for multiple queries
|
|
432
|
+
split_query = row.split('filter=')
|
|
433
|
+
query_timestamp = split_query[0]
|
|
434
|
+
if query_dict.get(query_timestamp):
|
|
435
|
+
query_dict[query_timestamp] += " or " + split_query[1]
|
|
436
|
+
else:
|
|
437
|
+
query_dict[query_timestamp] = 'filter=' + split_query[1]
|
|
438
|
+
final_query = [key + value for key, value in query_dict.items()]
|
|
439
|
+
return final_query
|
|
440
|
+
|
|
441
|
+
@staticmethod
|
|
442
|
+
def removing_audit_trail_logs(format_queries):
|
|
443
|
+
"""
|
|
444
|
+
Displays error message when searched for auditTrail events.
|
|
445
|
+
Adds the condition to filter auditTrail events for other queries.
|
|
446
|
+
"""
|
|
447
|
+
query_list = []
|
|
448
|
+
remove_audit = "andsource!=\"auditTrail\""
|
|
449
|
+
if any('auditTrail' in query for query in format_queries):
|
|
450
|
+
raise NotImplementedError('Sysdig connector does not provide auditTrail event')
|
|
451
|
+
else:
|
|
452
|
+
for query in format_queries:
|
|
453
|
+
split_format_query = query.split('filter=')
|
|
454
|
+
query_list.append(f'{split_format_query[0]}filter=({split_format_query[1]}){remove_audit}')
|
|
455
|
+
return query_list
|
|
456
|
+
|
|
457
|
+
def translate_pattern(pattern: Pattern, data_model_mapping, options):
|
|
458
|
+
"""
|
|
459
|
+
Conversion of ANTLR pattern to native data source query
|
|
460
|
+
:param pattern: expression object, ANTLR parsed expression object
|
|
461
|
+
:param data_model_mapping: DataMapper object, mapping object obtained by parsing json
|
|
462
|
+
:param options: dict, time_range defaults to 5
|
|
463
|
+
:return: string, Sysdig queries
|
|
464
|
+
"""
|
|
465
|
+
translated_query_strings = QueryStringPatternTranslator(pattern, data_model_mapping, options)
|
|
466
|
+
final_queries = translated_query_strings.translated_query
|
|
467
|
+
|
|
468
|
+
if final_queries.count('from') > 1:
|
|
469
|
+
final_queries = QueryStringPatternTranslator.format_multiple_observations(final_queries)
|
|
470
|
+
return QueryStringPatternTranslator.removing_audit_trail_logs(final_queries)
|
|
471
|
+
else:
|
|
472
|
+
return QueryStringPatternTranslator.removing_audit_trail_logs([final_queries])
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from stix_shifter_utils.modules.base.stix_translation.base_query_translator import BaseQueryTranslator
|
|
4
|
+
from . import query_constructor
|
|
5
|
+
|
|
6
|
+
logger = logging.getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class QueryTranslator(BaseQueryTranslator):
|
|
10
|
+
|
|
11
|
+
def transform_antlr(self, data, antlr_parsing_object):
|
|
12
|
+
"""
|
|
13
|
+
Transforms STIX pattern into a different query format. Based on a mapping file
|
|
14
|
+
:param antlr_parsing_object: Antlr parsing objects for the STIX pattern
|
|
15
|
+
:type antlr_parsing_object: object
|
|
16
|
+
:param mapping: The mapping file path to use as instructions on how to transform the given STIX query into another format. This should default to something if one isn't passed in
|
|
17
|
+
:type mapping: str (filepath)
|
|
18
|
+
:return: transformed query string
|
|
19
|
+
:rtype: str
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
logger.info("Converting STIX2 Pattern to data source query")
|
|
23
|
+
|
|
24
|
+
query_string = query_constructor.translate_pattern(
|
|
25
|
+
antlr_parsing_object, self, self.options)
|
|
26
|
+
return query_string
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from stix_shifter_utils.stix_translation.src.utils.transformers import ValueTransformer
|
|
2
|
+
from stix_shifter_utils.utils import logger
|
|
3
|
+
import re
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
LOGGER = logger.set_logger(__name__)
|
|
7
|
+
connector = __name__.split('.')[1]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TimestampConversion(ValueTransformer):
|
|
11
|
+
""" Convert timezone date to timestamp (YYYY-MM-DDThh:mm:ss.000Z)"""
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def transform(timestamp):
|
|
15
|
+
try:
|
|
16
|
+
# with milliseconds
|
|
17
|
+
if re.search(r"\d{4}(-\d{2}){2} \d{2}(:\d{2}){2}.\d{0,6}\+\d{2}:\d{2}", str(timestamp)):
|
|
18
|
+
converted_date = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S.%f%z")
|
|
19
|
+
timestamp = datetime.strftime(converted_date, "%Y-%m-%dT%H:%M:%S.%fZ")
|
|
20
|
+
# for without milliseconds, setting three zeroes in the millisecond in the converted date
|
|
21
|
+
elif re.search(r"\d{4}(-\d{2}){2} \d{2}(:\d{2}){2}\+\d{2}:\d{2}", str(timestamp)):
|
|
22
|
+
converted_date = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S%z")
|
|
23
|
+
timestamp = datetime.strftime(converted_date, "%Y-%m-%dT%H:%M:%S.%f")[:-3] + 'Z'
|
|
24
|
+
except:
|
|
25
|
+
LOGGER.error(f'{connector} connector error cannot convert this timestamp %s', timestamp)
|
|
26
|
+
return timestamp
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class SeverityToScore(ValueTransformer):
|
|
30
|
+
|
|
31
|
+
@staticmethod
|
|
32
|
+
def transform(severity):
|
|
33
|
+
"""
|
|
34
|
+
sysdig severity values levels 0-7 :
|
|
35
|
+
High: 0-3, Medium: 4-5, Low: 6, Info: 7
|
|
36
|
+
converting sysdig severity values 0-7 to 1-100
|
|
37
|
+
Example :
|
|
38
|
+
High: sysdig severity : 0, after conversion : 100
|
|
39
|
+
Low : sysdig severity : 6, after conversion : 40
|
|
40
|
+
Reference :
|
|
41
|
+
Sysdig Docs https://docs.sysdig.com/en/docs/sysdig-secure/secure-events/event-forwarding/#policy-event-severity
|
|
42
|
+
"""
|
|
43
|
+
try:
|
|
44
|
+
# value transformer to convert severity value on a scale of 1-100
|
|
45
|
+
if isinstance(severity, int):
|
|
46
|
+
return (10 - int(severity)) * 10
|
|
47
|
+
return severity
|
|
48
|
+
except KeyError:
|
|
49
|
+
LOGGER.error(f'{connector} connector error cannot convert severity scale value')
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class HostnameToIpAddress(ValueTransformer):
|
|
53
|
+
"""
|
|
54
|
+
Converts Node Name into IP address
|
|
55
|
+
"""
|
|
56
|
+
@staticmethod
|
|
57
|
+
def transform(address):
|
|
58
|
+
try:
|
|
59
|
+
match = re.search(r'\d+\-\d+\-\d+\-\d+', address)
|
|
60
|
+
if match:
|
|
61
|
+
# Getting the matched IP address
|
|
62
|
+
extracted_ip = match.group()
|
|
63
|
+
return extracted_ip.replace('-', '.')
|
|
64
|
+
return address
|
|
65
|
+
except KeyError:
|
|
66
|
+
LOGGER.error(f'{connector} connector error cannot convert ip value')
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from stix_shifter_utils.stix_transmission.utils.RestApiClientAsync import RestApiClientAsync
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class APIClient:
|
|
5
|
+
PING_ENDPOINT = 'api/v1/secureEvents/status'
|
|
6
|
+
EVENTS_ENDPOINT = 'api/v1/secureEvents?'
|
|
7
|
+
|
|
8
|
+
def __init__(self, connection, configuration):
|
|
9
|
+
self.auth = configuration.get('auth')
|
|
10
|
+
self.result_limit = connection['options'].get("result_limit")
|
|
11
|
+
headers = {}
|
|
12
|
+
if 'token' in self.auth:
|
|
13
|
+
headers['Authorization'] = "Bearer " + self.auth.get('token')
|
|
14
|
+
headers["Accept"] = "application/json"
|
|
15
|
+
headers["Content-Type"] = "application/json;charset=UTF-8"
|
|
16
|
+
# Added self-signed certificate parameter for verification
|
|
17
|
+
self.client = RestApiClientAsync(connection.get('host'),
|
|
18
|
+
connection.get('port', None),
|
|
19
|
+
headers,
|
|
20
|
+
url_modifier_function=None,
|
|
21
|
+
cert_verify=connection.get('selfSignedCert'))
|
|
22
|
+
self.timeout = connection['options'].get('timeout')
|
|
23
|
+
|
|
24
|
+
async def ping_data_source(self):
|
|
25
|
+
"""
|
|
26
|
+
ping the Data Source
|
|
27
|
+
:return: Response object
|
|
28
|
+
"""
|
|
29
|
+
return await self.client.call_api(self.PING_ENDPOINT, 'GET', headers=self.client.headers, timeout=self.timeout)
|
|
30
|
+
|
|
31
|
+
async def get_search_results(self, query):
|
|
32
|
+
"""
|
|
33
|
+
:param query: Data Source Query
|
|
34
|
+
:return: Response Object
|
|
35
|
+
"""
|
|
36
|
+
return await self.client.call_api(self.EVENTS_ENDPOINT, 'GET', urldata=query,
|
|
37
|
+
headers=self.client.headers, timeout=self.timeout)
|