Skip to content

Commit 57ed74e

Browse files
[crowdstrike] feat: migrated create traces to bulk endpoint (#2873)
1 parent 9f74295 commit 57ed74e

File tree

2 files changed

+56
-28
lines changed

2 files changed

+56
-28
lines changed

crowdstrike/crowdstrike/openbas_crowdstrike.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,23 @@ def _fetch_expectations(self, start_time):
7878

7979
# --- MATCHING ---
8080

81-
def _match_expectations(self, alerts: list[Item], expectations):
81+
def _match_expectations(
82+
self, alerts: list[Item], expectations
83+
) -> list[dict[str, str]]:
84+
"""
85+
Match OpenBAS expectations with CS alerts.
86+
87+
Args:
88+
alerts: The list of alerts received from CS.
89+
expectations: The list of OpenBAS expectation to match to CS alerts.
90+
91+
Returns:
92+
* A list of expectation traces that need to be created following all the matches, if any alerts matched any
93+
of the given expectations.
94+
OR
95+
* [], otherwise.
96+
"""
97+
traces_to_create: list[dict[str, str]] = []
8298

8399
self.helper.collector_logger.debug(
84100
"Total expectations returned: " + str(len(expectations))
@@ -131,16 +147,16 @@ def _match_expectations(self, alerts: list[Item], expectations):
131147
)
132148
expectations_not_filled.remove(expectation)
133149

134-
# Send alert to openbas for current matched expectation. Duplicate alerts are handled by openbas itself
150+
# Save alert to openbas for current matched expectation. Duplicate alerts are handled by openbas itself
135151
self.helper.collector_logger.info(
136152
"Expectation matched, adding trace for expectation "
137153
+ expectation["inject_expectation_id"]
138154
+ " ("
139155
+ expectation["inject_expectation_type"]
140156
+ ")"
141157
)
142-
self.helper.api.inject_expectation_trace.create(
143-
data={
158+
traces_to_create.append(
159+
{
144160
"inject_expectation_trace_expectation": expectation[
145161
"inject_expectation_id"
146162
],
@@ -157,6 +173,8 @@ def _match_expectations(self, alerts: list[Item], expectations):
157173
}
158174
)
159175

176+
return traces_to_create
177+
160178
# --- PROCESS ---
161179

162180
def _is_expectation_filled(self, expectation) -> bool:
@@ -172,11 +190,15 @@ def _process(self):
172190
now = datetime.now(pytz.UTC)
173191
start_time = now - timedelta(minutes=self.scanning_delta)
174192

175-
self._match_expectations(
193+
traces = self._match_expectations(
176194
alerts=self.strategy.get_raw_data(start_time),
177195
expectations=self._fetch_expectations(start_time),
178196
)
179197

198+
self.helper.api.inject_expectation_trace.bulk_create(
199+
payload={"expectation_traces": traces}
200+
)
201+
180202
def start(self):
181203
period = self.config.get_conf("collector_period", True, 60)
182204
self.helper.schedule(self._process, period)

crowdstrike/test/test_openbas_crowdstrike.py

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,9 @@
77

88

99
class TestOpenBASCrowdstrike(unittest.TestCase):
10-
@patch("pyobas.apis.InjectExpectationTraceManager.create")
1110
@patch("pyobas.apis.InjectExpectationManager.update")
1211
def test_when_alert_matches_update_prevention_expectation(
13-
self, mock_expectation_update, mock_traces_create
12+
self, mock_expectation_update
1413
):
1514
expected_expectation_id = "expectation_id"
1615
expectations = [
@@ -41,7 +40,7 @@ def test_when_alert_matches_update_prevention_expectation(
4140

4241
collector = get_default_collector(strategy)
4342

44-
collector._match_expectations([Item(**MOCKED_ALERT)], expectations)
43+
traces = collector._match_expectations([Item(**MOCKED_ALERT)], expectations)
4544

4645
mock_expectation_update.assert_called_once_with(
4746
expected_expectation_id,
@@ -52,9 +51,10 @@ def test_when_alert_matches_update_prevention_expectation(
5251
"metadata": {"alertId": expected_expectation_id},
5352
},
5453
)
55-
56-
mock_traces_create.assert_called_once_with(
57-
data={
54+
self.assertEqual(1, len(traces))
55+
self.assertEqual(
56+
traces[0],
57+
{
5858
"inject_expectation_trace_expectation": expected_expectation_id,
5959
"inject_expectation_trace_source_id": collector.config.get_conf(
6060
"collector_id"
@@ -69,10 +69,9 @@ def test_when_alert_matches_update_prevention_expectation(
6969
},
7070
)
7171

72-
@patch("pyobas.apis.InjectExpectationTraceManager.create")
7372
@patch("pyobas.apis.InjectExpectationManager.update")
7473
def test_when_alert_matches_but_not_prevented_update_prevention_expectation(
75-
self, mock_expectation_update, mock_traces_create
74+
self, mock_expectation_update
7675
):
7776
expected_expectation_id = "expectation_id"
7877
expectations = [
@@ -103,7 +102,7 @@ def test_when_alert_matches_but_not_prevented_update_prevention_expectation(
103102

104103
collector = get_default_collector(strategy)
105104

106-
collector._match_expectations([Item(**MOCKED_ALERT)], expectations)
105+
traces = collector._match_expectations([Item(**MOCKED_ALERT)], expectations)
107106

108107
mock_expectation_update.assert_called_once_with(
109108
expected_expectation_id,
@@ -115,8 +114,10 @@ def test_when_alert_matches_but_not_prevented_update_prevention_expectation(
115114
},
116115
)
117116

118-
mock_traces_create.assert_called_once_with(
119-
data={
117+
self.assertEqual(1, len(traces))
118+
self.assertEqual(
119+
traces[0],
120+
{
120121
"inject_expectation_trace_expectation": expected_expectation_id,
121122
"inject_expectation_trace_source_id": collector.config.get_conf(
122123
"collector_id"
@@ -131,10 +132,9 @@ def test_when_alert_matches_but_not_prevented_update_prevention_expectation(
131132
},
132133
)
133134

134-
@patch("pyobas.apis.InjectExpectationTraceManager.create")
135135
@patch("pyobas.apis.InjectExpectationManager.update")
136136
def test_when_alert_matches_update_detection_expectation(
137-
self, mock_expectation_update, mock_traces_create
137+
self, mock_expectation_update
138138
):
139139
expected_expectation_id = "expectation_id"
140140
expectations = [
@@ -165,7 +165,7 @@ def test_when_alert_matches_update_detection_expectation(
165165

166166
collector = get_default_collector(strategy)
167167

168-
collector._match_expectations([Item(**MOCKED_ALERT)], expectations)
168+
traces = collector._match_expectations([Item(**MOCKED_ALERT)], expectations)
169169

170170
mock_expectation_update.assert_called_once_with(
171171
expected_expectation_id,
@@ -177,8 +177,10 @@ def test_when_alert_matches_update_detection_expectation(
177177
},
178178
)
179179

180-
mock_traces_create.assert_called_once_with(
181-
data={
180+
self.assertEqual(1, len(traces))
181+
self.assertEqual(
182+
traces[0],
183+
{
182184
"inject_expectation_trace_expectation": expected_expectation_id,
183185
"inject_expectation_trace_source_id": collector.config.get_conf(
184186
"collector_id"
@@ -193,10 +195,9 @@ def test_when_alert_matches_update_detection_expectation(
193195
},
194196
)
195197

196-
@patch("pyobas.apis.InjectExpectationTraceManager.create")
197198
@patch("pyobas.apis.InjectExpectationManager.update")
198199
def test_when_expectation_has_expected_hostname_signature_ignore_it(
199-
self, mock_expectation_update, mock_traces_create
200+
self, mock_expectation_update
200201
):
201202
expected_expectation_id = "expectation_id"
202203
expectations = [
@@ -228,7 +229,7 @@ def test_when_expectation_has_expected_hostname_signature_ignore_it(
228229

229230
collector = get_default_collector(strategy)
230231

231-
collector._match_expectations([Item(**MOCKED_ALERT)], expectations)
232+
traces = collector._match_expectations([Item(**MOCKED_ALERT)], expectations)
232233

233234
mock_expectation_update.assert_called_once_with(
234235
expected_expectation_id,
@@ -240,8 +241,10 @@ def test_when_expectation_has_expected_hostname_signature_ignore_it(
240241
},
241242
)
242243

243-
mock_traces_create.assert_called_once_with(
244-
data={
244+
self.assertEqual(1, len(traces))
245+
self.assertEqual(
246+
traces[0],
247+
{
245248
"inject_expectation_trace_expectation": expected_expectation_id,
246249
"inject_expectation_trace_source_id": collector.config.get_conf(
247250
"collector_id"
@@ -289,9 +292,10 @@ def test_when_alert_fails_to_match_dont_update_prevention_expectation(
289292

290293
collector = get_default_collector(strategy)
291294

292-
collector._match_expectations([Item(**MOCKED_ALERT)], expectations)
295+
traces = collector._match_expectations([Item(**MOCKED_ALERT)], expectations)
293296

294297
mock_expectation_update.assert_not_called()
298+
self.assertEqual(0, len(traces))
295299

296300
@patch("pyobas.apis.InjectExpectationManager.update")
297301
def test_when_signatures_match_when_unknown_expectation_type_skip_updating_expectation(
@@ -326,10 +330,12 @@ def test_when_signatures_match_when_unknown_expectation_type_skip_updating_expec
326330

327331
collector = get_default_collector(strategy)
328332

329-
collector._match_expectations([Item(**MOCKED_ALERT)], expectations)
333+
traces = collector._match_expectations([Item(**MOCKED_ALERT)], expectations)
330334

331335
mock_expectation_update.assert_not_called()
332336

337+
self.assertEqual(0, len(traces))
338+
333339

334340
if __name__ == "__main__":
335341
unittest.main()

0 commit comments

Comments
 (0)