@cryptotaxi247 / CoPilot / commits / 094753d9

Sapi siem integration (#149)

* Fix integration name with spaces and add SAP SIEM integration and auth keys * Add SAP SIEM integration router * Refactor auth key extraction for Mimecast and SAP SIEM integrations * Refactor SAP SIEM route and collect SAP SIEM request * Refactor SAP SIEM integration code to support multiple API keys * Add SAP SIEM schema and services for collecting and checking suspicious logins * Add event_timestamp and case_created fields to Result model and SapSiemSource model * Update index name in find_suscpicious_logins function * Refactor fetch_and_validate_data function to accept keyword arguments * Add asset schema and update case with asset information * Remove temporary code for testing * Add customer_code field to SapSiemSource and SuspiciousLogin models * Add errDetails field to SapSiemSource and SuspiciousLogin models * Add SAP SIEM suspicious logins analysis route * Add scroll functionality for retrieving search results * Add event_analyzed flag to Elasticsearch document * Add SAP SIEM multiple logins analysis route * Convert loginID to lowercase before adding to ip_to_login_ids * Add event_analyzed_multiple_logins field to Result class * Add SapSiemMultipleLogins model and update sap_siem_multiple_logins_same_ip function * Refactor code to improve performance and readability * Add function to update event_analyzed_multiple_logins flag in Elasticsearch document * Add update_event_analyzed_multiple_logins_flag function call * Update customer code and handle exception in sap_siem_multiple_logins.py * docs * precommit fixes * Update SAP SIEM integration and scheduler*** * Add new columns to existing tables * Add optional extra_data parameter to update_job function * Add optional threshold parameter to run_sap_siem_suspicious_logins_analysis and run_sap_siem_multiple_logins_same_ip_analysis * Refactor invoke_sap_siem_integration_suspicious_logins_analysis() to use a default threshold value * Add scheduler jobs for SAP SIEM integration * Fix scroll clearing in SAP SIEM services * grafana sap siem user dashboard * Add SapSiemDashboard and provision_sap_siem function * Update SapSiemDashboard enum and provision function * Grafana dashboard change * Remove alert creation provisiong from connectors table * Update docker-compose.yml to version v0.0.3

taylor_socfortress committed Feb 14, 2024 at 10:48 UTC 094753d954af6c32715c06d8bf917fac916aeafc
32 files changed +4531 -42
README.md
+1 -1
@@ -63,7 +63,7 @@ systemctl restart docker
63
64 ```bash
65 # Clone the CoPilot repository
66 -wget https://raw.githubusercontent.com/socfortress/CoPilot/v0.0.2/docker-compose.yml
66 +wget https://raw.githubusercontent.com/socfortress/CoPilot/v0.0.3/docker-compose.yml
67
68 # Edit the docker-compose.yml file to set the server name and/or the services you want to use
69
backend/app/connectors/dfir_iris/utils/universal.py
+5 -4
@@ -109,6 +109,7 @@ async def fetch_and_parse_data(
109 session: ClientSession,
110 action: Callable,
111 *args,
112 + **kwargs: Optional[Any],
113 ) -> Dict[str, Union[bool, Optional[Dict]]]:
114 """
115 Fetches and parses data from DFIR-IRIS using a specified action.
@@ -122,8 +123,8 @@ async def fetch_and_parse_data(
123 dict: A dictionary containing the success status and either the fetched data or None if the operation was unsuccessful.
124 """
125 try:
125 - logger.info(f"Executing {action.__name__}... on args: {args}")
126 - status = action(*args)
126 + logger.info(f"Executing {action.__name__}... on args: {args} and kwargs: {kwargs}")
127 + status = action(*args, **kwargs)
128 assert_api_resp(status, soft_fail=False)
129 data = get_data_from_resp(status)
130 logger.info(f"Successfully executed {action.__name__}")
@@ -223,7 +224,7 @@ def handle_error(error_message: str, status_code: int = 500):
224 raise HTTPException(status_code=status_code, detail=error_message)
225
226
226 -async def fetch_and_validate_data(client: Any, func: Callable, *args: Any) -> Dict:
227 +async def fetch_and_validate_data(client: Any, func: Callable, *args: Any, **kwargs: Optional[Any]) -> Dict:
228 """
229 Fetches and validates data using the provided client, function, and arguments.
230
@@ -238,7 +239,7 @@ async def fetch_and_validate_data(client: Any, func: Callable, *args: Any) -> Di
239 Raises:
240 Exception: If the data fetching fails.
241 """
241 - result = await fetch_and_parse_data(client, func, *args)
242 + result = await fetch_and_parse_data(client, func, *args, **kwargs)
243 if not result["success"]:
244 handle_error(f"Failed to fetch data: {result['message']}")
245 return result
backend/app/connectors/grafana/dashboards/SapSiem/users_auth.json new
+2129
@@ -0,0 +1,2129 @@
1 +{
2 + "annotations": {
3 + "list": [
4 + {
5 + "builtIn": 1,
6 + "datasource": {
7 + "type": "datasource",
8 + "uid": "grafana"
9 + },
10 + "enable": true,
11 + "hide": true,
12 + "iconColor": "rgba(0, 211, 255, 1)",
13 + "name": "Annotations & Alerts",
14 + "target": {
15 + "limit": 100,
16 + "matchAny": false,
17 + "tags": [],
18 + "type": "dashboard"
19 + },
20 + "type": "dashboard"
21 + }
22 + ]
23 + },
24 + "editable": false,
25 + "fiscalYearStartMonth": 0,
26 + "graphTooltip": 0,
27 + "id": null,
28 + "links": [
29 + {
30 + "asDropdown": true,
31 + "icon": "external link",
32 + "includeVars": true,
33 + "keepTime": true,
34 + "tags": ["SAP"],
35 + "targetBlank": true,
36 + "title": "",
37 + "type": "dashboards"
38 + }
39 + ],
40 + "liveNow": false,
41 + "panels": [
42 + {
43 + "datasource": {
44 + "type": "grafana-opensearch-datasource",
45 + "uid": "replace_datasource_uid"
46 + },
47 + "fieldConfig": {
48 + "defaults": {
49 + "mappings": [
50 + {
51 + "options": {
52 + "match": "null",
53 + "result": {
54 + "text": "N/A"
55 + }
56 + },
57 + "type": "special"
58 + }
59 + ],
60 + "thresholds": {
61 + "mode": "absolute",
62 + "steps": [
63 + {
64 + "color": "super-light-red",
65 + "value": null
66 + }
67 + ]
68 + },
69 + "unit": "locale"
70 + },
71 + "overrides": []
72 + },
73 + "gridPos": {
74 + "h": 7,
75 + "w": 4,
76 + "x": 0,
77 + "y": 0
78 + },
79 + "id": 43,
80 + "links": [],
81 + "options": {
82 + "colorMode": "value",
83 + "graphMode": "area",
84 + "justifyMode": "auto",
85 + "orientation": "horizontal",
86 + "reduceOptions": {
87 + "calcs": ["sum"],
88 + "fields": "",
89 + "values": false
90 + },
91 + "text": {},
92 + "textMode": "auto",
93 + "wideLayout": true
94 + },
95 + "pluginVersion": "10.2.3",
96 + "targets": [
97 + {
98 + "bucketAggs": [
99 + {
100 + "field": "event_timestamp",
101 + "id": "2",
102 + "settings": {
103 + "interval": "auto",
104 + "min_doc_count": 0,
105 + "trimEdges": 0
106 + },
107 + "type": "date_histogram"
108 + }
109 + ],
110 + "datasource": {
111 + "type": "grafana-opensearch-datasource",
112 + "uid": "replace_datasource_uid"
113 + },
114 + "metrics": [
115 + {
116 + "field": "select field",
117 + "id": "1",
118 + "type": "count"
119 + }
120 + ],
121 + "query": "_exists_:errCode AND logSource:$log_source",
122 + "refId": "A",
123 + "timeField": "timestamp"
124 + }
125 + ],
126 + "title": "AUTH ERRORS",
127 + "type": "stat"
128 + },
129 + {
130 + "datasource": {
131 + "type": "grafana-opensearch-datasource",
132 + "uid": "replace_datasource_uid"
133 + },
134 + "fieldConfig": {
135 + "defaults": {
136 + "color": {
137 + "mode": "palette-classic"
138 + },
139 + "custom": {
140 + "hideFrom": {
141 + "legend": false,
142 + "tooltip": false,
143 + "viz": false
144 + }
145 + },
146 + "mappings": []
147 + },
148 + "overrides": []
149 + },
150 + "gridPos": {
151 + "h": 7,
152 + "w": 4,
153 + "x": 4,
154 + "y": 0
155 + },
156 + "id": 53,
157 + "options": {
158 + "legend": {
159 + "displayMode": "table",
160 + "placement": "right",
161 + "showLegend": false
162 + },
163 + "pieType": "donut",
164 + "reduceOptions": {
165 + "calcs": ["sum"],
166 + "fields": "",
167 + "values": true
168 + },
169 + "tooltip": {
170 + "mode": "single",
171 + "sort": "none"
172 + }
173 + },
174 + "pluginVersion": "8.5.1",
175 + "targets": [
176 + {
177 + "bucketAggs": [
178 + {
179 + "fake": true,
180 + "field": "errDetails",
181 + "id": "4",
182 + "settings": {
183 + "min_doc_count": 1,
184 + "order": "desc",
185 + "orderBy": "_count",
186 + "size": "10"
187 + },
188 + "type": "terms"
189 + }
190 + ],
191 + "datasource": {
192 + "type": "grafana-opensearch-datasource",
193 + "uid": "replace_datasource_uid"
194 + },
195 + "metrics": [
196 + {
197 + "field": "select field",
198 + "id": "1",
199 + "type": "count"
200 + }
201 + ],
202 + "query": "_exists_:errCode AND logSource:$log_source",
203 + "refId": "A",
204 + "timeField": "timestamp"
205 + }
206 + ],
207 + "title": "EVENTS BY ERROR (Top 10)",
208 + "transformations": [],
209 + "type": "piechart"
210 + },
211 + {
212 + "datasource": {
213 + "type": "grafana-opensearch-datasource",
214 + "uid": "replace_datasource_uid"
215 + },
216 + "fieldConfig": {
217 + "defaults": {
218 + "color": {
219 + "mode": "thresholds"
220 + },
221 + "custom": {
222 + "align": "auto",
223 + "cellOptions": {
224 + "type": "auto"
225 + },
226 + "inspect": false
227 + },
228 + "mappings": [],
229 + "thresholds": {
230 + "mode": "absolute",
231 + "steps": [
232 + {
233 + "color": "red",
234 + "value": null
235 + }
236 + ]
237 + }
238 + },
239 + "overrides": [
240 + {
241 + "matcher": {
242 + "id": "byName",
243 + "options": "Count"
244 + },
245 + "properties": [
246 + {
247 + "id": "displayName",
248 + "value": "EVENTS"
249 + },
250 + {
251 + "id": "unit",
252 + "value": "short"
253 + },
254 + {
255 + "id": "decimals",
256 + "value": -1
257 + },
258 + {
259 + "id": "custom.align"
260 + },
261 + {
262 + "id": "thresholds",
263 + "value": {
264 + "mode": "absolute",
265 + "steps": [
266 + {
267 + "color": "red",
268 + "value": null
269 + }
270 + ]
271 + }
272 + }
273 + ]
274 + }
275 + ]
276 + },
277 + "gridPos": {
278 + "h": 7,
279 + "w": 6,
280 + "x": 8,
281 + "y": 0
282 + },
283 + "id": 54,
284 + "options": {
285 + "cellHeight": "sm",
286 + "footer": {
287 + "countRows": false,
288 + "fields": "",
289 + "reducer": ["sum"],
290 + "show": false
291 + },
292 + "showHeader": true
293 + },
294 + "pluginVersion": "10.2.3",
295 + "targets": [
296 + {
297 + "bucketAggs": [
298 + {
299 + "fake": true,
300 + "field": "errDetails",
301 + "id": "4",
302 + "settings": {
303 + "min_doc_count": 1,
304 + "order": "desc",
305 + "orderBy": "_count",
306 + "size": "0"
307 + },
308 + "type": "terms"
309 + }
310 + ],
311 + "datasource": {
312 + "type": "grafana-opensearch-datasource",
313 + "uid": "replace_datasource_uid"
314 + },
315 + "metrics": [
316 + {
317 + "field": "select field",
318 + "id": "1",
319 + "type": "count"
320 + }
321 + ],
322 + "query": "logSource:$log_source",
323 + "refId": "A",
324 + "timeField": "timestamp"
325 + }
326 + ],
327 + "title": "EVENTS BY ERROR (Top 10)",
328 + "transformations": [
329 + {
330 + "id": "merge",
331 + "options": {
332 + "reducers": []
333 + }
334 + },
335 + {
336 + "id": "organize",
337 + "options": {
338 + "excludeByName": {},
339 + "indexByName": {},
340 + "renameByName": {
341 + "Count": "EVENTS",
342 + "data_errDetails": "ERROR",
343 + "data_office_365_Operation": "OPERATION"
344 + }
345 + }
346 + }
347 + ],
348 + "type": "table"
349 + },
350 + {
351 + "circleMaxSize": 30,
352 + "circleMinSize": 2,
353 + "colors": ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
354 + "datasource": {
355 + "type": "grafana-opensearch-datasource",
356 + "uid": "replace_datasource_uid"
357 + },
358 + "decimals": 0,
359 + "esMetric": "Count",
360 + "gridPos": {
361 + "h": 7,
362 + "w": 10,
363 + "x": 14,
364 + "y": 0
365 + },
366 + "hideEmpty": false,
367 + "hideZero": false,
368 + "id": 67,
369 + "initialZoom": 1,
370 + "locationData": "countries",
371 + "mapCenter": "(0°, 0°)",
372 + "mapCenterLatitude": 0,
373 + "mapCenterLongitude": 0,
374 + "maxDataPoints": 1,
375 + "mouseWheelZoom": false,
376 + "showLegend": true,
377 + "stickyLabels": false,
378 + "tableQueryOptions": {
379 + "geohashField": "geohash",
380 + "latitudeField": "latitude",
381 + "longitudeField": "longitude",
382 + "metricField": "metric",
383 + "queryType": "geohash"
384 + },
385 + "targets": [
386 + {
387 + "bucketAggs": [
388 + {
389 + "fake": true,
390 + "field": "ip_country_code",
391 + "id": "3",
392 + "settings": {
393 + "min_doc_count": 1,
394 + "order": "desc",
395 + "orderBy": "_term",
396 + "size": "0"
397 + },
398 + "type": "terms"
399 + },
400 + {
401 + "field": "timestamp",
402 + "id": "2",
403 + "settings": {
404 + "interval": "auto",
405 + "min_doc_count": 0,
406 + "trimEdges": 0
407 + },
408 + "type": "date_histogram"
409 + }
410 + ],
411 + "datasource": {
412 + "type": "grafana-opensearch-datasource",
413 + "uid": "replace_datasource_uid"
414 + },
415 + "metrics": [
416 + {
417 + "field": "select field",
418 + "id": "1",
419 + "type": "count"
420 + }
421 + ],
422 + "query": "NOT errCode:0 AND logSource:$log_source",
423 + "refId": "A",
424 + "timeField": "timestamp"
425 + }
426 + ],
427 + "thresholds": "0,10",
428 + "title": "FAILED AUTH EVENTS BY COUNTRY",
429 + "type": "grafana-worldmap-panel",
430 + "unitPlural": "",
431 + "unitSingle": "",
432 + "valueName": "total"
433 + },
434 + {
435 + "datasource": {
436 + "type": "grafana-opensearch-datasource",
437 + "uid": "replace_datasource_uid"
438 + },
439 + "fieldConfig": {
440 + "defaults": {
441 + "mappings": [
442 + {
443 + "options": {
444 + "match": "null",
445 + "result": {
446 + "text": "N/A"
447 + }
448 + },
449 + "type": "special"
450 + }
451 + ],
452 + "thresholds": {
453 + "mode": "absolute",
454 + "steps": [
455 + {
456 + "color": "blue",
457 + "value": null
458 + }
459 + ]
460 + },
461 + "unit": "locale"
462 + },
463 + "overrides": []
464 + },
465 + "gridPos": {
466 + "h": 7,
467 + "w": 4,
468 + "x": 0,
469 + "y": 7
470 + },
471 + "id": 61,
472 + "links": [],
473 + "options": {
474 + "colorMode": "value",
475 + "graphMode": "none",
476 + "justifyMode": "auto",
477 + "orientation": "horizontal",
478 + "reduceOptions": {
479 + "calcs": ["sum"],
480 + "fields": "",
481 + "values": false
482 + },
483 + "text": {},
484 + "textMode": "auto",
485 + "wideLayout": true
486 + },
487 + "pluginVersion": "10.2.3",
488 + "targets": [
489 + {
490 + "bucketAggs": [
491 + {
492 + "field": "timestamp",
493 + "id": "2",
494 + "settings": {
495 + "interval": "auto",
496 + "min_doc_count": 0,
497 + "trimEdges": 0
498 + },
499 + "type": "date_histogram"
500 + }
501 + ],
502 + "datasource": {
503 + "type": "grafana-opensearch-datasource",
504 + "uid": "replace_datasource_uid"
505 + },
506 + "metrics": [
507 + {
508 + "field": "select field",
509 + "id": "1",
510 + "type": "count"
511 + }
512 + ],
513 + "query": "logSource:$log_source",
514 + "refId": "A",
515 + "timeField": "timestamp"
516 + }
517 + ],
518 + "title": "TOTAL EVENTS",
519 + "type": "stat"
520 + },
521 + {
522 + "datasource": {
523 + "type": "grafana-opensearch-datasource",
524 + "uid": "replace_datasource_uid"
525 + },
526 + "fieldConfig": {
527 + "defaults": {
528 + "color": {
529 + "mode": "palette-classic"
530 + },
531 + "custom": {
532 + "axisBorderShow": false,
533 + "axisCenteredZero": false,
534 + "axisColorMode": "text",
535 + "axisLabel": "",
536 + "axisPlacement": "auto",
537 + "barAlignment": 0,
538 + "drawStyle": "bars",
539 + "fillOpacity": 0,
540 + "gradientMode": "none",
541 + "hideFrom": {
542 + "legend": false,
543 + "tooltip": false,
544 + "viz": false
545 + },
546 + "insertNulls": false,
547 + "lineInterpolation": "linear",
548 + "lineWidth": 1,
549 + "pointSize": 5,
550 + "scaleDistribution": {
551 + "type": "linear"
552 + },
553 + "showPoints": "auto",
554 + "spanNulls": false,
555 + "stacking": {
556 + "group": "A",
557 + "mode": "normal"
558 + },
559 + "thresholdsStyle": {
560 + "mode": "off"
561 + }
562 + },
563 + "mappings": [],
564 + "thresholds": {
565 + "mode": "absolute",
566 + "steps": [
567 + {
568 + "color": "green",
569 + "value": null
570 + },
571 + {
572 + "color": "red",
573 + "value": 80
574 + }
575 + ]
576 + }
577 + },
578 + "overrides": []
579 + },
580 + "gridPos": {
581 + "h": 13,
582 + "w": 20,
583 + "x": 4,
584 + "y": 7
585 + },
586 + "id": 66,
587 + "options": {
588 + "legend": {
589 + "calcs": [],
590 + "displayMode": "table",
591 + "placement": "right",
592 + "showLegend": true
593 + },
594 + "tooltip": {
595 + "mode": "single",
596 + "sort": "none"
597 + }
598 + },
599 + "targets": [
600 + {
601 + "alias": "",
602 + "bucketAggs": [
603 + {
604 + "field": "params_loginID",
605 + "id": "3",
606 + "settings": {
607 + "min_doc_count": "1",
608 + "order": "desc",
609 + "orderBy": "_count",
610 + "size": "10"
611 + },
612 + "type": "terms"
613 + },
614 + {
615 + "field": "timestamp",
616 + "id": "2",
617 + "settings": {
618 + "interval": "auto"
619 + },
620 + "type": "date_histogram"
621 + }
622 + ],
623 + "datasource": {
624 + "type": "grafana-opensearch-datasource",
625 + "uid": "replace_datasource_uid"
626 + },
627 + "metrics": [
628 + {
629 + "id": "1",
630 + "type": "count"
631 + }
632 + ],
633 + "query": "logSource:$log_source",
634 + "refId": "A",
635 + "timeField": "timestamp"
636 + }
637 + ],
638 + "title": "TOP 10 USERS - HISTOGRAM",
639 + "transparent": true,
640 + "type": "timeseries"
641 + },
642 + {
643 + "datasource": {
644 + "type": "grafana-opensearch-datasource",
645 + "uid": "replace_datasource_uid"
646 + },
647 + "fieldConfig": {
648 + "defaults": {
649 + "mappings": [
650 + {
651 + "options": {
652 + "match": "null",
653 + "result": {
654 + "text": "N/A"
655 + }
656 + },
657 + "type": "special"
658 + }
659 + ],
660 + "thresholds": {
661 + "mode": "absolute",
662 + "steps": [
663 + {
664 + "color": "blue",
665 + "value": null
666 + }
667 + ]
668 + },
669 + "unit": "locale"
670 + },
671 + "overrides": []
672 + },
673 + "gridPos": {
674 + "h": 6,
675 + "w": 4,
676 + "x": 0,
677 + "y": 14
678 + },
679 + "id": 18,
680 + "links": [],
681 + "options": {
682 + "colorMode": "value",
683 + "graphMode": "none",
684 + "justifyMode": "auto",
685 + "orientation": "horizontal",
686 + "reduceOptions": {
687 + "calcs": ["sum"],
688 + "fields": "",
689 + "values": false
690 + },
691 + "text": {},
692 + "textMode": "auto",
693 + "wideLayout": true
694 + },
695 + "pluginVersion": "10.2.3",
696 + "targets": [
697 + {
698 + "bucketAggs": [
699 + {
700 + "$$hashKey": "object:331",
701 + "field": "timestamp",
702 + "id": "2",
703 + "settings": {
704 + "interval": "365d",
705 + "min_doc_count": 0,
706 + "trimEdges": 0
707 + },
708 + "type": "date_histogram"
709 + }
710 + ],
711 + "datasource": {
712 + "type": "grafana-opensearch-datasource",
713 + "uid": "replace_datasource_uid"
714 + },
715 + "metrics": [
716 + {
717 + "field": "params_loginID",
718 + "id": "1",
719 + "type": "cardinality"
720 + }
721 + ],
722 + "query": "logSource:$log_source",
723 + "refId": "A",
724 + "timeField": "timestamp"
725 + }
726 + ],
727 + "title": "SAP - TOTAL ACCOUNTS",
728 + "type": "stat"
729 + },
730 + {
731 + "datasource": {
732 + "type": "grafana-opensearch-datasource",
733 + "uid": "replace_datasource_uid"
734 + },
735 + "fieldConfig": {
736 + "defaults": {
737 + "color": {
738 + "mode": "thresholds"
739 + },
740 + "mappings": [],
741 + "thresholds": {
742 + "mode": "absolute",
743 + "steps": [
744 + {
745 + "color": "green",
746 + "value": null
747 + },
748 + {
749 + "color": "red",
750 + "value": 80
751 + }
752 + ]
753 + }
754 + },
755 + "overrides": []
756 + },
757 + "gridPos": {
758 + "h": 12,
759 + "w": 9,
760 + "x": 0,
761 + "y": 20
762 + },
763 + "id": 57,
764 + "options": {
765 + "displayMode": "gradient",
766 + "maxVizHeight": 300,
767 + "minVizHeight": 10,
768 + "minVizWidth": 0,
769 + "namePlacement": "auto",
770 + "orientation": "horizontal",
771 + "reduceOptions": {
772 + "calcs": ["sum"],
773 + "fields": "",
774 + "values": false
775 + },
776 + "showUnfilled": true,
777 + "sizing": "auto",
778 + "valueMode": "color"
779 + },
780 + "pluginVersion": "10.2.3",
781 + "targets": [
782 + {
783 + "alias": "",
784 + "bucketAggs": [
785 + {
786 + "field": "userAgent_raw",
787 + "id": "2",
788 + "settings": {
789 + "min_doc_count": "1",
790 + "order": "desc",
791 + "orderBy": "_count",
792 + "size": "15"
793 + },
794 + "type": "terms"
795 + },
796 + {
797 + "field": "timestamp",
798 + "id": "3",
799 + "settings": {
800 + "interval": "auto",
801 + "min_doc_count": "1",
802 + "timeZone": "utc",
803 + "trimEdges": "0"
804 + },
805 + "type": "date_histogram"
806 + }
807 + ],
808 + "datasource": {
809 + "type": "grafana-opensearch-datasource",
810 + "uid": "replace_datasource_uid"
811 + },
812 + "metrics": [
813 + {
814 + "id": "1",
815 + "type": "count"
816 + }
817 + ],
818 + "query": "logSource:$log_source",
819 + "refId": "A",
820 + "timeField": "timestamp"
821 + }
822 + ],
823 + "title": "USER AGENTS",
824 + "type": "bargauge"
825 + },
826 + {
827 + "circleMaxSize": 30,
828 + "circleMinSize": 2,
829 + "colors": ["rgba(245, 54, 54, 0.9)", "rgba(237, 129, 40, 0.89)", "rgba(50, 172, 45, 0.97)"],
830 + "datasource": {
831 + "type": "grafana-opensearch-datasource",
832 + "uid": "replace_datasource_uid"
833 + },
834 + "decimals": 0,
835 + "esMetric": "Count",
836 + "gridPos": {
837 + "h": 12,
838 + "w": 7,
839 + "x": 9,
840 + "y": 20
841 + },
842 + "hideEmpty": false,
843 + "hideZero": false,
844 + "id": 50,
845 + "initialZoom": 1,
846 + "locationData": "countries",
847 + "mapCenter": "(0°, 0°)",
848 + "mapCenterLatitude": 0,
849 + "mapCenterLongitude": 0,
850 + "maxDataPoints": 1,
851 + "mouseWheelZoom": false,
852 + "showLegend": true,
853 + "stickyLabels": false,
854 + "tableQueryOptions": {
855 + "geohashField": "geohash",
856 + "latitudeField": "latitude",
857 + "longitudeField": "longitude",
858 + "metricField": "metric",
859 + "queryType": "geohash"
860 + },
861 + "targets": [
862 + {
863 + "bucketAggs": [
864 + {
865 + "fake": true,
866 + "field": "ip_country_code",
867 + "id": "3",
868 + "settings": {
869 + "min_doc_count": 1,
870 + "order": "desc",
871 + "orderBy": "_term",
872 + "size": "0"
873 + },
874 + "type": "terms"
875 + },
876 + {
877 + "field": "timestamp",
878 + "id": "2",
879 + "settings": {
880 + "interval": "auto",
881 + "min_doc_count": 0,
882 + "trimEdges": 0
883 + },
884 + "type": "date_histogram"
885 + }
886 + ],
887 + "datasource": {
888 + "type": "grafana-opensearch-datasource",
889 + "uid": "replace_datasource_uid"
890 + },
891 + "metrics": [
892 + {
893 + "field": "select field",
894 + "id": "1",
895 + "type": "count"
896 + }
897 + ],
898 + "query": "logSource:$log_source",
899 + "refId": "A",
900 + "timeField": "timestamp"
901 + }
902 + ],
903 + "thresholds": "0,10",
904 + "title": "ALL AUTH EVENTS BY COUNTRY",
905 + "type": "grafana-worldmap-panel",
906 + "unitPlural": "",
907 + "unitSingle": "",
908 + "valueName": "total"
909 + },
910 + {
911 + "datasource": {
912 + "type": "grafana-opensearch-datasource",
913 + "uid": "replace_datasource_uid"
914 + },
915 + "fieldConfig": {
916 + "defaults": {
917 + "color": {
918 + "mode": "thresholds"
919 + },
920 + "custom": {
921 + "align": "auto",
922 + "cellOptions": {
923 + "type": "auto"
924 + },
925 + "inspect": false
926 + },
927 + "mappings": [],
928 + "thresholds": {
929 + "mode": "absolute",
930 + "steps": [
931 + {
932 + "color": "orange",
933 + "value": null
934 + }
935 + ]
936 + }
937 + },
938 + "overrides": [
939 + {
940 + "matcher": {
941 + "id": "byName",
942 + "options": "Count"
943 + },
944 + "properties": [
945 + {
946 + "id": "displayName",
947 + "value": "EVENTS"
948 + },
949 + {
950 + "id": "unit",
951 + "value": "short"
952 + },
953 + {
954 + "id": "decimals",
955 + "value": -1
956 + },
957 + {
958 + "id": "custom.align"
959 + },
960 + {
961 + "id": "custom.cellOptions",
962 + "value": {
963 + "type": "color-text"
964 + }
965 + }
966 + ]
967 + },
968 + {
969 + "matcher": {
970 + "id": "byName",
971 + "options": "AD ACCOUNT"
972 + },
973 + "properties": [
974 + {
975 + "id": "custom.width",
976 + "value": 219
977 + },
978 + {
979 + "id": "custom.cellOptions",
980 + "value": {
981 + "type": "color-text"
982 + }
983 + }
984 + ]
985 + },
986 + {
987 + "matcher": {
988 + "id": "byName",
989 + "options": "data_office_365_LogonError"
990 + },
991 + "properties": [
992 + {
993 + "id": "custom.width",
994 + "value": 326
995 + },
996 + {
997 + "id": "custom.cellOptions",
998 + "value": {
999 + "type": "color-text"
1000 + }
1001 + },
1002 + {
1003 + "id": "links",
1004 + "value": [
1005 + {
1006 + "targetBlank": true,
1007 + "title": "MICROSOFT LOGON ERROR",
1008 + "url": "https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-aadsts-error-codes"
1009 + }
1010 + ]
1011 + }
1012 + ]
1013 + },
1014 + {
1015 + "matcher": {
1016 + "id": "byName",
1017 + "options": "data_office_365_ActorIpAddress_country_code"
1018 + },
1019 + "properties": [
1020 + {
1021 + "id": "custom.cellOptions",
1022 + "value": {
1023 + "type": "color-text"
1024 + }
1025 + }
1026 + ]
1027 + },
1028 + {
1029 + "matcher": {
1030 + "id": "byName",
1031 + "options": "COUNTRY"
1032 + },
1033 + "properties": [
1034 + {
1035 + "id": "custom.width",
1036 + "value": 86
1037 + }
1038 + ]
1039 + },
1040 + {
1041 + "matcher": {
1042 + "id": "byName",
1043 + "options": "ERROR"
1044 + },
1045 + "properties": [
1046 + {
1047 + "id": "custom.width",
1048 + "value": 399
1049 + }
1050 + ]
1051 + }
1052 + ]
1053 + },
1054 + "gridPos": {
1055 + "h": 12,
1056 + "w": 8,
1057 + "x": 16,
1058 + "y": 20
1059 + },
1060 + "id": 58,
1061 + "options": {
1062 + "cellHeight": "sm",
1063 + "footer": {
1064 + "countRows": false,
1065 + "fields": "",
1066 + "reducer": ["sum"],
1067 + "show": false
1068 + },
1069 + "showHeader": true,
1070 + "sortBy": []
1071 + },
1072 + "pluginVersion": "10.2.3",
1073 + "targets": [
1074 + {
1075 + "bucketAggs": [
1076 + {
1077 + "fake": true,
1078 + "field": "params_loginID",
1079 + "id": "4",
1080 + "settings": {
1081 + "min_doc_count": 1,
1082 + "order": "desc",
1083 + "orderBy": "_count",
1084 + "size": "0"
1085 + },
1086 + "type": "terms"
1087 + },
1088 + {
1089 + "field": "errDetails",
1090 + "id": "5",
1091 + "settings": {
1092 + "min_doc_count": "1",
1093 + "order": "desc",
1094 + "orderBy": "_term",
1095 + "size": "10"
1096 + },
1097 + "type": "terms"
1098 + },
1099 + {
1100 + "field": "ip_country_code",
1101 + "id": "6",
1102 + "settings": {
1103 + "min_doc_count": "1",
1104 + "order": "desc",
1105 + "orderBy": "_term",
1106 + "size": "10"
1107 + },
1108 + "type": "terms"
1109 + }
1110 + ],
1111 + "datasource": {
1112 + "type": "grafana-opensearch-datasource",
1113 + "uid": "replace_datasource_uid"
1114 + },
1115 + "metrics": [
1116 + {
1117 + "field": "select field",
1118 + "id": "1",
1119 + "type": "count"
1120 + }
1121 + ],
1122 + "query": "_exists_:errCode AND logSource:$log_source",
1123 + "refId": "A",
1124 + "timeField": "timestamp"
1125 + }
1126 + ],
1127 + "title": "FAILED AUTHS BY ACCOUNT AND ERROR",
1128 + "transformations": [
1129 + {
1130 + "id": "merge",
1131 + "options": {
1132 + "reducers": []
1133 + }
1134 + },
1135 + {
1136 + "id": "organize",
1137 + "options": {
1138 + "excludeByName": {},
1139 + "indexByName": {
1140 + "Count": 3,
1141 + "data_errDetails": 2,
1142 + "data_ip_country_code": 1,
1143 + "data_userKeyDetails_name": 0
1144 + },
1145 + "renameByName": {
1146 + "Count": "EVENTS",
1147 + "data_errDetails": "ERROR",
1148 + "data_ip_country_code": "COUNTRY",
1149 + "data_office_365_ActorIpAddress_country_code": "COUNTRY",
1150 + "data_office_365_LogonError": "ERROR",
1151 + "data_office_365_Operation": "WORKLOAD",
1152 + "data_office_365_UserId": "AD ACCOUNT",
1153 + "data_userKeyDetails_name": "ACCT"
1154 + }
1155 + }
1156 + }
1157 + ],
1158 + "type": "table"
1159 + },
1160 + {
1161 + "datasource": {
1162 + "type": "grafana-opensearch-datasource",
1163 + "uid": "replace_datasource_uid"
1164 + },
1165 + "fieldConfig": {
1166 + "defaults": {
1167 + "color": {
1168 + "mode": "thresholds"
1169 + },
1170 + "custom": {
1171 + "align": "auto",
1172 + "cellOptions": {
1173 + "type": "auto"
1174 + },
1175 + "filterable": true,
1176 + "inspect": false
1177 + },
1178 + "mappings": [],
1179 + "thresholds": {
1180 + "mode": "absolute",
1181 + "steps": [
1182 + {
1183 + "color": "green",
1184 + "value": null
1185 + },
1186 + {
1187 + "color": "red",
1188 + "value": 80
1189 + }
1190 + ]
1191 + }
1192 + },
1193 + "overrides": [
1194 + {
1195 + "matcher": {
1196 + "id": "byName",
1197 + "options": "rule_level"
1198 + },
1199 + "properties": [
1200 + {
1201 + "id": "displayName",
1202 + "value": "RULE LEVEL"
1203 + },
1204 + {
1205 + "id": "unit",
1206 + "value": "short"
1207 + },
1208 + {
1209 + "id": "decimals",
1210 + "value": -1
1211 + },
1212 + {
1213 + "id": "custom.cellOptions",
1214 + "value": {
1215 + "mode": "gradient",
1216 + "type": "color-background"
1217 + }
1218 + },
1219 + {
1220 + "id": "custom.align"
1221 + },
1222 + {
1223 + "id": "thresholds",
1224 + "value": {
1225 + "mode": "absolute",
1226 + "steps": [
1227 + {
1228 + "color": "#37872D",
1229 + "value": null
1230 + },
1231 + {
1232 + "color": "red",
1233 + "value": 7
1234 + },
1235 + {
1236 + "color": "rgba(245, 54, 54, 0.9)",
1237 + "value": 12
1238 + }
1239 + ]
1240 + }
1241 + }
1242 + ]
1243 + },
1244 + {
1245 + "matcher": {
1246 + "id": "byName",
1247 + "options": "Date/Time"
1248 + },
1249 + "properties": [
1250 + {
1251 + "id": "custom.width",
1252 + "value": 242
1253 + }
1254 + ]
1255 + },
1256 + {
1257 + "matcher": {
1258 + "id": "byName",
1259 + "options": "MITRE TACTIC"
1260 + },
1261 + "properties": [
1262 + {
1263 + "id": "custom.width",
1264 + "value": 332
1265 + }
1266 + ]
1267 + },
1268 + {
1269 + "matcher": {
1270 + "id": "byName",
1271 + "options": "IP ADDRESS"
1272 + },
1273 + "properties": [
1274 + {
1275 + "id": "custom.width",
1276 + "value": 163
1277 + }
1278 + ]
1279 + },
1280 + {
1281 + "matcher": {
1282 + "id": "byName",
1283 + "options": "MITRE TECHNIQUE"
1284 + },
1285 + "properties": [
1286 + {
1287 + "id": "custom.width",
1288 + "value": 312
1289 + }
1290 + ]
1291 + },
1292 + {
1293 + "matcher": {
1294 + "id": "byName",
1295 + "options": "EVENT ID"
1296 + },
1297 + "properties": [
1298 + {
1299 + "id": "links",
1300 + "value": [
1301 + {
1302 + "targetBlank": true,
1303 + "title": "VIEW EVENT DETAILS",
1304 + "url": "https://grafana.detacon.cloud/explore?left=%7B%22datasource%22:%22SAP_SIEM%22,%22queries%22:%5B%7B%22refId%22:%22A%22,%22query%22:%22_id:${__value.text}%22,%22alias%22:%22%22,%22metrics%22:%5B%7B%22id%22:%221%22,%22type%22:%22logs%22,%22settings%22:%7B%22limit%22:%22500%22%7D%7D%5D,%22bucketAggs%22:%5B%5D,%22timeField%22:%22timestamp%22%7D%5D,%22range%22:%7B%22from%22:%22now-6h%22,%22to%22:%22now%22%7D%7D"
1305 + }
1306 + ]
1307 + },
1308 + {
1309 + "id": "custom.width",
1310 + "value": 336
1311 + }
1312 + ]
1313 + },
1314 + {
1315 + "matcher": {
1316 + "id": "byName",
1317 + "options": "RESULT"
1318 + },
1319 + "properties": [
1320 + {
1321 + "id": "custom.width",
1322 + "value": 118
1323 + },
1324 + {
1325 + "id": "mappings",
1326 + "value": [
1327 + {
1328 + "options": {
1329 + "8": {
1330 + "color": "red",
1331 + "index": 2
1332 + },
1333 + "Failed": {
1334 + "color": "orange",
1335 + "index": 1
1336 + },
1337 + "Success": {
1338 + "color": "green",
1339 + "index": 0
1340 + }
1341 + },
1342 + "type": "value"
1343 + }
1344 + ]
1345 + },
1346 + {
1347 + "id": "custom.cellOptions",
1348 + "value": {
1349 + "type": "color-text"
1350 + }
1351 + }
1352 + ]
1353 + },
1354 + {
1355 + "matcher": {
1356 + "id": "byName",
1357 + "options": "SRC IP"
1358 + },
1359 + "properties": [
1360 + {
1361 + "id": "custom.width",
1362 + "value": 265
1363 + }
1364 + ]
1365 + },
1366 + {
1367 + "matcher": {
1368 + "id": "byName",
1369 + "options": "ACCOUNT"
1370 + },
1371 + "properties": [
1372 + {
1373 + "id": "custom.width",
1374 + "value": 343
1375 + }
1376 + ]
1377 + },
1378 + {
1379 + "matcher": {
1380 + "id": "byName",
1381 + "options": "EVENT TIME"
1382 + },
1383 + "properties": [
1384 + {
1385 + "id": "custom.width",
1386 + "value": 195
1387 + }
1388 + ]
1389 + }
1390 + ]
1391 + },
1392 + "gridPos": {
1393 + "h": 16,
1394 + "w": 24,
1395 + "x": 0,
1396 + "y": 32
1397 + },
1398 + "id": 27,
1399 + "options": {
1400 + "cellHeight": "sm",
1401 + "footer": {
1402 + "countRows": false,
1403 + "enablePagination": true,
1404 + "fields": "",
1405 + "reducer": ["sum"],
1406 + "show": false
1407 + },
1408 + "showHeader": true,
1409 + "sortBy": [
1410 + {
1411 + "desc": true,
1412 + "displayName": "RULE LEVEL"
1413 + }
1414 + ]
1415 + },
1416 + "pluginVersion": "10.2.3",
1417 + "targets": [
1418 + {
1419 + "bucketAggs": [],
1420 + "datasource": {
1421 + "type": "grafana-opensearch-datasource",
1422 + "uid": "replace_datasource_uid"
1423 + },
1424 + "metrics": [
1425 + {
1426 + "id": "1",
1427 + "settings": {
1428 + "size": "250"
1429 + },
1430 + "type": "raw_data"
1431 + }
1432 + ],
1433 + "query": "logSource:$log_source",
1434 + "refId": "A",
1435 + "timeField": "timestamp"
1436 + }
1437 + ],
1438 + "title": "EVENTS",
1439 + "transformations": [
1440 + {
1441 + "id": "organize",
1442 + "options": {
1443 + "excludeByName": {
1444 + "@metadata_beat": true,
1445 + "@metadata_type": true,
1446 + "@metadata_version": true,
1447 + "IMPHASH": true,
1448 + "MD5": true,
1449 + "SHA1": true,
1450 + "SHA256": true,
1451 + "_id": false,
1452 + "_index": true,
1453 + "_type": true,
1454 + "agent_ephemeral_id": true,
1455 + "agent_hostname": true,
1456 + "agent_id": true,
1457 + "agent_ip": true,
1458 + "agent_ip_city_name": true,
1459 + "agent_ip_country_code": true,
1460 + "agent_ip_geolocation": true,
1461 + "agent_labels_customer": true,
1462 + "agent_name": true,
1463 + "agent_type": true,
1464 + "agent_version": true,
1465 + "beats_type": true,
1466 + "cluster_name": true,
1467 + "cluster_node": true,
1468 + "collector_node_id": true,
1469 + "data_XffFirstIp": true,
1470 + "data_XffFirstIp_city_name": true,
1471 + "data_XffFirstIp_country_code": true,
1472 + "data_XffFirstIp_geolocation": true,
1473 + "data_alert_action": true,
1474 + "data_alert_category": true,
1475 + "data_alert_gid": true,
1476 + "data_alert_rev": true,
1477 + "data_alert_severity": true,
1478 + "data_alert_signature": true,
1479 + "data_alert_signature_id": true,
1480 + "data_apikey": true,
1481 + "data_app_proto": true,
1482 + "data_audit_auid": true,
1483 + "data_audit_command": true,
1484 + "data_audit_euid": true,
1485 + "data_audit_exe": true,
1486 + "data_audit_gid": true,
1487 + "data_audit_id": true,
1488 + "data_audit_pid": true,
1489 + "data_audit_res": true,
1490 + "data_audit_session": true,
1491 + "data_audit_type": true,
1492 + "data_audit_uid": true,
1493 + "data_callID": true,
1494 + "data_dest_ip": true,
1495 + "data_dest_port": true,
1496 + "data_dstuser": true,
1497 + "data_endpoint": true,
1498 + "data_errCode": true,
1499 + "data_event_type": true,
1500 + "data_extra_data": true,
1501 + "data_file": true,
1502 + "data_flow_bytes_toclient": true,
1503 + "data_flow_bytes_toserver": true,
1504 + "data_flow_id": true,
1505 + "data_flow_pkts_toclient": true,
1506 + "data_flow_pkts_toserver": true,
1507 + "data_flow_start": true,
1508 + "data_httpReq_SDK": true,
1509 + "data_httpReq_country": true,
1510 + "data_http_http_content_type": true,
1511 + "data_http_http_port": true,
1512 + "data_http_length": true,
1513 + "data_http_status": true,
1514 + "data_http_url": true,
1515 + "data_id": true,
1516 + "data_in_iface": true,
1517 + "data_integration": true,
1518 + "data_ip_city_name": true,
1519 + "data_ip_geolocation": true,
1520 + "data_label": true,
1521 + "data_metadata_flowbits": true,
1522 + "data_metadata_flowints_http_anomaly_count": true,
1523 + "data_metadata_flowints_tcp_retransmission_count": true,
1524 + "data_office_365_Actor": true,
1525 + "data_office_365_ActorContextId": true,
1526 + "data_office_365_ActorIpAddress": false,
1527 + "data_office_365_ActorIpAddress_city_name": true,
1528 + "data_office_365_ActorIpAddress_country_code": true,
1529 + "data_office_365_ActorIpAddress_geolocation": true,
1530 + "data_office_365_ApplicationId": true,
1531 + "data_office_365_AzureActiveDirectoryEventType": true,
1532 + "data_office_365_ClientIP": true,
1533 + "data_office_365_ClientIP_city_name": true,
1534 + "data_office_365_ClientIP_country_code": true,
1535 + "data_office_365_ClientIP_geolocation": true,
1536 + "data_office_365_CreationTime": false,
1537 + "data_office_365_DeviceProperties": true,
1538 + "data_office_365_ErrorNumber": true,
1539 + "data_office_365_ExtendedProperties": true,
1540 + "data_office_365_Id": true,
1541 + "data_office_365_InterSystemsId": true,
1542 + "data_office_365_IntraSystemId": true,
1543 + "data_office_365_LogonError": true,
1544 + "data_office_365_ModifiedProperties": true,
1545 + "data_office_365_ObjectId": true,
1546 + "data_office_365_Operation": true,
1547 + "data_office_365_OrganizationId": true,
1548 + "data_office_365_RecordType": true,
1549 + "data_office_365_ResultStatus": false,
1550 + "data_office_365_Target": true,
1551 + "data_office_365_TargetContextId": true,
1552 + "data_office_365_UserId": false,
1553 + "data_office_365_UserKey": true,
1554 + "data_office_365_UserType": true,
1555 + "data_office_365_Version": true,
1556 + "data_office_365_Workload": true,
1557 + "data_osquery_action": true,
1558 + "data_osquery_calendarTime": true,
1559 + "data_osquery_columns_address": true,
1560 + "data_osquery_columns_address_city_name": true,
1561 + "data_osquery_columns_address_country_code": true,
1562 + "data_osquery_columns_address_geolocation": true,
1563 + "data_osquery_columns_cmdline": true,
1564 + "data_osquery_columns_cwd": true,
1565 + "data_osquery_columns_description": true,
1566 + "data_osquery_columns_directory": true,
1567 + "data_osquery_columns_disk_bytes_read": true,
1568 + "data_osquery_columns_disk_bytes_written": true,
1569 + "data_osquery_columns_egid": true,
1570 + "data_osquery_columns_euid": true,
1571 + "data_osquery_columns_family": true,
1572 + "data_osquery_columns_fd": true,
1573 + "data_osquery_columns_gid": true,
1574 + "data_osquery_columns_gid_signed": true,
1575 + "data_osquery_columns_host": true,
1576 + "data_osquery_columns_interface": true,
1577 + "data_osquery_columns_local_address": true,
1578 + "data_osquery_columns_local_address_city_name": true,
1579 + "data_osquery_columns_local_address_country_code": true,
1580 + "data_osquery_columns_local_address_geolocation": true,
1581 + "data_osquery_columns_local_port": true,
1582 + "data_osquery_columns_mac": true,
1583 + "data_osquery_columns_name": true,
1584 + "data_osquery_columns_net_namespace": true,
1585 + "data_osquery_columns_nice": true,
1586 + "data_osquery_columns_on_disk": true,
1587 + "data_osquery_columns_parent": true,
1588 + "data_osquery_columns_path": true,
1589 + "data_osquery_columns_pgroup": true,
1590 + "data_osquery_columns_pid": true,
1591 + "data_osquery_columns_port": true,
1592 + "data_osquery_columns_protocol": true,
1593 + "data_osquery_columns_remote_address": true,
1594 + "data_osquery_columns_remote_address_city_name": true,
1595 + "data_osquery_columns_remote_address_country_code": true,
1596 + "data_osquery_columns_remote_address_geolocation": true,
1597 + "data_osquery_columns_remote_port": true,
1598 + "data_osquery_columns_resident_size": true,
1599 + "data_osquery_columns_root": true,
1600 + "data_osquery_columns_sgid": true,
1601 + "data_osquery_columns_shell": true,
1602 + "data_osquery_columns_socket": true,
1603 + "data_osquery_columns_start_time": true,
1604 + "data_osquery_columns_state": true,
1605 + "data_osquery_columns_suid": true,
1606 + "data_osquery_columns_system_time": true,
1607 + "data_osquery_columns_threads": true,
1608 + "data_osquery_columns_time_utc": true,
1609 + "data_osquery_columns_total_size": true,
1610 + "data_osquery_columns_tty": true,
1611 + "data_osquery_columns_type": true,
1612 + "data_osquery_columns_uid": true,
1613 + "data_osquery_columns_uid_signed": true,
1614 + "data_osquery_columns_user": true,
1615 + "data_osquery_columns_user_time": true,
1616 + "data_osquery_columns_username": true,
1617 + "data_osquery_columns_wired_size": true,
1618 + "data_osquery_counter": true,
1619 + "data_osquery_decorations_host_uuid": true,
1620 + "data_osquery_decorations_hostname": true,
1621 + "data_osquery_epoch": true,
1622 + "data_osquery_hostIdentifier": true,
1623 + "data_osquery_name": true,
1624 + "data_osquery_numerics": true,
1625 + "data_osquery_unixTime": true,
1626 + "data_params_apiKey": true,
1627 + "data_params_include": true,
1628 + "data_params_loginID": true,
1629 + "data_params_password": true,
1630 + "data_params_secret": true,
1631 + "data_params_userKey": true,
1632 + "data_proto": true,
1633 + "data_response": true,
1634 + "data_restrictions_ipBlacklistRestricted": true,
1635 + "data_restrictions_ipBlacklistRestrictionEnforced": true,
1636 + "data_restrictions_ipWhitelistRestricted": true,
1637 + "data_restrictions_ipWhitelistRestrictionEnforced": true,
1638 + "data_sca_check_command": true,
1639 + "data_sca_check_compliance_cis": true,
1640 + "data_sca_check_compliance_cis_csc": true,
1641 + "data_sca_check_compliance_gdpr_IV": true,
1642 + "data_sca_check_compliance_gpg_13": true,
1643 + "data_sca_check_compliance_hipaa": true,
1644 + "data_sca_check_compliance_nist_800_53": true,
1645 + "data_sca_check_compliance_pci_dss": true,
1646 + "data_sca_check_compliance_tsc": true,
1647 + "data_sca_check_description": true,
1648 + "data_sca_check_id": true,
1649 + "data_sca_check_previous_result": true,
1650 + "data_sca_check_rationale": true,
1651 + "data_sca_check_remediation": true,
1652 + "data_sca_check_result": true,
1653 + "data_sca_check_title": true,
1654 + "data_sca_description": true,
1655 + "data_sca_failed": true,
1656 + "data_sca_file": true,
1657 + "data_sca_invalid": true,
1658 + "data_sca_passed": true,
1659 + "data_sca_policy": true,
1660 + "data_sca_policy_id": true,
1661 + "data_sca_scan_id": true,
1662 + "data_sca_score": true,
1663 + "data_sca_total_checks": true,
1664 + "data_sca_type": true,
1665 + "data_script": true,
1666 + "data_serverIP_city_name": true,
1667 + "data_serverIP_country_code": false,
1668 + "data_serverIP_geolocation": true,
1669 + "data_src_ip": true,
1670 + "data_src_ip_city_name": true,
1671 + "data_src_ip_country_code": true,
1672 + "data_src_ip_geolocation": true,
1673 + "data_src_port": true,
1674 + "data_srcip": true,
1675 + "data_srcip_city_name": true,
1676 + "data_srcip_country_code": true,
1677 + "data_srcip_geolocation": true,
1678 + "data_srcuser": true,
1679 + "data_status_code": true,
1680 + "data_timestamp": true,
1681 + "data_title": true,
1682 + "data_tls_session_resumed": true,
1683 + "data_tls_version": true,
1684 + "data_tx_id": true,
1685 + "data_type": true,
1686 + "data_uid": true,
1687 + "data_userAgent_platform": true,
1688 + "data_userAgent_raw": true,
1689 + "data_userAgent_version": true,
1690 + "data_userKey": true,
1691 + "data_userKeyDetails_name": true,
1692 + "data_win_eventXML_binaryData": true,
1693 + "data_win_eventXML_binaryDataSize": true,
1694 + "data_win_eventXML_param1": true,
1695 + "data_win_eventdata_authenticationPackageName": true,
1696 + "data_win_eventdata_callTrace": true,
1697 + "data_win_eventdata_commandLine": true,
1698 + "data_win_eventdata_company": true,
1699 + "data_win_eventdata_creationUtcTime": true,
1700 + "data_win_eventdata_currentDirectory": true,
1701 + "data_win_eventdata_description": true,
1702 + "data_win_eventdata_destinationHostname": true,
1703 + "data_win_eventdata_destinationIp": true,
1704 + "data_win_eventdata_destinationIp_city_name": true,
1705 + "data_win_eventdata_destinationIp_country_code": true,
1706 + "data_win_eventdata_destinationIp_geolocation": true,
1707 + "data_win_eventdata_destinationIsIpv6": true,
1708 + "data_win_eventdata_destinationPort": true,
1709 + "data_win_eventdata_destinationPortName": true,
1710 + "data_win_eventdata_details": true,
1711 + "data_win_eventdata_elevatedToken": true,
1712 + "data_win_eventdata_eventType": true,
1713 + "data_win_eventdata_fileVersion": true,
1714 + "data_win_eventdata_fileVersion_city_name": true,
1715 + "data_win_eventdata_fileVersion_country_code": true,
1716 + "data_win_eventdata_fileVersion_geolocation": true,
1717 + "data_win_eventdata_grantedAccess": true,
1718 + "data_win_eventdata_hashes": true,
1719 + "data_win_eventdata_image": true,
1720 + "data_win_eventdata_imageLoaded": true,
1721 + "data_win_eventdata_impersonationLevel": true,
1722 + "data_win_eventdata_initiated": true,
1723 + "data_win_eventdata_integrityLevel": true,
1724 + "data_win_eventdata_ipAddress": true,
1725 + "data_win_eventdata_ipPort": true,
1726 + "data_win_eventdata_keyLength": true,
1727 + "data_win_eventdata_logonGuid": true,
1728 + "data_win_eventdata_logonId": true,
1729 + "data_win_eventdata_logonProcessName": true,
1730 + "data_win_eventdata_logonType": true,
1731 + "data_win_eventdata_originalFileName": true,
1732 + "data_win_eventdata_param1": true,
1733 + "data_win_eventdata_param2": true,
1734 + "data_win_eventdata_param3": true,
1735 + "data_win_eventdata_param4": true,
1736 + "data_win_eventdata_parentCommandLine": true,
1737 + "data_win_eventdata_parentImage": true,
1738 + "data_win_eventdata_parentProcessGuid": true,
1739 + "data_win_eventdata_parentProcessId": true,
1740 + "data_win_eventdata_processGuid": true,
1741 + "data_win_eventdata_processId": true,
1742 + "data_win_eventdata_processName": true,
1743 + "data_win_eventdata_product": true,
1744 + "data_win_eventdata_protocol": true,
1745 + "data_win_eventdata_queryName": true,
1746 + "data_win_eventdata_queryResults": true,
1747 + "data_win_eventdata_queryStatus": true,
1748 + "data_win_eventdata_ruleName": true,
1749 + "data_win_eventdata_serviceName": true,
1750 + "data_win_eventdata_serviceSid": true,
1751 + "data_win_eventdata_signature": true,
1752 + "data_win_eventdata_signatureStatus": true,
1753 + "data_win_eventdata_signed": true,
1754 + "data_win_eventdata_sourceHostname": true,
1755 + "data_win_eventdata_sourceImage": true,
1756 + "data_win_eventdata_sourceIp": true,
1757 + "data_win_eventdata_sourceIp_city_name": true,
1758 + "data_win_eventdata_sourceIp_country_code": true,
1759 + "data_win_eventdata_sourceIp_geolocation": true,
1760 + "data_win_eventdata_sourceIsIpv6": true,
1761 + "data_win_eventdata_sourcePort": true,
1762 + "data_win_eventdata_sourceProcessGUID": true,
1763 + "data_win_eventdata_sourceProcessId": true,
1764 + "data_win_eventdata_sourceThreadId": true,
1765 + "data_win_eventdata_status": true,
1766 + "data_win_eventdata_subjectDomainName": true,
1767 + "data_win_eventdata_subjectLogonId": true,
1768 + "data_win_eventdata_subjectUserName": true,
1769 + "data_win_eventdata_subjectUserSid": true,
1770 + "data_win_eventdata_targetDomainName": true,
1771 + "data_win_eventdata_targetFilename": true,
1772 + "data_win_eventdata_targetImage": true,
1773 + "data_win_eventdata_targetLinkedLogonId": true,
1774 + "data_win_eventdata_targetLogonId": true,
1775 + "data_win_eventdata_targetObject": true,
1776 + "data_win_eventdata_targetProcessGUID": true,
1777 + "data_win_eventdata_targetProcessId": true,
1778 + "data_win_eventdata_targetUserName": true,
1779 + "data_win_eventdata_targetUserSid": true,
1780 + "data_win_eventdata_terminalSessionId": true,
1781 + "data_win_eventdata_ticketEncryptionType": true,
1782 + "data_win_eventdata_ticketOptions": true,
1783 + "data_win_eventdata_user": true,
1784 + "data_win_eventdata_utcTime": true,
1785 + "data_win_eventdata_virtualAccount": true,
1786 + "data_win_system_channel": true,
1787 + "data_win_system_computer": true,
1788 + "data_win_system_eventID": true,
1789 + "data_win_system_eventRecordID": true,
1790 + "data_win_system_eventSourceName": true,
1791 + "data_win_system_keywords": true,
1792 + "data_win_system_level": true,
1793 + "data_win_system_message": true,
1794 + "data_win_system_opcode": true,
1795 + "data_win_system_processID": true,
1796 + "data_win_system_providerGuid": true,
1797 + "data_win_system_providerName": true,
1798 + "data_win_system_severityValue": true,
1799 + "data_win_system_systemTime": true,
1800 + "data_win_system_task": true,
1801 + "data_win_system_threadID": true,
1802 + "data_win_system_version": true,
1803 + "decoder_name": true,
1804 + "decoder_parent": true,
1805 + "dns_query": true,
1806 + "dns_query_threat_indicated": true,
1807 + "dst_ip": true,
1808 + "dst_ip_city_name": true,
1809 + "dst_ip_country_code": true,
1810 + "dst_ip_geolocation": true,
1811 + "dst_ip_threat_indicated": true,
1812 + "dst_port": true,
1813 + "ecs_version": true,
1814 + "error": true,
1815 + "event_hash": true,
1816 + "file_path": true,
1817 + "firewall_rule_name": true,
1818 + "full_log": false,
1819 + "gl2_accounted_message_size": true,
1820 + "gl2_message_id": true,
1821 + "gl2_processing_error": true,
1822 + "gl2_remote_ip": true,
1823 + "gl2_remote_port": true,
1824 + "gl2_source_collector": true,
1825 + "gl2_source_input": true,
1826 + "gl2_source_node": true,
1827 + "hash_md5": true,
1828 + "hash_sha1": true,
1829 + "hash_sha256": true,
1830 + "highlight": true,
1831 + "host_architecture": true,
1832 + "host_containerized": true,
1833 + "host_hostname": true,
1834 + "host_id": true,
1835 + "host_ip": true,
1836 + "host_mac": true,
1837 + "host_name": true,
1838 + "host_os_codename": true,
1839 + "host_os_kernel": true,
1840 + "host_os_name": true,
1841 + "host_os_platform": true,
1842 + "host_os_version": true,
1843 + "hostname": true,
1844 + "id": true,
1845 + "input_type": true,
1846 + "level": true,
1847 + "location": true,
1848 + "log_file_path": true,
1849 + "log_offset": true,
1850 + "manager_name": true,
1851 + "message": true,
1852 + "module": true,
1853 + "msg_timestamp": true,
1854 + "parent_process_cmd_line": true,
1855 + "parent_process_id": true,
1856 + "parent_process_image": true,
1857 + "pid": true,
1858 + "predecoder_hostname": true,
1859 + "predecoder_program_name": true,
1860 + "predecoder_timestamp": true,
1861 + "previous_log": true,
1862 + "previous_output": true,
1863 + "process_cmd_line": true,
1864 + "process_id": true,
1865 + "process_image": true,
1866 + "process_name": true,
1867 + "protocol": true,
1868 + "rule_cis": true,
1869 + "rule_cis_csc": true,
1870 + "rule_firedtimes": true,
1871 + "rule_gdpr": true,
1872 + "rule_gdpr_IV": true,
1873 + "rule_gpg13": true,
1874 + "rule_gpg_13": true,
1875 + "rule_group1": true,
1876 + "rule_groups": true,
1877 + "rule_hipaa": true,
1878 + "rule_id": true,
1879 + "rule_info": true,
1880 + "rule_mail": true,
1881 + "rule_mitre_id": true,
1882 + "rule_mitre_tactic": false,
1883 + "rule_nist_800_53": true,
1884 + "rule_pci_dss": true,
1885 + "rule_tsc": true,
1886 + "scanid": true,
1887 + "service": true,
1888 + "software_package": true,
1889 + "software_vendor": true,
1890 + "sort": true,
1891 + "source": true,
1892 + "src_ip": true,
1893 + "src_ip_city_name": true,
1894 + "src_ip_country_code": true,
1895 + "src_ip_geolocation": true,
1896 + "src_port": true,
1897 + "streams": true,
1898 + "syscheck_attrs_after": true,
1899 + "syscheck_audit_effective_user_id": true,
1900 + "syscheck_audit_effective_user_name": true,
1901 + "syscheck_audit_group_id": true,
1902 + "syscheck_audit_group_name": true,
1903 + "syscheck_audit_login_user_id": true,
1904 + "syscheck_audit_login_user_name": true,
1905 + "syscheck_audit_process_cwd": true,
1906 + "syscheck_audit_process_id": true,
1907 + "syscheck_audit_process_name": true,
1908 + "syscheck_audit_process_parent_cwd": true,
1909 + "syscheck_audit_process_parent_name": true,
1910 + "syscheck_audit_process_ppid": true,
1911 + "syscheck_audit_user_id": true,
1912 + "syscheck_audit_user_name": true,
1913 + "syscheck_changed_attributes": true,
1914 + "syscheck_event": true,
1915 + "syscheck_gid_after": true,
1916 + "syscheck_gname_after": true,
1917 + "syscheck_hard_links": true,
1918 + "syscheck_inode_after": true,
1919 + "syscheck_inode_before": true,
1920 + "syscheck_md5_after": true,
1921 + "syscheck_md5_before": true,
1922 + "syscheck_mode": true,
1923 + "syscheck_mtime_after": true,
1924 + "syscheck_mtime_before": true,
1925 + "syscheck_path": true,
1926 + "syscheck_perm_after": true,
1927 + "syscheck_perm_before": true,
1928 + "syscheck_sha1_after": true,
1929 + "syscheck_sha1_before": true,
1930 + "syscheck_sha256_after": true,
1931 + "syscheck_sha256_before": true,
1932 + "syscheck_size_after": true,
1933 + "syscheck_size_before": true,
1934 + "syscheck_uid_after": true,
1935 + "syscheck_uname_after": true,
1936 + "syscheck_win_perm_after": true,
1937 + "syscheck_win_perm_after_0_allowed": true,
1938 + "syscheck_win_perm_after_0_name": true,
1939 + "syscheck_win_perm_after_1_allowed": true,
1940 + "syscheck_win_perm_after_1_name": true,
1941 + "syscheck_win_perm_after_2_allowed": true,
1942 + "syscheck_win_perm_after_2_name": true,
1943 + "syscheck_win_perm_after_3_allowed": true,
1944 + "syscheck_win_perm_after_3_name": true,
1945 + "syslog_customer": true,
1946 + "syslog_level": true,
1947 + "syslog_tag": true,
1948 + "syslog_type": true,
1949 + "sysmon_event_description": true,
1950 + "threat_ids": true,
1951 + "threat_indicated": true,
1952 + "threat_names": true,
1953 + "time": true,
1954 + "timestamp": false,
1955 + "timestamp_utc": true,
1956 + "true": true,
1957 + "user_name": true,
1958 + "win_registry_key": true,
1959 + "win_system_eventID": true,
1960 + "windows_auth_package": true,
1961 + "windows_domain": true,
1962 + "windows_event_id": true,
1963 + "windows_event_severity": true,
1964 + "windows_logon_type": true
1965 + },
1966 + "indexByName": {
1967 + "_id": 2,
1968 + "_index": 10,
1969 + "_type": 11,
1970 + "agent_id": 12,
1971 + "agent_name": 5,
1972 + "cluster_name": 13,
1973 + "cluster_node": 14,
1974 + "data_office_365_Actor": 15,
1975 + "data_office_365_ActorContextId": 16,
1976 + "data_office_365_ActorIpAddress": 4,
1977 + "data_office_365_ActorIpAddress_city_name": 17,
1978 + "data_office_365_ActorIpAddress_country_code": 18,
1979 + "data_office_365_ActorIpAddress_geolocation": 19,
1980 + "data_office_365_ApplicationId": 20,
1981 + "data_office_365_AzureActiveDirectoryEventType": 21,
1982 + "data_office_365_ClientIP": 22,
1983 + "data_office_365_ClientIP_city_name": 23,
1984 + "data_office_365_ClientIP_country_code": 24,
1985 + "data_office_365_ClientIP_geolocation": 25,
1986 + "data_office_365_CreationTime": 0,
1987 + "data_office_365_DeviceProperties": 26,
1988 + "data_office_365_ErrorNumber": 27,
1989 + "data_office_365_ExtendedProperties": 28,
1990 + "data_office_365_Id": 29,
1991 + "data_office_365_InterSystemsId": 30,
1992 + "data_office_365_IntraSystemId": 31,
1993 + "data_office_365_LogonError": 32,
1994 + "data_office_365_ModifiedProperties": 33,
1995 + "data_office_365_ObjectId": 34,
1996 + "data_office_365_Operation": 42,
1997 + "data_office_365_OrganizationId": 35,
1998 + "data_office_365_RecordType": 36,
1999 + "data_office_365_ResultStatus": 6,
2000 + "data_office_365_Target": 37,
2001 + "data_office_365_TargetContextId": 38,
2002 + "data_office_365_UserId": 3,
2003 + "data_office_365_UserKey": 39,
2004 + "data_office_365_UserType": 40,
2005 + "data_office_365_Version": 41,
2006 + "data_office_365_Workload": 66,
2007 + "decoder_name": 43,
2008 + "gl2_accounted_message_size": 44,
2009 + "gl2_message_id": 45,
2010 + "gl2_processing_error": 46,
2011 + "gl2_remote_ip": 47,
2012 + "gl2_remote_port": 48,
2013 + "gl2_source_input": 49,
2014 + "gl2_source_node": 50,
2015 + "highlight": 51,
2016 + "id": 52,
2017 + "location": 53,
2018 + "manager_name": 54,
2019 + "message": 55,
2020 + "rule_description": 7,
2021 + "rule_firedtimes": 56,
2022 + "rule_group1": 57,
2023 + "rule_groups": 58,
2024 + "rule_id": 9,
2025 + "rule_level": 8,
2026 + "rule_mail": 59,
2027 + "sort": 60,
2028 + "source": 61,
2029 + "streams": 62,
2030 + "syslog_level": 63,
2031 + "syslog_type": 64,
2032 + "timestamp": 1,
2033 + "true": 65
2034 + },
2035 + "renameByName": {
2036 + "_id": "EVENT ID",
2037 + "agent_name": "",
2038 + "data_errDetails": "ERROR",
2039 + "data_errMessage": "ERROR MSG",
2040 + "data_ip": "IP",
2041 + "data_ip_country_code": "COUNTRY",
2042 + "data_message": "MSG",
2043 + "data_office_365_ActorIpAddress": "SRC IP",
2044 + "data_office_365_CreationTime": "EVENT TIME",
2045 + "data_office_365_DeviceProperties_BrowserType": "BROWSER",
2046 + "data_office_365_DeviceProperties_DisplayName": "HOSTNAME",
2047 + "data_office_365_DeviceProperties_OS": "OS",
2048 + "data_office_365_ExtendedProperties_RequestType": "REQ TYPE",
2049 + "data_office_365_ExtendedProperties_ResultStatusDetail": "RESULT",
2050 + "data_office_365_ExtendedProperties_UserAgent": "AGENT",
2051 + "data_office_365_ExtendedProperties_UserAuthenticationMethod": "AUTH METHOD",
2052 + "data_office_365_ResultStatus": "RESULT",
2053 + "data_office_365_UserId": "ACCOUNT",
2054 + "data_serverIP": "SERVER IP",
2055 + "data_serverIP_country_code": "SERVER COUNTRY",
2056 + "data_serverIP_geolocation": "",
2057 + "data_status_code": "",
2058 + "data_userAgent_browser": "BROWSER",
2059 + "data_userAgent_os": "OS",
2060 + "rule_description": "DESCRIPTION",
2061 + "rule_id": "RULE ID",
2062 + "rule_level": "",
2063 + "rule_mitre_tactic": "MITRE TACTIC",
2064 + "rule_mitre_technique": "MITRE TECHNIQUE",
2065 + "timestamp": "Date/Time"
2066 + }
2067 + }
2068 + }
2069 + ],
2070 + "transparent": true,
2071 + "type": "table"
2072 + }
2073 + ],
2074 + "refresh": "",
2075 + "schemaVersion": 39,
2076 + "tags": ["SAP"],
2077 + "templating": {
2078 + "list": [
2079 + {
2080 + "datasource": {
2081 + "type": "grafana-opensearch-datasource",
2082 + "uid": "replace_datasource_uid"
2083 + },
2084 + "filters": [],
2085 + "hide": 0,
2086 + "label": "",
2087 + "name": "Filters",
2088 + "skipUrlSync": false,
2089 + "type": "adhoc"
2090 + },
2091 + {
2092 + "current": {
2093 + "selected": true,
2094 + "text": ["All"],
2095 + "value": ["$__all"]
2096 + },
2097 + "datasource": {
2098 + "type": "grafana-opensearch-datasource",
2099 + "uid": "replace_datasource_uid"
2100 + },
2101 + "definition": "{ \"find\": \"terms\", \"field\": \"logSource\", \"query\": \"\"}",
2102 + "hide": 1,
2103 + "includeAll": true,
2104 + "label": "LOG SOURCE",
2105 + "multi": true,
2106 + "name": "log_source",
2107 + "options": [],
2108 + "query": "{ \"find\": \"terms\", \"field\": \"logSource\", \"query\": \"\"}",
2109 + "refresh": 1,
2110 + "regex": "",
2111 + "skipUrlSync": false,
2112 + "sort": 0,
2113 + "type": "query"
2114 + }
2115 + ]
2116 + },
2117 + "time": {
2118 + "from": "now-24h",
2119 + "to": "now"
2120 + },
2121 + "timepicker": {
2122 + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"],
2123 + "time_options": ["5m", "15m", "1h", "6h", "12h", "24h", "2d", "7d", "30d"]
2124 + },
2125 + "timezone": "",
2126 + "title": "SAP - USERS AUTH",
2127 + "version": 5,
2128 + "weekStart": ""
2129 +}
backend/app/connectors/grafana/schema/dashboards.py
+7 -1
@@ -66,6 +66,10 @@ class MimecastDashboard(Enum):
66 SUMMARY = ("Mimecast", "summary.json")
67
68
69 +class SapSiemDashboard(Enum):
70 + USERS_AUTH = ("SapSiem", "users_auth.json")
71 +
72 +
73 class DashboardProvisionRequest(BaseModel):
74 dashboards: List[str] = Field(
75 ...,
@@ -83,7 +87,9 @@ class DashboardProvisionRequest(BaseModel):
87
88 @validator("dashboards", each_item=True)
89 def check_dashboard_exists(cls, e):
86 - valid_dashboards = {item.name: item for item in list(WazuhDashboard) + list(Office365Dashboard)}
90 + valid_dashboards = {
91 + item.name: item for item in list(WazuhDashboard) + list(Office365Dashboard) + list(MimecastDashboard) + list(SapSiemDashboard)
92 + }
93 if e not in valid_dashboards:
94 raise ValueError(f'Dashboard identifier "{e}" is not recognized.')
95 return e
backend/app/connectors/grafana/services/dashboards.py
+4 -1
@@ -9,6 +9,7 @@ from app.connectors.grafana.schema.dashboards import GrafanaDashboard
9 from app.connectors.grafana.schema.dashboards import GrafanaDashboardResponse
10 from app.connectors.grafana.schema.dashboards import MimecastDashboard
11 from app.connectors.grafana.schema.dashboards import Office365Dashboard
12 +from app.connectors.grafana.schema.dashboards import SapSiemDashboard
13 from app.connectors.grafana.schema.dashboards import WazuhDashboard
14 from app.connectors.grafana.utils.universal import create_grafana_client
15
@@ -144,7 +145,9 @@ async def provision_dashboards(
145 provisioned_dashboards = []
146 errors = []
147
147 - valid_dashboards = {item.name: item for item in list(WazuhDashboard) + list(Office365Dashboard) + list(MimecastDashboard)}
148 + valid_dashboards = {
149 + item.name: item for item in list(WazuhDashboard) + list(Office365Dashboard) + list(MimecastDashboard) + list(SapSiemDashboard)
150 + }
151
152 for dashboard_name in dashboard_request.dashboards:
153 dashboard_enum = valid_dashboards[dashboard_name]
backend/app/db/all_models.py
+1
@@ -13,3 +13,4 @@ from app.integrations.alert_creation_settings.models.alert_creation_settings imp
13 from app.integrations.models.customer_integration_settings import CustomerIntegrations
14 from app.schedulers.models.scheduler import JobMetadata
15 from app.integrations.monitoring_alert.models.monitoring_alert import MonitoringAlerts
16 +from app.integrations.sap_siem.models.sap_siem import SapSiemMultipleLogins
backend/app/db/db_populate.py
+8 -10
@@ -129,16 +129,6 @@ def get_connectors_list():
129 "Connection to Graylog GELF Input to receive events from integrations. Make sure you have created a GELF Input in Graylog.",
130 "GELF_INPUT_PORT",
131 ),
132 - (
133 - "Alert Creation Provisioning",
134 - "3",
135 - "host_only",
136 - (
137 - "Connection to Alert Creation Provisioning. Make sure you have "
138 - "deployed the Alert Creation Provisioning Application provided by "
139 - "SOCFortress: https://github.com/socfortress/Customer-Provisioning-Alert"
140 - ),
141 - ),
132 # ... Add more connectors as needed ...
133 ]
134
@@ -238,6 +228,9 @@ def load_markdown_for_integration(integration_name: str) -> str:
228 str: The content of the markdown file.
229 """
230 # file_path = os.path.join("integrations_markdown", f"{integration_name.lower()}.md")
231 + # if space in the integration name, replace it with underscore
232 + if " " in integration_name:
233 + integration_name = integration_name.replace(" ", "_")
234 file_path = os.path.join(
235 "app",
236 "integrations",
@@ -261,6 +254,7 @@ def get_available_integrations_list():
254 available_integrations = [
255 ("Office365", "Integrate Office365 with SOCFortress."),
256 ("Mimecast", "Integrate Mimecast with SOCFortress."),
257 + ("SAP SIEM", "Integrate SAP SIEM with SOCFortress."),
258 # ... Add more available integrations as needed ...
259 ]
260
@@ -350,6 +344,10 @@ async def get_available_integrations_auth_keys_list(session: AsyncSession):
344 ("Mimecast", "EMAIL_ADDRESS"),
345 ("Mimecast", "ACCESS_KEY"),
346 ("Mimecast", "SECRET_KEY"),
347 + ("SAP SIEM", "API_KEY"),
348 + ("SAP SIEM", "SECRET_KEY"),
349 + ("SAP SIEM", "USER_KEY"),
350 + ("SAP SIEM", "API_DOMAIN"),
351 # ... Add more available integrations auth keys as needed ...
352 ]
353
backend/app/db/db_setup.py
+30
@@ -1,4 +1,6 @@
1 from loguru import logger
2 +from sqlalchemy import text
3 +from sqlalchemy.exc import OperationalError
4 from sqlalchemy.ext.asyncio import AsyncSession
5
6 # ! New with Async
@@ -33,6 +35,34 @@ async def create_tables(async_engine):
35 await add_connectors_if_not_exist(session)
36
37
38 +async def update_tables(async_engine):
39 + """
40 + Updates tables in the database. Needed for adding new columns to existing tables.
41 +
42 + Args:
43 + async_engine (AsyncEngine): The async engine to connect to the database.
44 +
45 + Returns:
46 + None
47 + """
48 + logger.info("Updating tables")
49 +
50 + # Define the new columns to be added
51 + new_columns = {"scheduled_job_metadata": ["extra_data TEXT"]}
52 +
53 + async with async_engine.begin() as conn:
54 + for table_name, columns in new_columns.items():
55 + for column in columns:
56 + alter_table_query = text(f"ALTER TABLE {table_name} ADD COLUMN {column}")
57 + try:
58 + await conn.execute(alter_table_query)
59 + except OperationalError as e:
60 + if "duplicate column name" in str(e):
61 + logger.info(f"Column {column} already exists in {table_name}")
62 + else:
63 + raise
64 +
65 +
66 async def create_roles(async_engine):
67 """
68 Creates roles in the database.
backend/app/integrations/markdown/sap_siem.md new
+1
@@ -0,0 +1 @@
1 +# [SAP SIEM](https://help.sap.com/docs/SAP_CUSTOMER_DATA_CLOUD/8b8d6fffe113457094a17701f63e3d6a/4143815a70b21014bbc5a10ce4041860.html)
backend/app/integrations/mimecast/routes/mimecast.py
+3 -3
@@ -13,7 +13,7 @@ from app.integrations.mimecast.schema.mimecast import MimecastTTPURLSRequest
13 from app.integrations.mimecast.services.mimecast import get_ttp_urls
14 from app.integrations.mimecast.services.mimecast import invoke_mimecast
15 from app.integrations.routes import find_customer_integration
16 -from app.integrations.utils.utils import extract_mimecast_auth_keys
16 +from app.integrations.utils.utils import extract_auth_keys
17 from app.integrations.utils.utils import get_customer_integration_response
18
19 integration_mimecast_router = APIRouter()
@@ -57,7 +57,7 @@ async def invoke_mimecast_route(
57 customer_integration_response,
58 )
59
60 - mimecast_auth_keys = extract_mimecast_auth_keys(customer_integration)
60 + mimecast_auth_keys = extract_auth_keys(customer_integration, service_name="Mimecast")
61
62 auth_keys = MimecastAuthKeys(**mimecast_auth_keys)
63
@@ -87,7 +87,7 @@ async def mimecast_ttp_url_route(
87 customer_integration_response,
88 )
89
90 - mimecast_auth_keys = extract_mimecast_auth_keys(customer_integration)
90 + mimecast_auth_keys = extract_auth_keys(customer_integration, service_name="Mimecast")
91
92 auth_keys = MimecastAuthKeys(**mimecast_auth_keys)
93
backend/app/integrations/monitoring_alert/routes/monitoring_alert.py
+75
@@ -1,4 +1,5 @@
1 from typing import List
2 +from typing import Optional
3
4 from fastapi import APIRouter
5 from fastapi import Depends
@@ -27,6 +28,12 @@ from app.integrations.monitoring_alert.schema.monitoring_alert import (
28 )
29 from app.integrations.monitoring_alert.services.suricata import analyze_suricata_alerts
30 from app.integrations.monitoring_alert.services.wazuh import analyze_wazuh_alerts
31 +from app.integrations.sap_siem.services.sap_siem_multiple_logins import (
32 + sap_siem_multiple_logins_same_ip,
33 +)
34 +from app.integrations.sap_siem.services.sap_siem_suspicious_logins import (
35 + sap_siem_suspicious_logins,
36 +)
37
38 monitoring_alerts_router = APIRouter()
39
@@ -222,3 +229,71 @@ async def run_suricata_analysis(
229 success=True,
230 message="Analysis completed successfully",
231 )
232 +
233 +
234 +@monitoring_alerts_router.post(
235 + "/run_analysis/sap_siem/suspicious_logins",
236 + response_model=AlertAnalysisResponse,
237 +)
238 +async def run_sap_siem_suspicious_logins_analysis(
239 + threshold: Optional[int] = 3,
240 + session: AsyncSession = Depends(get_db),
241 +) -> AlertAnalysisResponse:
242 + """
243 + This route is used to run analysis on the monitoring alerts.
244 +
245 + 1. Get all the monitoring alerts from the database where the customer_code matches the customer_code provided
246 + and the alert_source is SAP SIEM.
247 +
248 + 2. Call the sap_siem_suspicious_logins function to analyze the alerts.
249 +
250 + Args:
251 + request (CollectSapSiemRequest): The customer code.
252 + session (AsyncSession, optional): The database session. Defaults to Depends(get_db).
253 +
254 + Returns:
255 + WazuhAnalysisResponse: The response containing the analysis results.
256 + """
257 + logger.info("Running analysis for SAP SIEM suspicious logins")
258 +
259 + # Call the analyze_wazuh_alerts function to analyze the alerts
260 + await sap_siem_suspicious_logins(threshold=threshold, session=session)
261 +
262 + return AlertAnalysisResponse(
263 + success=True,
264 + message="Analysis completed successfully",
265 + )
266 +
267 +
268 +@monitoring_alerts_router.post(
269 + "/run_analysis/sap_siem/multiple_logins",
270 + response_model=AlertAnalysisResponse,
271 +)
272 +async def run_sap_siem_multiple_logins_same_ip_analysis(
273 + threshold: Optional[int] = 1,
274 + session: AsyncSession = Depends(get_db),
275 +) -> AlertAnalysisResponse:
276 + """
277 + This route is used to run analysis on the monitoring alerts.
278 +
279 + 1. Get all the monitoring alerts from the database where the customer_code matches the customer_code provided
280 + and the alert_source is SAP SIEM.
281 +
282 + 2. Call the sap_siem_multiple_logins_same_ip function to analyze the alerts.
283 +
284 + Args:
285 + request (CollectSapSiemRequest): The customer code.
286 + session (AsyncSession, optional): The database session. Defaults to Depends(get_db).
287 +
288 + Returns:
289 + WazuhAnalysisResponse: The response containing the analysis results.
290 + """
291 + logger.info("Running analysis for SAP SIEM multiple logins")
292 +
293 + # Call the analyze_wazuh_alerts function to analyze the alerts
294 + await sap_siem_multiple_logins_same_ip(threshold=threshold, session=session)
295 +
296 + return AlertAnalysisResponse(
297 + success=True,
298 + message="Analysis completed successfully",
299 + )
backend/app/integrations/monitoring_alert/routes/provision.py
+1 -1
@@ -152,7 +152,7 @@ async def provision_monitoring_alert_testing_route(
152 Used for testing purposes.
153 """
154 message = EventShipperPayload(
155 - customer_code="replace_me",
155 + customer_code="00002",
156 integration="testing",
157 version="1.0",
158 **request,
backend/app/integrations/monitoring_alert/services/suricata.py
-3
@@ -70,9 +70,6 @@ async def construct_alert_source_link(
70 """
71 logger.info(f"Constructing alert source link for alert: {alert_details}")
72 query_string = f"%22query%22:%22alert_signature_id:%5C%22{alert_details.alert_id}%5C%22%20AND%20"
73 - # ! TODO: REMOVE ONCE TESTING IS COMPLETE
74 - if alert_details.agent_labels_customer == "WCPS":
75 - alert_details.agent_labels_customer = "00002"
73 grafana_url = (
74 await get_customer_alert_settings(
75 customer_code=alert_details.agent_labels_customer,
backend/app/integrations/routes.py
-1
@@ -613,7 +613,6 @@ async def get_customer_integrations_by_customer_code(
613 )
614 result = await session.execute(stmt)
615 customer_integrations = result.scalars().unique().all()
616 - logger.info(f"customer_integrations: {customer_integrations}")
616 return CustomerIntegrationsResponse(
617 available_integrations=customer_integrations,
618 message="Customer integrations successfully retrieved.",
backend/app/integrations/sap_siem/models/sap_siem.py new
+23
@@ -0,0 +1,23 @@
1 +from datetime import datetime
2 +from typing import Optional
3 +
4 +from sqlmodel import Field
5 +from sqlmodel import SQLModel
6 +
7 +
8 +class SapSiemMultipleLogins(SQLModel, table=True):
9 + """
10 + Represents the SAP SIEM multiple logins table.
11 + Table is used to track when an IP has successfully logged in with multiple loginIDs.
12 + Used in the SAP SIEM integration.
13 + """
14 +
15 + __tablename__ = "sap_siem_multiple_logins"
16 + id: Optional[int] = Field(primary_key=True)
17 + ip: str = Field(description="The IP involved in the case.")
18 + last_case_created_timestamp: datetime = Field(
19 + description="Timestamp of the last case created.",
20 + )
21 + associated_loginIDs: str = Field(
22 + description="Comma-separated loginIDs associated with this IP.",
23 + )
backend/app/integrations/sap_siem/routes/provision.py new
+65
@@ -0,0 +1,65 @@
1 +from fastapi import APIRouter
2 +from fastapi import Depends
3 +from sqlalchemy.ext.asyncio import AsyncSession
4 +
5 +from app.db.db_session import get_db
6 +from app.integrations.sap_siem.schema.provision import ProvisionSapSiemRequest
7 +from app.integrations.sap_siem.schema.provision import ProvisionSapSiemResponse
8 +from app.integrations.sap_siem.services.provision import provision_sap_siem
9 +from app.integrations.utils.utils import get_customer_integration_response
10 +from app.schedulers.models.scheduler import CreateSchedulerRequest
11 +from app.schedulers.scheduler import add_scheduler_jobs
12 +
13 +integration_sap_siem_provision_scheduler_router = APIRouter()
14 +
15 +
16 +@integration_sap_siem_provision_scheduler_router.post(
17 + "/provision",
18 + response_model=ProvisionSapSiemResponse,
19 + description="Provision a SAP SIEM integration.",
20 +)
21 +async def provision_sap_siem_route(
22 + provision_sap_siem_request: ProvisionSapSiemRequest,
23 + session: AsyncSession = Depends(get_db),
24 +) -> ProvisionSapSiemResponse:
25 + """
26 + Provisions a mimecast integration.
27 +
28 + Args:
29 + provision_sap_siem_request (ProvisionSapSiemRequest): The request object containing the necessary data for provisioning.
30 + session (AsyncSession, optional): The database session. Defaults to Depends(get_db).
31 +
32 + Returns:
33 + ProvisionMimecastResponse: The response object indicating the success or failure of the provisioning process.
34 + """
35 + # Check if the customer integration settings are available and can be provisioned
36 + await get_customer_integration_response(
37 + provision_sap_siem_request.customer_code,
38 + session,
39 + )
40 + await provision_sap_siem(provision_sap_siem_request, session)
41 + await add_scheduler_jobs(
42 + CreateSchedulerRequest(
43 + function_name="invoke_sap_siem_integration_collection",
44 + time_interval=provision_sap_siem_request.time_interval,
45 + job_id="invoke_sap_siem_integration_collection",
46 + ),
47 + )
48 + await add_scheduler_jobs(
49 + CreateSchedulerRequest(
50 + function_name="invoke_sap_siem_integration_suspicious_logins_analysis",
51 + time_interval=provision_sap_siem_request.time_interval,
52 + job_id="invoke_sap_siem_integration_suspicious_logins_analysis",
53 + ),
54 + )
55 + await add_scheduler_jobs(
56 + CreateSchedulerRequest(
57 + function_name="invoke_sap_siem_integration_multiple_logins_same_ip_analysis",
58 + time_interval=provision_sap_siem_request.time_interval,
59 + job_id="invoke_sap_siem_integration_multiple_logins_same_ip_analysis",
60 + ),
61 + )
62 + return ProvisionSapSiemResponse(
63 + success=True,
64 + message="SAP SIEM integration provisioned successfully.",
65 + )
backend/app/integrations/sap_siem/routes/sap_siem.py new
+71
@@ -0,0 +1,71 @@
1 +from fastapi import APIRouter
2 +from fastapi import Depends
3 +from loguru import logger
4 +from sqlalchemy.ext.asyncio import AsyncSession
5 +
6 +from app.db.db_session import get_db
7 +from app.integrations.routes import find_customer_integration
8 +from app.integrations.sap_siem.schema.sap_siem import CollectSapSiemRequest
9 +from app.integrations.sap_siem.schema.sap_siem import InvokeSapSiemRequest
10 +from app.integrations.sap_siem.schema.sap_siem import InvokeSAPSiemResponse
11 +from app.integrations.sap_siem.schema.sap_siem import SapSiemAuthKeys
12 +from app.integrations.sap_siem.services.collect import collect_sap_siem
13 +from app.integrations.utils.utils import extract_auth_keys
14 +from app.integrations.utils.utils import get_customer_integration_response
15 +
16 +integration_sap_siem_router = APIRouter()
17 +
18 +
19 +@integration_sap_siem_router.post(
20 + "",
21 + response_model=InvokeSAPSiemResponse,
22 + description="Pull down SAP SIEM Events.",
23 +)
24 +async def collect_sap_siem_route(sap_siem_request: InvokeSapSiemRequest, session: AsyncSession = Depends(get_db)):
25 + """Pull down SAP SIEM Events."""
26 + customer_integration_response = await get_customer_integration_response(
27 + sap_siem_request.customer_code,
28 + session,
29 + )
30 +
31 + customer_integration = await find_customer_integration(
32 + sap_siem_request.customer_code,
33 + sap_siem_request.integration_name,
34 + customer_integration_response,
35 + )
36 +
37 + sap_siem_auth_keys = extract_auth_keys(customer_integration, service_name="SAP SIEM")
38 +
39 + logger.info(f"SAP SIEM Auth Keys: {sap_siem_auth_keys}")
40 +
41 + auth_keys = SapSiemAuthKeys(**sap_siem_auth_keys)
42 + # if multiple apiKey values are present, make a loop to iterate through them
43 + # and collect the data for each apiKey
44 + if "," in auth_keys.API_KEY:
45 + api_keys = auth_keys.API_KEY.split(",")
46 + for key in api_keys:
47 + collect_sap_siem_request = CollectSapSiemRequest(
48 + apiKey=key,
49 + secretKey=auth_keys.SECRET_KEY,
50 + userKey=auth_keys.USER_KEY,
51 + apiDomain=auth_keys.API_DOMAIN,
52 + threshold=sap_siem_request.threshold,
53 + lower_bound=sap_siem_request.lower_bound,
54 + upper_bound=sap_siem_request.upper_bound,
55 + customer_code=sap_siem_request.customer_code,
56 + )
57 + await collect_sap_siem(sap_siem_request=collect_sap_siem_request)
58 + else:
59 + collect_sap_siem_request = CollectSapSiemRequest(
60 + apiKey=auth_keys.API_KEY,
61 + secretKey=auth_keys.SECRET_KEY,
62 + userKey=auth_keys.USER_KEY,
63 + apiDomain=auth_keys.API_DOMAIN,
64 + threshold=sap_siem_request.threshold,
65 + lower_bound=sap_siem_request.lower_bound,
66 + upper_bound=sap_siem_request.upper_bound,
67 + customer_code=sap_siem_request.customer_code,
68 + )
69 + await collect_sap_siem(sap_siem_request=collect_sap_siem_request)
70 +
71 + return InvokeSAPSiemResponse(success=True, message="SAP SIEM Events collected successfully.")
backend/app/integrations/sap_siem/schema/provision.py new
+87
@@ -0,0 +1,87 @@
1 +from typing import Any
2 +from typing import Dict
3 +from typing import List
4 +from typing import Optional
5 +
6 +from pydantic import BaseModel
7 +from pydantic import Field
8 +from pydantic import root_validator
9 +
10 +
11 +class ProvisionSapSiemRequest(BaseModel):
12 + customer_code: str = Field(
13 + ...,
14 + description="The customer code.",
15 + examples=["00002"],
16 + )
17 + time_interval: int = Field(
18 + ...,
19 + description="The time interval for the scheduler.",
20 + examples=[5],
21 + )
22 + integration_name: str = Field(
23 + "SAP SIEM",
24 + description="The integration name.",
25 + examples=["SAP SIEM"],
26 + )
27 +
28 + # ensure the `integration_name` is always set to "Mimecast"
29 + @root_validator(pre=True)
30 + def set_integration_name(cls, values: Dict[str, Any]) -> Dict[str, Any]:
31 + values["integration_name"] = "SAP SIEM"
32 + return values
33 +
34 +
35 +class ProvisionSapSiemResponse(BaseModel):
36 + success: bool
37 + message: str
38 +
39 +
40 +# ! STREAMS ! #
41 +class StreamRule(BaseModel):
42 + field: str
43 + type: int
44 + inverted: bool
45 + value: str
46 +
47 +
48 +class SapSiemEventStream(BaseModel):
49 + title: str = Field(..., description="Title of the stream")
50 + description: str = Field(..., description="Description of the stream")
51 + index_set_id: str = Field(..., description="ID of the associated index set")
52 + rules: List[StreamRule] = Field(..., description="List of rules for the stream")
53 + matching_type: str = Field(..., description="Matching type for the rules")
54 + remove_matches_from_default_stream: bool = Field(
55 + ...,
56 + description="Whether to remove matches from the default stream",
57 + )
58 + content_pack: Optional[str] = Field(
59 + None,
60 + description="Associated content pack, if any",
61 + )
62 +
63 + class Config:
64 + schema_extra = {
65 + "example": {
66 + "title": "SAP SIEM EVENTS - Example Company",
67 + "description": "SAP SIEM EVENTS - Example Company",
68 + "index_set_id": "12345",
69 + "rules": [
70 + {
71 + "field": "customer_code",
72 + "type": 1,
73 + "inverted": False,
74 + "value": "ExampleCode",
75 + },
76 + {
77 + "field": "integration",
78 + "type": 1,
79 + "inverted": False,
80 + "value": "sap_siem",
81 + },
82 + ],
83 + "matching_type": "AND",
84 + "remove_matches_from_default_stream": True,
85 + "content_pack": None,
86 + },
87 + }
backend/app/integrations/sap_siem/schema/sap_siem.py new
+383
@@ -0,0 +1,383 @@
1 +from datetime import datetime
2 +from datetime import timedelta
3 +from enum import Enum
4 +from typing import Dict
5 +from typing import List
6 +from typing import Optional
7 +
8 +from pydantic import BaseModel
9 +from pydantic import Field
10 +from pydantic import root_validator
11 +
12 +
13 +class InvokeSapSiemRequest(BaseModel):
14 + customer_code: str = Field(
15 + ...,
16 + description="The customer code.",
17 + examples=["00002"],
18 + )
19 + integration_name: str = Field(
20 + "SAP SIEM",
21 + description="The integration name.",
22 + examples=["SAP SIEM"],
23 + )
24 + threshold: Optional[int] = Field(
25 + 3,
26 + description="Number of 'Invalid LoginID' before the first 'OK'",
27 + )
28 + time_range: Optional[str] = Field(
29 + "15m",
30 + pattern="^[1-9][0-9]*[mhdw]$",
31 + description="Time range for the query (1m, 1h, 1d, 1w)",
32 + )
33 +
34 + lower_bound: str = None
35 + upper_bound: str = None
36 +
37 + @root_validator(pre=True)
38 + def set_time_bounds(cls, values):
39 + time_range = values.get("time_range")
40 + if time_range:
41 + unit = time_range[-1]
42 + amount = int(time_range[:-1])
43 +
44 + now = datetime.utcnow()
45 +
46 + if unit == "m":
47 + lower_bound = now - timedelta(minutes=amount)
48 + elif unit == "h":
49 + lower_bound = now - timedelta(hours=amount)
50 + elif unit == "d":
51 + lower_bound = now - timedelta(days=amount)
52 + elif unit == "w":
53 + lower_bound = now - timedelta(weeks=amount)
54 +
55 + values["lower_bound"] = lower_bound.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
56 + values["upper_bound"] = now.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
57 + return values
58 +
59 +
60 +class InvokeSAPSiemResponse(BaseModel):
61 + success: bool
62 + message: str
63 +
64 +
65 +class SapSiemAuthKeys(BaseModel):
66 + API_KEY: str = Field(
67 + ...,
68 + description="YOUR API KEY",
69 + examples=["3_yUWT3uDMs9E1N87r4Ey"],
70 + )
71 + SECRET_KEY: str = Field(
72 + ...,
73 + description="YOUR SECRET KEY",
74 + examples=["4ijD6uMCca"],
75 + )
76 + USER_KEY: Optional[str] = Field(
77 + None,
78 + description="YOUR USER KEY",
79 + examples=["AK9zAL"],
80 + )
81 + API_DOMAIN: str = Field(
82 + ...,
83 + description="YOUR API DOMAIN",
84 + examples=["audit.eu1.gigya.com"],
85 + )
86 +
87 +
88 +class CollectSapSiemRequest(BaseModel):
89 + customer_code: str = Field(
90 + ...,
91 + description="The customer code.",
92 + examples=["00002"],
93 + )
94 + apiKey: str = Field(..., description="API key for authorization")
95 + secretKey: str = Field(..., description="Secret key for authorization")
96 + userKey: str = Field(..., description="User key for identification")
97 + apiDomain: str = Field(..., description="API domain")
98 + threshold: Optional[int] = Field(
99 + 1,
100 + description="Number of 'Invalid LoginID' before the first 'OK'",
101 + )
102 + lower_bound: str = None
103 + upper_bound: str = None
104 +
105 +
106 +######### ! SAP API RESPONSE ! #########
107 +class HttpReq(BaseModel):
108 + SDK: str = Field(
109 + ...,
110 + description="Software Development Kit used for the HTTP request",
111 + )
112 + country: str = Field(..., description="Country code")
113 +
114 +
115 +class Params(BaseModel):
116 + clientContext: Optional[str] = Field(None, description="Client context information")
117 + include: Optional[str] = Field(None, description="Data to include in the response")
118 + password: str = Field(..., description="Password for the user")
119 + loginID: str = Field(..., description="Login ID of the user")
120 + apiKey: str = Field(..., description="API key for authorization")
121 + format: Optional[str] = Field(None, description="Response format")
122 + secret: Optional[str] = Field(None, description="Secret key for authorization")
123 + userKey: Optional[str] = Field(None, description="User key for identification")
124 +
125 +
126 +class UserAgent(BaseModel):
127 + os: str = Field(..., description="Operating system of the user")
128 + browser: str = Field(..., description="Browser used by the user")
129 + raw: Optional[str] = Field(None, description="Raw user agent string")
130 + version: str = Field(..., description="Browser version")
131 + platform: str = Field(..., description="Platform type (desktop/mobile)")
132 +
133 +
134 +class UserKeyDetails(BaseModel):
135 + name: Optional[str] = Field(None, description="Name of the user")
136 + emailDomain: Optional[str] = Field(None, description="Email domain of the user")
137 +
138 +
139 +class Restrictions(BaseModel):
140 + ipWhitelistRestricted: bool = Field(
141 + ...,
142 + description="Is IP whitelisting restricted",
143 + )
144 + ipWhitelistRestrictionEnforced: bool = Field(
145 + ...,
146 + description="Is IP whitelist restriction enforced",
147 + )
148 + ipBlacklistRestricted: bool = Field(
149 + ...,
150 + description="Is IP blacklisting restricted",
151 + )
152 + ipBlacklistRestrictionEnforced: bool = Field(
153 + ...,
154 + description="Is IP blacklist restriction enforced",
155 + )
156 +
157 +
158 +class Result(BaseModel):
159 + callID: str = Field(..., description="Unique identifier for the call")
160 + authType: Optional[str] = Field(None, description="Type of authentication used")
161 + timestamp: str = Field(
162 + ...,
163 + alias="@timestamp",
164 + description="Timestamp of the event",
165 + )
166 + errCode: str = Field(..., description="Error code")
167 + errDetails: Optional[str] = Field(None, description="Detailed error message")
168 + errMessage: str = Field(..., description="Error message")
169 + endpoint: str = Field(..., description="API endpoint hit")
170 + userKey: Optional[str] = Field(None, description="User key for identification")
171 + httpReq: HttpReq = Field(..., description="HTTP request details")
172 + ip: str = Field(..., description="IP address of the user")
173 + serverIP: str = Field(..., description="Server IP address")
174 + params: Params = Field(..., description="Parameters passed in the request")
175 + uid: Optional[str] = Field(
176 + "No uid found",
177 + description="Unique identifier for the user",
178 + )
179 + apikey: str = Field(..., description="API key used for the request")
180 + userAgent: UserAgent = Field(..., description="User agent details")
181 + userKeyDetails: Optional[UserKeyDetails] = Field(
182 + None,
183 + description="Details related to user key",
184 + )
185 + XffFirstIp: str = Field(..., description="First IP in the X-Forwarded-For header")
186 + restrictions: Restrictions = Field(..., description="IP restrictions")
187 + riskScore: Optional[str] = Field(
188 + None,
189 + description="Risk score associated with the request",
190 + )
191 + event_timestamp: Optional[datetime] = Field(
192 + None,
193 + description="Timestamp of the event",
194 + )
195 + case_created: Optional[str] = Field(
196 + "False",
197 + description="Whether a case has been created for the event",
198 + )
199 + event_analyzed: Optional[str] = Field(
200 + "False",
201 + description="Whether the event has been analyzed",
202 + )
203 + event_analyzed_multiple_logins: Optional[str] = Field(
204 + "False",
205 + description="Whether the event has been analyzed for multiple logins",
206 + )
207 +
208 +
209 +class SapSiemResponseBody(BaseModel):
210 + results: List[Result] = Field(..., description="List of result objects")
211 + totalCount: int = Field(..., description="Total count of results")
212 + statusCode: int = Field(..., description="Status code of the response")
213 + errorCode: int = Field(..., description="Error code of the response")
214 + statusReason: str = Field(..., description="Status reason of the response")
215 + callId: str = Field(..., description="Unique identifier for the overall call")
216 + time: str = Field(..., description="Time of the response")
217 + objectsCount: int = Field(..., description="Count of objects in results")
218 +
219 +
220 +#### ! WAZUH INDEXER RESULTS ! ####
221 +
222 +
223 +class SapSiemSource(BaseModel):
224 + logSource: Optional[str] = Field(None, description="The source of the log")
225 + params_loginID: str = Field(..., description="The login ID of the user")
226 + errCode: str = Field(..., description="The error code")
227 + ip: str = Field(..., description="The IP address of the user")
228 + httpReq_country: str = Field(..., description="The country from which the HTTP request originated")
229 + event_timestamp: str = Field(..., description="The timestamp of the event")
230 + errMessage: Optional[str] = Field(None, description="The error message")
231 + customer_code: str = Field(..., description="The customer code")
232 + errDetails: Optional[str] = Field(None, description="Detailed error message")
233 +
234 +
235 +class SapSiemHit(BaseModel):
236 + index: str = Field(..., description="The index of the hit", alias="_index")
237 + id: str = Field(..., description="The ID of the hit", alias="_id")
238 + score: Optional[float] = Field(None, description="The score of the hit", alias="_score")
239 + source: SapSiemSource = Field(..., description="The source data of the hit", alias="_source")
240 + sort: Optional[List[int]] = Field(None, description="The sort order of the hit")
241 +
242 +
243 +class SapSiemTotal(BaseModel):
244 + value: int = Field(..., description="The total number of hits")
245 + relation: str = Field(..., description="The relation of the total hits")
246 +
247 +
248 +class SapSiemHits(BaseModel):
249 + total: SapSiemTotal = Field(..., description="The total hits data")
250 + max_score: Optional[float] = Field(None, description="The maximum score among the hits")
251 + hits: List[SapSiemHit] = Field(..., description="The list of hits")
252 +
253 +
254 +class SapSiemShards(BaseModel):
255 + total: int = Field(..., description="The total number of shards")
256 + successful: int = Field(..., description="The number of successful shards")
257 + skipped: int = Field(..., description="The number of skipped shards")
258 + failed: int = Field(..., description="The number of failed shards")
259 +
260 +
261 +class SapSiemWazuhIndexerResponse(BaseModel):
262 + scroll_id: Optional[str] = Field(None, description="The scroll ID", alias="_scroll_id")
263 + took: int = Field(..., description="The time it took to execute the request")
264 + timed_out: bool = Field(..., description="Whether the request timed out")
265 + shards: SapSiemShards = Field(..., description="The shards data", alias="_shards")
266 + hits: SapSiemHits = Field(..., description="The hits data")
267 +
268 +
269 +class SuspiciousLogin(BaseModel):
270 + customer_code: str
271 + logSource: Optional[str] = Field(None)
272 + loginID: str
273 + country: Optional[str]
274 + ip: str
275 + event_timestamp: str
276 + errMessage: str
277 + index: Optional[str] = Field(None, description="The index of the hit", alias="_index")
278 + id: Optional[str] = Field(None, description="The ID of the hit", alias="_id")
279 + errDetails: Optional[str] = Field(None, description="Detailed error message")
280 +
281 +
282 +class ErrCode(Enum):
283 + """
284 + Error codes for SAP SIEM
285 + """
286 +
287 + INVALID_LOGIN_ID = "403042" # Invalid LoginID
288 + IP_BLOCKED = "403051" # IP is blocked
289 + ACCOUNT_TEMPORARILY_LOCKED = "403120" # Account temporarily locked
290 + OK = "0" # Successful login
291 +
292 +
293 +################# ! IRIS CASE CREATION SCHEMA ! #################
294 +class IrisCasePayload(BaseModel):
295 + case_name: str = Field(..., description="The name of the case.")
296 + case_description: str = Field(..., description="The description of the case.")
297 + case_customer: int = Field(1, description="The customer of the case.")
298 + case_classification: int = Field(
299 + 1,
300 + description="The classification of the case.",
301 + )
302 + soc_id: str = Field("1", description="The SOC ID of the case.")
303 + custom_attributes: Optional[Dict] = Field(
304 + None,
305 + description="The custom attributes of the case.",
306 + )
307 + create_customer: bool = Field(
308 + False,
309 + description="The create customer flag of the case.",
310 + )
311 +
312 + def to_dict(self):
313 + return self.dict(exclude_none=True)
314 +
315 +
316 +class ModificationHistoryEntry(BaseModel):
317 + user: str
318 + user_id: int
319 + action: str
320 +
321 +
322 +class CaseData(BaseModel):
323 + case_id: int
324 + open_date: str
325 + modification_history: Dict[str, ModificationHistoryEntry]
326 + close_date: Optional[str]
327 + case_description: str
328 + classification_id: int
329 + case_soc_id: str
330 + case_name: str
331 + custom_attributes: Optional[Dict[str, str]]
332 + case_uuid: str
333 + review_status_id: Optional[int]
334 + state_id: int
335 + case_customer: int
336 + reviewer_id: Optional[int]
337 + user_id: int
338 + owner_id: int
339 + closing_note: Optional[str]
340 + status_id: int
341 +
342 +
343 +class CaseResponse(BaseModel):
344 + success: bool
345 + data: CaseData
346 +
347 +
348 +################# ! IRIS ASSET ADD SCHEMA ! #################
349 +class AddAssetModel(BaseModel):
350 + name: str
351 + asset_type: int
352 + analysis_status: int = Field(
353 + None,
354 + description="The analysis status ID of the asset.",
355 + )
356 + compromise_status: int = Field(
357 + None,
358 + description="The asset compromise status ID of the asset.",
359 + )
360 + asset_tags: Optional[List[str]] = Field(
361 + None,
362 + description="The asset tags of the asset.",
363 + )
364 + description: Optional[str] = Field(
365 + None,
366 + description="The asset description of the asset.",
367 + )
368 + asset_domain: Optional[str] = Field(
369 + None,
370 + description="The asset domain of the asset.",
371 + )
372 + ip: Optional[str] = Field(None, description="The asset IP of the asset.")
373 + ioc_links: Optional[List[int]] = Field(
374 + None,
375 + description="The IoC links of the asset.",
376 + )
377 + custom_attributes: Optional[Dict[str, str]] = Field(
378 + None,
379 + description="The custom attributes of the asset.",
380 + )
381 +
382 + def to_dict(self):
383 + return self.dict(exclude_none=True)
backend/app/integrations/sap_siem/services/collect.py new
+85
@@ -0,0 +1,85 @@
1 +import requests
2 +from loguru import logger
3 +
4 +from app.integrations.sap_siem.schema.sap_siem import CollectSapSiemRequest
5 +from app.integrations.sap_siem.schema.sap_siem import InvokeSAPSiemResponse
6 +from app.integrations.sap_siem.schema.sap_siem import SapSiemResponseBody
7 +from app.integrations.utils.event_shipper import event_shipper
8 +from app.integrations.utils.schema import EventShipperPayload
9 +
10 +
11 +def build_request_payload(sap_siem_request: CollectSapSiemRequest) -> dict:
12 + return {
13 + "apiKey": sap_siem_request.apiKey,
14 + "secret": sap_siem_request.secretKey,
15 + "userKey": sap_siem_request.userKey,
16 + "query": f"SELECT * FROM auditLog WHERE endpoint = 'accounts.login' and @timestamp >= '{sap_siem_request.lower_bound}' "
17 + f"and @timestamp < '{sap_siem_request.upper_bound}'",
18 + }
19 +
20 +
21 +async def make_request(sap_siem_request: CollectSapSiemRequest) -> SapSiemResponseBody:
22 + """
23 + Makes a request to the SAP SIEM integration.
24 +
25 + Args:
26 + sap_siem_request (CollectSapSiemRequest): The request payload containing the necessary information for the SAP SIEM integration.
27 +
28 + Returns:
29 + SapSiemResponseBody: The response model containing the result of the SAP SIEM integration invocation.
30 +
31 + Raises:
32 + HTTPException: If the SAP SIEM integration fails.
33 + """
34 + logger.info("Making request to SAP SIEM")
35 + form_data = build_request_payload(sap_siem_request)
36 + response = requests.post(
37 + f"https://{sap_siem_request.apiDomain}/audit.search",
38 + data=form_data,
39 + )
40 + return SapSiemResponseBody(**response.json())
41 +
42 +
43 +async def send_to_event_shipper(message: EventShipperPayload) -> None:
44 + """
45 + Sends the message to the event shipper.
46 +
47 + Args:
48 + message (EventShipperPayload): The message to send to the event shipper.
49 + """
50 + await event_shipper(message)
51 +
52 +
53 +async def collect_sap_siem(sap_siem_request: CollectSapSiemRequest) -> InvokeSAPSiemResponse:
54 + """
55 + Collects SAP SIEM events.
56 +
57 + Args:
58 + sap_siem_request (CollectSapSiemRequest): The request payload containing the necessary information for the SAP SIEM integration.
59 +
60 + Returns:
61 + InvokeSAPSiemResponse: The response model containing the result of the SAP SIEM integration invocation.
62 +
63 + Raises:
64 + HTTPException: If the SAP SIEM integration fails.
65 + """
66 + logger.info(f"Collecting SAP SIEM Events for customer_code: {sap_siem_request.customer_code}")
67 +
68 + results = await make_request(sap_siem_request)
69 +
70 + for result in results.results:
71 + # write the `timestamp` field as `event_timestamp`
72 + result.event_timestamp = result.timestamp
73 + await send_to_event_shipper(
74 + EventShipperPayload(
75 + customer_code=sap_siem_request.customer_code,
76 + integration="sap_siem",
77 + version="1.0",
78 + **result.dict(),
79 + ),
80 + )
81 +
82 + return InvokeSAPSiemResponse(
83 + success=True,
84 + message="SAP SIEM Events collected successfully",
85 + )
backend/app/integrations/sap_siem/services/provision.py new
+404
@@ -0,0 +1,404 @@
1 +import json
2 +from datetime import datetime
3 +
4 +from loguru import logger
5 +from sqlalchemy import and_
6 +from sqlalchemy import update
7 +from sqlalchemy.ext.asyncio import AsyncSession
8 +
9 +from app.connectors.grafana.schema.dashboards import DashboardProvisionRequest
10 +from app.connectors.grafana.schema.dashboards import SapSiemDashboard
11 +from app.connectors.grafana.services.dashboards import provision_dashboards
12 +from app.connectors.grafana.utils.universal import create_grafana_client
13 +from app.connectors.graylog.services.management import start_stream
14 +from app.connectors.graylog.utils.universal import send_post_request
15 +from app.customer_provisioning.schema.grafana import GrafanaDatasource
16 +from app.customer_provisioning.schema.grafana import GrafanaDataSourceCreationResponse
17 +from app.customer_provisioning.schema.graylog import GraylogIndexSetCreationResponse
18 +from app.customer_provisioning.schema.graylog import StreamCreationResponse
19 +from app.customer_provisioning.schema.graylog import TimeBasedIndexSet
20 +from app.customer_provisioning.services.grafana import create_grafana_folder
21 +from app.customer_provisioning.services.grafana import get_opensearch_version
22 +from app.customers.routes.customers import get_customer
23 +from app.customers.routes.customers import get_customer_meta
24 +from app.integrations.models.customer_integration_settings import CustomerIntegrations
25 +from app.integrations.routes import create_integration_meta
26 +from app.integrations.sap_siem.schema.provision import ProvisionSapSiemRequest
27 +from app.integrations.sap_siem.schema.provision import ProvisionSapSiemResponse
28 +from app.integrations.sap_siem.schema.provision import SapSiemEventStream
29 +from app.integrations.schema import CustomerIntegrationsMetaSchema
30 +from app.utils import get_connector_attribute
31 +
32 +
33 +################## ! GRAYLOG ! ##################
34 +async def build_index_set_config(
35 + customer_code: str,
36 + session: AsyncSession,
37 +) -> TimeBasedIndexSet:
38 + """
39 + Build the configuration for a time-based index set.
40 +
41 + Args:
42 + request (ProvisionNewCustomer): The request object containing customer information.
43 +
44 + Returns:
45 + TimeBasedIndexSet: The configured time-based index set.
46 + """
47 + return TimeBasedIndexSet(
48 + title=f"SAP SIEM - {(await get_customer(customer_code, session)).customer.customer_name}",
49 + description=f"SAP SIEM - {customer_code}",
50 + index_prefix=f"sap_siem_{customer_code}",
51 + rotation_strategy_class="org.graylog2.indexer.rotation.strategies.TimeBasedRotationStrategy",
52 + rotation_strategy={
53 + "type": "org.graylog2.indexer.rotation.strategies.TimeBasedRotationStrategyConfig",
54 + "rotation_period": "P1D",
55 + "rotate_empty_index_set": False,
56 + "max_rotation_period": None,
57 + },
58 + retention_strategy_class="org.graylog2.indexer.retention.strategies.DeletionRetentionStrategy",
59 + retention_strategy={
60 + "type": "org.graylog2.indexer.retention.strategies.DeletionRetentionStrategyConfig",
61 + "max_number_of_indices": 30,
62 + },
63 + creation_date=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
64 + index_analyzer="standard",
65 + shards=1,
66 + replicas=0,
67 + index_optimization_max_num_segments=1,
68 + index_optimization_disabled=False,
69 + writable=True,
70 + field_type_refresh_interval=5000,
71 + )
72 +
73 +
74 +# Function to send the POST request and handle the response
75 +async def send_index_set_creation_request(
76 + index_set: TimeBasedIndexSet,
77 +) -> GraylogIndexSetCreationResponse:
78 + """
79 + Sends a request to create an index set in Graylog.
80 +
81 + Args:
82 + index_set (TimeBasedIndexSet): The index set to be created.
83 +
84 + Returns:
85 + GraylogIndexSetCreationResponse: The response from Graylog after creating the index set.
86 + """
87 + json_index_set = json.dumps(index_set.dict())
88 + logger.info(f"json_index_set set: {json_index_set}")
89 + response_json = await send_post_request(
90 + endpoint="/api/system/indices/index_sets",
91 + data=index_set.dict(),
92 + )
93 + return GraylogIndexSetCreationResponse(**response_json)
94 +
95 +
96 +async def create_index_set(
97 + customer_code: str,
98 + session: AsyncSession,
99 +) -> GraylogIndexSetCreationResponse:
100 + """
101 + Creates an index set for a new customer.
102 +
103 + Args:
104 + request (ProvisionNewCustomer): The request object containing the customer information.
105 +
106 + Returns:
107 + GraylogIndexSetCreationResponse: The response object containing the result of the index set creation.
108 + """
109 + logger.info(f"Creating index set for customer {customer_code}")
110 + index_set_config = await build_index_set_config(customer_code, session)
111 + return await send_index_set_creation_request(index_set_config)
112 +
113 +
114 +# ! Event STREAMS ! #
115 +# Function to create event stream configuration
116 +async def build_event_stream_config(
117 + customer_code: str,
118 + index_set_id: str,
119 + session: AsyncSession,
120 +) -> SapSiemEventStream:
121 + """
122 + Builds the configuration for a SAP SIEM event stream.
123 +
124 + Args:
125 + customer_code (str): The customer code.
126 + index_set_id (str): The index set ID.
127 + session (AsyncSession): The async session.
128 +
129 + Returns:
130 + SapSiemEventStream: The configured SAP SIEM event stream.
131 + """
132 + return SapSiemEventStream(
133 + title=f"SAP SIEM EVENTS - {(await get_customer(customer_code, session)).customer.customer_name}",
134 + description=f"SAP SIEM EVENTS - {(await get_customer(customer_code, session)).customer.customer_name}",
135 + index_set_id=index_set_id,
136 + rules=[
137 + {
138 + "field": "integration",
139 + "type": 1,
140 + "inverted": False,
141 + "value": "sap_siem",
142 + },
143 + {
144 + "field": "customer_code",
145 + "type": 1,
146 + "inverted": False,
147 + "value": f"{customer_code}",
148 + },
149 + ],
150 + matching_type="AND",
151 + remove_matches_from_default_stream=True,
152 + content_pack=None,
153 + )
154 +
155 +
156 +async def send_event_stream_creation_request(
157 + event_stream: SapSiemEventStream,
158 +) -> StreamCreationResponse:
159 + """
160 + Sends a request to create an event stream.
161 +
162 + Args:
163 + event_stream (SapSiemEventStream): The event stream to be created.
164 +
165 + Returns:
166 + StreamCreationResponse: The response containing the created event stream.
167 + """
168 + json_event_stream = json.dumps(event_stream.dict())
169 + logger.info(f"json_event_stream set: {json_event_stream}")
170 + response_json = await send_post_request(
171 + endpoint="/api/streams",
172 + data=event_stream.dict(),
173 + )
174 + return StreamCreationResponse(**response_json)
175 +
176 +
177 +async def create_event_stream(
178 + customer_code: str,
179 + index_set_id: str,
180 + session: AsyncSession,
181 +) -> StreamCreationResponse:
182 + """
183 + Creates an event stream for a customer.
184 +
185 + Args:
186 + request (ProvisionNewCustomer): The request object containing customer information.
187 + index_set_id (str): The ID of the index set.
188 +
189 + Returns:
190 + The result of the event stream creation request.
191 + """
192 + event_stream_config = await build_event_stream_config(
193 + customer_code,
194 + index_set_id,
195 + session,
196 + )
197 + return await send_event_stream_creation_request(event_stream_config)
198 +
199 +
200 +#### ! GRAFANA ! ####
201 +async def create_grafana_datasource(
202 + customer_code: str,
203 + session: AsyncSession,
204 +) -> GrafanaDataSourceCreationResponse:
205 + """
206 + Creates a Grafana datasource for the specified customer.
207 +
208 + Args:
209 + customer_code (str): The customer code.
210 + session (AsyncSession): The async session.
211 +
212 + Returns:
213 + GrafanaDataSourceCreationResponse: The response containing the created datasource details.
214 + """
215 + logger.info("Creating Grafana datasource")
216 + grafana_client = await create_grafana_client("Grafana")
217 + # Switch to the newly created organization
218 + grafana_client.user.switch_actual_user_organisation(
219 + (await get_customer_meta(customer_code, session)).customer_meta.customer_meta_grafana_org_id,
220 + )
221 + datasource_payload = GrafanaDatasource(
222 + name="SAP SIEM",
223 + type="grafana-opensearch-datasource",
224 + typeName="OpenSearch",
225 + access="proxy",
226 + url=await get_connector_attribute(
227 + connector_id=1,
228 + column_name="connector_url",
229 + session=session,
230 + ),
231 + database=f"sap_siem_{customer_code}*",
232 + basicAuth=True,
233 + basicAuthUser=await get_connector_attribute(
234 + connector_id=1,
235 + column_name="connector_username",
236 + session=session,
237 + ),
238 + secureJsonData={
239 + "basicAuthPassword": await get_connector_attribute(
240 + connector_id=1,
241 + column_name="connector_password",
242 + session=session,
243 + ),
244 + },
245 + isDefault=False,
246 + jsonData={
247 + "database": f"sap_siem_{customer_code}*",
248 + "flavor": "opensearch",
249 + "includeFrozen": False,
250 + "logLevelField": "level",
251 + "logMessageField": "params_loginID",
252 + "maxConcurrentShardRequests": 5,
253 + "pplEnabled": True,
254 + "timeField": "timestamp",
255 + "tlsSkipVerify": True,
256 + "version": await get_opensearch_version(),
257 + },
258 + readOnly=True,
259 + )
260 + results = grafana_client.datasource.create_datasource(
261 + datasource=datasource_payload.dict(),
262 + )
263 + return GrafanaDataSourceCreationResponse(**results)
264 +
265 +
266 +async def provision_sap_siem(
267 + provision_sap_siem_request: ProvisionSapSiemRequest,
268 + session: AsyncSession,
269 +) -> ProvisionSapSiemResponse:
270 + """
271 + Provisions Mimecast integration for a customer.
272 +
273 + Args:
274 + provision_mimecast_request (ProvisionMimecastRequest): The request object containing the necessary information for provisioning.
275 + session (AsyncSession, optional): The database session. Defaults to Depends(get_db).
276 +
277 + Returns:
278 + ProvisionMimecastResponse: The response object containing the result of the provisioning.
279 + """
280 + logger.info(
281 + f"Provisioning SAP SIEM integration for customer {provision_sap_siem_request.customer_code}.",
282 + )
283 +
284 + # Create Index Set
285 + index_set_id = (
286 + await create_index_set(
287 + customer_code=provision_sap_siem_request.customer_code,
288 + session=session,
289 + )
290 + ).data.id
291 + logger.info(f"Index set: {index_set_id}")
292 + # Create event stream
293 + stream_id = (
294 + await create_event_stream(
295 + provision_sap_siem_request.customer_code,
296 + index_set_id,
297 + session,
298 + )
299 + ).data.stream_id
300 + # Start stream
301 + await start_stream(stream_id=stream_id)
302 +
303 + # Grafana Deployment
304 + sap_siem_datasource_uid = (
305 + await create_grafana_datasource(
306 + customer_code=provision_sap_siem_request.customer_code,
307 + session=session,
308 + )
309 + ).datasource.uid
310 + grafana_sap_siem_folder_id = (
311 + await create_grafana_folder(
312 + organization_id=(
313 + await get_customer_meta(
314 + provision_sap_siem_request.customer_code,
315 + session,
316 + )
317 + ).customer_meta.customer_meta_grafana_org_id,
318 + folder_title="SAP SIEM",
319 + )
320 + ).id
321 + await provision_dashboards(
322 + DashboardProvisionRequest(
323 + dashboards=[dashboard.name for dashboard in SapSiemDashboard],
324 + organizationId=(
325 + await get_customer_meta(
326 + provision_sap_siem_request.customer_code,
327 + session,
328 + )
329 + ).customer_meta.customer_meta_grafana_org_id,
330 + folderId=grafana_sap_siem_folder_id,
331 + datasourceUid=sap_siem_datasource_uid,
332 + ),
333 + )
334 + await create_integration_meta_entry(
335 + CustomerIntegrationsMetaSchema(
336 + customer_code=provision_sap_siem_request.customer_code,
337 + integration_name="SAP SIEM",
338 + graylog_input_id=None,
339 + graylog_index_id=index_set_id,
340 + graylog_stream_id=stream_id,
341 + grafana_org_id=(
342 + await get_customer_meta(
343 + provision_sap_siem_request.customer_code,
344 + session,
345 + )
346 + ).customer_meta.customer_meta_grafana_org_id,
347 + grafana_dashboard_folder_id=grafana_sap_siem_folder_id,
348 + ),
349 + session,
350 + )
351 + await update_customer_integration_table(
352 + provision_sap_siem_request.customer_code,
353 + session,
354 + )
355 +
356 + return ProvisionSapSiemResponse(
357 + success=True,
358 + message="SAP SIEM integration provisioned successfully.",
359 + )
360 +
361 +
362 +############## ! WRITE TO DB ! ##############
363 +async def create_integration_meta_entry(
364 + customer_integration_meta: CustomerIntegrationsMetaSchema,
365 + session: AsyncSession,
366 +) -> None:
367 + """
368 + Creates an entry for the customer integration meta in the database.
369 +
370 + Args:
371 + customer_integration_meta (CustomerIntegrationsMetaSchema): The customer integration meta object.
372 + session (AsyncSession): The async session object for database operations.
373 + """
374 + await create_integration_meta(customer_integration_meta, session)
375 + logger.info(
376 + f"Integration meta entry created for customer {customer_integration_meta.customer_code}.",
377 + )
378 +
379 +
380 +async def update_customer_integration_table(
381 + customer_code: str,
382 + session: AsyncSession,
383 +) -> None:
384 + """
385 + Updates the `customer_integrations` table to set the `deployed` column to True where the `customer_code`
386 + matches the given customer code and the `integration_service_name` is "SAP SIEM".
387 +
388 + Args:
389 + customer_code (str): The customer code.
390 + session (AsyncSession): The async session object for making HTTP requests.
391 + """
392 + await session.execute(
393 + update(CustomerIntegrations)
394 + .where(
395 + and_(
396 + CustomerIntegrations.customer_code == customer_code,
397 + CustomerIntegrations.integration_service_name == "SAP SIEM",
398 + ),
399 + )
400 + .values(deployed=True),
401 + )
402 + await session.commit()
403 +
404 + return None
backend/app/integrations/sap_siem/services/sap_siem_multiple_logins.py new
+529
@@ -0,0 +1,529 @@
1 +from collections import defaultdict
2 +from datetime import datetime
3 +from typing import List
4 +from typing import Set
5 +
6 +from fastapi import HTTPException
7 +from loguru import logger
8 +from sqlalchemy.ext.asyncio import AsyncSession
9 +from sqlalchemy.future import select
10 +
11 +from app.connectors.dfir_iris.utils.universal import fetch_and_validate_data
12 +from app.connectors.dfir_iris.utils.universal import initialize_client_and_case
13 +from app.connectors.wazuh_indexer.utils.universal import create_wazuh_indexer_client
14 +from app.integrations.sap_siem.models.sap_siem import SapSiemMultipleLogins
15 +from app.integrations.sap_siem.schema.sap_siem import AddAssetModel
16 +from app.integrations.sap_siem.schema.sap_siem import CaseResponse
17 +from app.integrations.sap_siem.schema.sap_siem import InvokeSAPSiemResponse
18 +from app.integrations.sap_siem.schema.sap_siem import IrisCasePayload
19 +from app.integrations.sap_siem.schema.sap_siem import SapSiemWazuhIndexerResponse
20 +from app.integrations.sap_siem.schema.sap_siem import SuspiciousLogin
21 +from app.utils import get_customer_alert_settings
22 +
23 +# Global set to keep track of IPs that have already been checked
24 +checked_ips = set()
25 +
26 +
27 +async def handle_common_suspicious_login_tasks(
28 + suspicious_login,
29 + unique_instances,
30 + case_ids,
31 + create_case_fn,
32 + session: AsyncSession,
33 +):
34 + """
35 + Handles common tasks for suspicious logins.
36 +
37 + Args:
38 + suspicious_login: The suspicious login object.
39 + unique_instances: List of unique instances.
40 + case_ids: List of case IDs.
41 + create_case_fn: Function to create a case.
42 + session: The async session.
43 +
44 + Returns:
45 + None
46 + """
47 + case = await create_case_fn(suspicious_login, session)
48 + case_ids.append(case.data.case_id)
49 + user_activity = await collect_user_activity(suspicious_login)
50 + await handle_user_activity(user_activity, unique_instances, case.data.case_id)
51 + await mark_as_checked(suspicious_login)
52 +
53 +
54 +async def handle_suspicious_login_multiple(suspicious_login, unique_instances, case_ids, session: AsyncSession):
55 + """
56 + Handles suspicious login events with multiple logins.
57 +
58 + Args:
59 + suspicious_login: The suspicious login event.
60 + unique_instances: List of unique instances of the suspicious login event.
61 + case_ids: List of case IDs associated with the suspicious login event.
62 + session: The database session.
63 +
64 + Returns:
65 + None
66 + """
67 + await handle_common_suspicious_login_tasks(
68 + suspicious_login,
69 + unique_instances,
70 + case_ids,
71 + create_iris_case_multiple,
72 + session,
73 + )
74 + await update_event_analyzed_multiple_logins_flag(suspicious_login.id, suspicious_login.index)
75 +
76 +
77 +async def update_event_analyzed_multiple_logins_flag(id: str, index: str):
78 + """
79 + Update the event_analyzed_multiple_logins flag in the Elasticsearch document to True.
80 +
81 + :param suspicious_login: The suspicious login to update
82 +
83 + :return: None
84 + """
85 + es_client = await create_wazuh_indexer_client("Wazuh-Indexer")
86 + try:
87 + es_client.update(
88 + index=index,
89 + id=id,
90 + body={
91 + "doc": {
92 + "event_analyzed_multiple_logins": "True",
93 + },
94 + },
95 + )
96 + logger.info(f"Updated event_analyzed_multiple_logins flag for suspicious login: {id}")
97 + except Exception as e:
98 + logger.error(
99 + f"Failed to update case created flag {e}",
100 + )
101 + # Attempt to remove read-only block
102 + try:
103 + es_client.indices.put_settings(
104 + index=index,
105 + body={"index.blocks.write": None},
106 + )
107 + logger.info(
108 + f"Removed read-only block from index {index}. Retrying update.",
109 + )
110 +
111 + # Retry the update operation
112 + es_client.update(
113 + index=index,
114 + id=id,
115 + body={"doc": {"event_analyzed_multiple_logins": "True"}},
116 + )
117 + logger.info(
118 + f"Added event_analyzed_multiple_logins flag to index {index} for suspicious login: {id}",
119 + )
120 +
121 + # Reenable the write block
122 + es_client.indices.put_settings(
123 + index=index,
124 + body={"index.blocks.write": True},
125 + )
126 + except Exception as e2:
127 + logger.error(
128 + f"Failed to remove read-only block from index {index}: {e2}",
129 + )
130 + return False
131 +
132 +
133 +async def mark_as_checked(suspicious_login):
134 + """
135 + Marks a suspicious login as checked by adding it to the set of checked IPs.
136 +
137 + Args:
138 + suspicious_login (Login): The suspicious login object to mark as checked.
139 +
140 + Returns:
141 + None
142 + """
143 + checked_ips.add((suspicious_login.loginID, suspicious_login.ip))
144 +
145 +
146 +async def handle_user_activity(user_activity: SapSiemWazuhIndexerResponse, unique_instances, case_id):
147 + """
148 + Handles user activity by processing each hit in the user_activity and performing the following steps:
149 + 1. Extracts relevant information from the hit.
150 + 2. Checks if the current activity is already present in the unique_instances set.
151 + 3. If not present, adds the user activity to the IRIS case.
152 + 4. Creates an asset payload using the current activity.
153 + 5. Updates the case with the asset payload.
154 + 6. Updates the event analyzed multiple logins flag for the hit.
155 + 7. Adds the current activity to the unique_instances set.
156 +
157 + Parameters:
158 + - user_activity (SapSiemWazuhIndexerResponse): The user activity to be processed.
159 + - unique_instances (set): A set containing unique instances of user activity.
160 + - case_id (str): The ID of the IRIS case.
161 +
162 + Returns:
163 + None
164 + """
165 + for hit in user_activity.hits.hits:
166 + current_activity = {
167 + "loginID": hit.source.params_loginID,
168 + "ip": hit.source.ip,
169 + "country": hit.source.httpReq_country,
170 + "errMessage": hit.source.errMessage,
171 + "event_timestamp": hit.source.event_timestamp,
172 + "customer_code": hit.source.customer_code,
173 + "errDetails": hit.source.errDetails,
174 + }
175 + current_activity_frozenset = frozenset(current_activity.items())
176 + if current_activity_frozenset not in unique_instances:
177 + logger.info(f"Adding user activity to IRIS case: {current_activity}")
178 + current_asset = SuspiciousLogin(**current_activity)
179 + asset_payload = create_asset_payload(asset=current_asset)
180 + logger.info(f"Asset Payload: {asset_payload}")
181 + await update_case_with_asset(case_id, asset_payload)
182 + await update_event_analyzed_multiple_logins_flag(hit.id, hit.index)
183 + unique_instances.add(current_activity_frozenset)
184 +
185 +
186 +def create_asset_payload(asset: SuspiciousLogin):
187 + """
188 + Create a payload for adding an asset based on a SuspiciousLogin object.
189 +
190 + Args:
191 + asset (SuspiciousLogin): The SuspiciousLogin object containing the asset details.
192 +
193 + Returns:
194 + AddAssetModel: The payload for adding the asset.
195 +
196 + """
197 + if asset.errMessage == "OK":
198 + return AddAssetModel(
199 + name=asset.loginID,
200 + ip=asset.ip,
201 + description=f"Country: {asset.country}\n\nMessage: {asset.errDetails}\n\nTimestamp: {asset.event_timestamp}",
202 + asset_type=1,
203 + compromise_status=1,
204 + analysis_status=2,
205 + )
206 + return AddAssetModel(
207 + name=asset.loginID,
208 + ip=asset.ip,
209 + description=f"Country: {asset.country}\n\nMessage: {asset.errDetails}\n\nTimestamp: {asset.event_timestamp}",
210 + asset_type=1,
211 + analysis_status=2,
212 + )
213 +
214 +
215 +async def update_case_with_asset(case_id: str, asset_payload):
216 + """
217 + Update the case with the asset information.
218 +
219 + :param case_id: The ID of the case to update
220 + :param asset_payload: The payload to update the case with
221 +
222 + :return: None
223 + """
224 + logger.info(f"Updating IRIS case {case_id} with asset: {asset_payload}")
225 + client, case_client = await initialize_client_and_case("DFIR-IRIS")
226 + return await fetch_and_validate_data(
227 + client,
228 + case_client.add_asset,
229 + cid=case_id,
230 + **asset_payload.to_dict(),
231 + )
232 +
233 +
234 +async def create_iris_case_multiple(suspicious_login: SuspiciousLogin, session: AsyncSession) -> CaseResponse:
235 + """
236 + Creates an IRIS case for multiple logins with the same IP address.
237 +
238 + Args:
239 + suspicious_login (SuspiciousLogin): The suspicious login information.
240 + session (AsyncSession): The async session for database operations.
241 +
242 + Returns:
243 + CaseResponse: The response containing the created case information.
244 + """
245 + logger.info(f"Creating IRIS case same IP with multiple users: {suspicious_login}")
246 + case_name = (
247 + f"Log Source: {suspicious_login.logSource} SAP SIEM. " f"IP Address: {suspicious_login.ip} found logging in with multiple users."
248 + )
249 +
250 + case_description = (
251 + f"Log Source: {suspicious_login.logSource}\n\n"
252 + f"IP Address: {suspicious_login.ip}\n\n"
253 + f"Country: {suspicious_login.country}\n\n"
254 + f"Timestamp: {suspicious_login.event_timestamp}"
255 + )
256 +
257 + case_customer = (await get_customer_alert_settings(suspicious_login.customer_code, session=session)).iris_customer_id
258 +
259 + payload = IrisCasePayload(
260 + case_name=case_name,
261 + case_description=case_description,
262 + case_customer=case_customer,
263 + case_classification=18,
264 + soc_id="1",
265 + create_customer=False,
266 + )
267 + client, case_client = await initialize_client_and_case("DFIR-IRIS")
268 + result = await fetch_and_validate_data(
269 + client,
270 + case_client.add_case,
271 + **payload.to_dict(),
272 + )
273 + await update_event_analyzed_multiple_logins_flag(suspicious_login.id, suspicious_login.index)
274 +
275 + return CaseResponse(**result)
276 +
277 +
278 +async def collect_user_activity(suspicious_logins: SuspiciousLogin) -> SapSiemWazuhIndexerResponse:
279 + """
280 + Collect the IP addresses of the suspicious logins and query the database for all activity from those IP addresses.
281 + Collects a max of 1000 records.
282 +
283 + :param suspicious_logins: A list of suspicious logins
284 +
285 + :return: List of the user Activity collected from the sap_siem table
286 + """
287 + es_client = await create_wazuh_indexer_client("Wazuh-Indexer")
288 + results = es_client.search(
289 + index="integrations_*",
290 + body={
291 + "size": 1000,
292 + "query": {"bool": {"must": [{"term": {"ip": suspicious_logins.ip}}]}},
293 + },
294 + )
295 + return SapSiemWazuhIndexerResponse(**results)
296 +
297 +
298 +async def get_initial_search_results(es_client):
299 + """
300 + Retrieves the initial search results from Elasticsearch.
301 +
302 + Args:
303 + es_client (Elasticsearch): The Elasticsearch client.
304 +
305 + Returns:
306 + dict: The search results.
307 + """
308 + return es_client.search(
309 + index="integrations_*",
310 + body={
311 + "size": 1000,
312 + "query": {"bool": {"must": [{"term": {"errMessage": "OK"}}, {"term": {"event_analyzed_multiple_logins": "False"}}]}},
313 + "sort": [{"event_timestamp": {"order": "asc"}}],
314 + },
315 + scroll="1m",
316 + )
317 +
318 +
319 +async def get_next_batch_of_results(es_client, scroll_id):
320 + """
321 + Retrieves the next batch of results using the provided Elasticsearch client and scroll ID.
322 +
323 + Args:
324 + es_client (Elasticsearch): The Elasticsearch client.
325 + scroll_id (str): The scroll ID to retrieve the next batch of results.
326 +
327 + Returns:
328 + dict: The next batch of results.
329 + """
330 + return es_client.scroll(scroll_id=scroll_id, scroll="1m")
331 +
332 +
333 +async def process_hits(hits, ip_to_login_ids, suspicious_activity):
334 + """
335 + Process the hits received from SAP SIEM and update the IP to login IDs mapping and suspicious activity.
336 +
337 + Args:
338 + hits (list): List of hits received from SAP SIEM.
339 + ip_to_login_ids (dict): Dictionary mapping IP addresses to login IDs.
340 + suspicious_activity (dict): Dictionary mapping IP addresses to a list of suspicious login objects.
341 +
342 + Returns:
343 + None
344 + """
345 + for hit in hits:
346 + if hit.source.errMessage == "OK":
347 + # Convert loginID to lowercase before comparing
348 + login_id = hit.source.params_loginID.lower()
349 + ip_to_login_ids[hit.source.ip].add(login_id)
350 +
351 + suspicious_login = SuspiciousLogin(
352 + _index=hit.index,
353 + _id=hit.id,
354 + customer_code=hit.source.customer_code,
355 + logSource=hit.source.logSource,
356 + loginID=hit.source.params_loginID,
357 + country=hit.source.httpReq_country,
358 + ip=hit.source.ip,
359 + event_timestamp=hit.source.event_timestamp,
360 + errMessage=hit.source.errMessage,
361 + errDetails=hit.source.errDetails,
362 + )
363 +
364 + suspicious_activity[hit.source.ip].append(suspicious_login)
365 +
366 +
367 +async def check_multiple_successful_logins_by_ip(threshold: int) -> List[SuspiciousLogin]:
368 + """
369 + Checks for multiple successful logins by IP address.
370 +
371 + Args:
372 + threshold (int): The minimum number of logins required to be considered suspicious.
373 +
374 + Returns:
375 + List[SuspiciousLogin]: A list of suspicious login objects.
376 + """
377 + ip_to_login_ids = defaultdict(set)
378 + suspicious_activity = defaultdict(list)
379 +
380 + es_client = await create_wazuh_indexer_client("Wazuh-Indexer")
381 + scroll_id = None
382 +
383 + while True:
384 + if scroll_id is None:
385 + results = await get_initial_search_results(es_client)
386 + else:
387 + results = await get_next_batch_of_results(es_client, scroll_id)
388 +
389 + if not results["hits"]["hits"]:
390 + break
391 +
392 + results = SapSiemWazuhIndexerResponse(**results)
393 + await process_hits(results.hits.hits, ip_to_login_ids, suspicious_activity)
394 +
395 + scroll_id = results.scroll_id
396 +
397 + # Clear the scroll when you're done to free up resources
398 + if scroll_id is not None:
399 + es_client.clear_scroll(scroll_id=scroll_id)
400 +
401 + suspicious_activity = {ip: results for ip, results in suspicious_activity.items() if len(ip_to_login_ids[ip]) > threshold}
402 +
403 + return [login for sublist in suspicious_activity.values() for login in sublist]
404 +
405 +
406 +async def get_suspicious_ips(threshold: int) -> List[SuspiciousLogin]:
407 + """
408 + Retrieves a list of suspicious login attempts based on the specified threshold.
409 +
410 + Args:
411 + threshold (int): The number of successful logins from the same IP address that is considered suspicious.
412 +
413 + Returns:
414 + List[SuspiciousLogin]: A list of SuspiciousLogin objects representing the suspicious login attempts.
415 + """
416 + return await check_multiple_successful_logins_by_ip(threshold=threshold)
417 +
418 +
419 +async def get_existing_database_record(session: AsyncSession, ip: str) -> SapSiemMultipleLogins:
420 + """
421 + Retrieves an existing database record for the given IP address.
422 +
423 + Args:
424 + session (AsyncSession): The async session object for database operations.
425 + ip (str): The IP address to search for.
426 +
427 + Returns:
428 + SapSiemMultipleLogins: The database record matching the IP address, or None if not found.
429 + """
430 + result = await session.execute(select(SapSiemMultipleLogins).where(SapSiemMultipleLogins.ip == ip))
431 + return result.scalar_one_or_none() if result is not None else None
432 +
433 +
434 +def update_existing_database_record(existing_case: SapSiemMultipleLogins, new_login_ids: Set[str]) -> None:
435 + """
436 + Update the existing database record for a SapSiemMultipleLogins case with new login IDs.
437 +
438 + Args:
439 + existing_case (SapSiemMultipleLogins): The existing database record to be updated.
440 + new_login_ids (Set[str]): The new login IDs to be added to the existing record.
441 +
442 + Returns:
443 + None
444 + """
445 + existing_loginIDs = set(existing_case.associated_loginIDs.split(","))
446 + if not new_login_ids.issubset(existing_loginIDs):
447 + updated_login_ids = existing_loginIDs.union(new_login_ids)
448 + existing_case.associated_loginIDs = ",".join(updated_login_ids)
449 + existing_case.last_case_created_timestamp = datetime.now()
450 +
451 +
452 +def create_new_database_record(ip: str, new_login_ids: Set[str]) -> SapSiemMultipleLogins:
453 + """
454 + Creates a new database record for SAP SIEM multiple logins.
455 +
456 + Args:
457 + ip (str): The IP address associated with the multiple logins.
458 + new_login_ids (Set[str]): The set of new login IDs.
459 +
460 + Returns:
461 + SapSiemMultipleLogins: The newly created database record.
462 + """
463 + return SapSiemMultipleLogins(
464 + ip=ip,
465 + last_case_created_timestamp=datetime.now(),
466 + associated_loginIDs=",".join(new_login_ids),
467 + )
468 +
469 +
470 +async def sap_siem_multiple_logins_same_ip(threshold: int, session: AsyncSession) -> InvokeSAPSiemResponse:
471 + """
472 + Finds same IP with multiple users and handles suspicious logins.
473 +
474 + Args:
475 + threshold (int): The threshold value for determining suspicious logins.
476 + session (AsyncSession): The database session.
477 +
478 + Returns:
479 + InvokeSAPSiemResponse: The response indicating the success of the operation.
480 + """
481 + logger.info("Finding same IP with multiple users")
482 +
483 + suspicious_ips = await get_suspicious_ips(threshold)
484 + logger.info(f"Suspicious IPs: {suspicious_ips}")
485 +
486 + unique_instances = set()
487 + case_ids = []
488 + # Dictionary to aggregate suspicious logins by IP
489 + aggregated_logins_by_ip = defaultdict(list)
490 +
491 + for suspicious_login in suspicious_ips:
492 + aggregated_logins_by_ip[suspicious_login.ip].append(suspicious_login)
493 +
494 + for ip, associated_logins in aggregated_logins_by_ip.items():
495 + logger.info(f"IP: {ip}, Associated Logins: {associated_logins}")
496 + if session is not None:
497 + existing_case = await get_existing_database_record(session, ip)
498 +
499 + new_login_ids = {login.loginID for login in associated_logins}
500 + if existing_case:
501 + logger.info(f"Updating existing database record: {existing_case}")
502 + update_existing_database_record(existing_case, new_login_ids)
503 + else:
504 + logger.info(f"Creating new case for IP: {ip}")
505 + new_case = create_new_database_record(ip, new_login_ids)
506 + session.add(new_case)
507 +
508 + # Create a single new IRIS case for this IP
509 + # Modify this to include information from all associated_logins
510 + await handle_suspicious_login_multiple(
511 + associated_logins[0],
512 + unique_instances,
513 + case_ids,
514 + session=session,
515 + )
516 + else:
517 + raise HTTPException(
518 + status_code=500,
519 + detail="Failed to create IRIS case",
520 + )
521 + await session.commit()
522 +
523 + # Clear the global set
524 + checked_ips.clear()
525 +
526 + return InvokeSAPSiemResponse(
527 + success=True,
528 + message="SAP SIEM multiple logins invoked.",
529 + )
backend/app/integrations/sap_siem/services/sap_siem_suspicious_logins.py new
+401
@@ -0,0 +1,401 @@
1 +from typing import List
2 +
3 +from loguru import logger
4 +from sqlalchemy.ext.asyncio import AsyncSession
5 +
6 +from app.connectors.dfir_iris.utils.universal import fetch_and_validate_data
7 +from app.connectors.dfir_iris.utils.universal import initialize_client_and_case
8 +from app.connectors.wazuh_indexer.utils.universal import create_wazuh_indexer_client
9 +from app.integrations.sap_siem.schema.sap_siem import AddAssetModel
10 +from app.integrations.sap_siem.schema.sap_siem import CaseResponse
11 +from app.integrations.sap_siem.schema.sap_siem import ErrCode
12 +from app.integrations.sap_siem.schema.sap_siem import InvokeSAPSiemResponse
13 +from app.integrations.sap_siem.schema.sap_siem import IrisCasePayload
14 +from app.integrations.sap_siem.schema.sap_siem import SapSiemWazuhIndexerResponse
15 +from app.integrations.sap_siem.schema.sap_siem import SuspiciousLogin
16 +from app.utils import get_customer_alert_settings
17 +
18 +# global set to keep track of checked IPs
19 +checked_ips = set()
20 +
21 +
22 +async def check_for_suspicious_login(hit, last_invalid_login, suspicious_logins, threshold):
23 + logSource = hit.source.logSource
24 + loginID = hit.source.params_loginID
25 + errCode = hit.source.errCode
26 + country = hit.source.httpReq_country
27 + ip = hit.source.ip
28 + event_timestamp = hit.source.event_timestamp
29 + customer_code = hit.source.customer_code
30 + index = hit.index
31 + id = hit.id
32 + logger.info(f"Checking loginID: {loginID} with errCode: {errCode} and IP: {ip} and index: {index} and id: {id}")
33 +
34 + if ip not in last_invalid_login:
35 + last_invalid_login[ip] = {"count": 0, "event_timestamp": None}
36 +
37 + if errCode in [e.value for e in ErrCode] and errCode != ErrCode.OK.value:
38 + logger.info(f"Found invalid login: {loginID} with IP: {ip} and errCode: {errCode}")
39 + last_invalid_login[ip]["count"] += 1
40 + last_invalid_login[ip]["event_timestamp"] = event_timestamp
41 +
42 + logger.info(f"Checking if last_invalid_login[ip]['count'] >= threshold: {last_invalid_login[ip]['count']} >= {threshold}")
43 + logger.info(f"Hit: {hit}")
44 + if errCode == ErrCode.OK.value and last_invalid_login[ip]["count"] >= threshold:
45 + logger.info(f"Found suspicious login: {loginID} with IP: {ip} and errCode: {errCode}")
46 + suspicious_login = SuspiciousLogin(
47 + _index=index,
48 + _id=id,
49 + customer_code=customer_code,
50 + logSource=logSource,
51 + loginID=loginID,
52 + country=country,
53 + ip=ip,
54 + event_timestamp=event_timestamp,
55 + errMessage=str(errCode),
56 + errDetails=hit.source.errDetails,
57 + )
58 + suspicious_logins.append(suspicious_login)
59 + last_invalid_login[ip] = {"count": 0, "event_timestamp": None}
60 +
61 +
62 +async def find_suscpicious_logins(threshold: int) -> List[SuspiciousLogin]:
63 + es_client = await create_wazuh_indexer_client("Wazuh-Indexer")
64 + scroll_id = None
65 + suspicious_logins = []
66 + last_invalid_login = {}
67 +
68 + while True:
69 + if scroll_id is None:
70 + # Initial search
71 + results = es_client.search(
72 + # ! TODO: change to sap_siem index when ready for deploy
73 + index="integrations_*",
74 + body={
75 + "size": 10,
76 + "query": {"bool": {"must": [{"term": {"case_created": "False"}}, {"term": {"event_analyzed": "False"}}]}},
77 + "_source": ["logSource", "params_loginID", "errCode", "httpReq_country", "ip", "event_timestamp", "customer_code"],
78 + "sort": [{"event_timestamp": {"order": "asc"}}],
79 + },
80 + scroll="1m", # Keep the search context open for 1 minute
81 + )
82 + else:
83 + # Get the next batch of results
84 + results = es_client.scroll(scroll_id=scroll_id, scroll="1m")
85 +
86 + # If there are no more results, break the loop
87 + if not results["hits"]["hits"]:
88 + logger.info("No more results")
89 + break
90 + else:
91 + logger.info(f"Results: {results}")
92 +
93 + results = SapSiemWazuhIndexerResponse(**results)
94 + for hit in results.hits.hits:
95 + logger.info(f"Hit: {hit}")
96 + await check_for_suspicious_login(hit, last_invalid_login, suspicious_logins, threshold=threshold)
97 + await update_event_analyzed_flag(hit.id, hit.index)
98 +
99 + # Update the scroll ID
100 + scroll_id = results.scroll_id
101 +
102 + # Clear the scroll when you're done to free up resources
103 + if scroll_id is not None:
104 + es_client.clear_scroll(scroll_id=scroll_id)
105 +
106 + return suspicious_logins
107 +
108 +
109 +async def collect_user_activity(suspicious_logins: SuspiciousLogin) -> SapSiemWazuhIndexerResponse:
110 + """
111 + Collect the IP addresses of the suspicious logins and query the database for all activity from those IP addresses.
112 + Collects a max of 1000 records.
113 +
114 + :param suspicious_logins: A list of suspicious logins
115 +
116 + :return: List of the user Activity collected from the sap_siem table
117 + """
118 + es_client = await create_wazuh_indexer_client("Wazuh-Indexer")
119 + results = es_client.search(
120 + index="integrations_*",
121 + body={
122 + "size": 1000,
123 + "query": {"bool": {"must": [{"term": {"ip": suspicious_logins.ip}}]}},
124 + },
125 + )
126 + return SapSiemWazuhIndexerResponse(**results)
127 +
128 +
129 +def create_asset_payload(asset: SuspiciousLogin):
130 + if asset.errMessage == "OK":
131 + return AddAssetModel(
132 + name=asset.loginID,
133 + ip=asset.ip,
134 + description=f"Country: {asset.country}\n\nMessage: {asset.errDetails}\n\nTimestamp: {asset.event_timestamp}",
135 + asset_type=1,
136 + compromise_status=1,
137 + analysis_status=2,
138 + )
139 + return AddAssetModel(
140 + name=asset.loginID,
141 + ip=asset.ip,
142 + description=f"Country: {asset.country}\n\nMessage: {asset.errDetails}\n\nTimestamp: {asset.event_timestamp}",
143 + asset_type=1,
144 + analysis_status=2,
145 + )
146 +
147 +
148 +async def update_case_with_asset(case_id: str, asset_payload):
149 + """
150 + Update the case with the asset information.
151 +
152 + :param case_id: The ID of the case to update
153 + :param asset_payload: The payload to update the case with
154 +
155 + :return: None
156 + """
157 + logger.info(f"Updating IRIS case {case_id} with asset: {asset_payload}")
158 + client, case_client = await initialize_client_and_case("DFIR-IRIS")
159 + return await fetch_and_validate_data(
160 + client,
161 + case_client.add_asset,
162 + cid=case_id,
163 + **asset_payload.to_dict(),
164 + )
165 +
166 +
167 +async def handle_user_activity(user_activity: SapSiemWazuhIndexerResponse, unique_instances, case_id):
168 + for hit in user_activity.hits.hits:
169 + current_activity = {
170 + "loginID": hit.source.params_loginID,
171 + "ip": hit.source.ip,
172 + "country": hit.source.httpReq_country,
173 + "errMessage": hit.source.errMessage,
174 + "event_timestamp": hit.source.event_timestamp,
175 + "customer_code": hit.source.customer_code,
176 + "errDetails": hit.source.errDetails,
177 + }
178 + current_activity_frozenset = frozenset(current_activity.items())
179 + if current_activity_frozenset not in unique_instances:
180 + logger.info(f"Adding user activity to IRIS case: {current_activity}")
181 + current_asset = SuspiciousLogin(**current_activity)
182 + asset_payload = create_asset_payload(asset=current_asset)
183 + logger.info(f"Asset Payload: {asset_payload}")
184 + await update_case_with_asset(case_id, asset_payload)
185 + unique_instances.add(current_activity_frozenset)
186 +
187 +
188 +async def mark_as_checked(suspicious_login):
189 + checked_ips.add((suspicious_login.loginID, suspicious_login.ip))
190 +
191 +
192 +async def handle_common_suspicious_login_tasks(
193 + suspicious_login,
194 + unique_instances,
195 + case_ids,
196 + create_case_fn,
197 + session: AsyncSession,
198 +):
199 + logger.info(f"Handling common suspicious login tasks: {suspicious_login}")
200 + case = await create_case_fn(suspicious_login, session=session)
201 + logger.info(f"Case: {case}")
202 + case_ids.append(case.data.case_id)
203 + user_activity = await collect_user_activity(suspicious_login)
204 + logger.info(f"User Activity: {user_activity}")
205 + await handle_user_activity(user_activity, unique_instances, case.data.case_id)
206 + await mark_as_checked(suspicious_login)
207 +
208 +
209 +async def handle_suspicious_login(suspicious_login, unique_instances, case_ids, session: AsyncSession):
210 + logger.info(f"Handling suspicious login: {suspicious_login}")
211 + await handle_common_suspicious_login_tasks(suspicious_login, unique_instances, case_ids, create_iris_case, session)
212 + logger.info(f"Marking suspicious login as checked: {suspicious_login}")
213 + await update_case_created_flag(
214 + id=suspicious_login.id,
215 + index=suspicious_login.index,
216 + )
217 +
218 +
219 +async def update_case_created_flag(id: str, index: str):
220 + """
221 + Update the case_created flag in the Elasticsearch document to True.
222 +
223 + :param suspicious_login: The suspicious login to update
224 +
225 + :return: None
226 + """
227 + es_client = await create_wazuh_indexer_client("Wazuh-Indexer")
228 + try:
229 + es_client.update(
230 + index=index,
231 + id=id,
232 + body={
233 + "doc": {
234 + "case_created": "True",
235 + },
236 + },
237 + )
238 + logger.info(f"Updated case_created flag for suspicious login: {id}")
239 + except Exception as e:
240 + logger.error(
241 + f"Failed to update case created flag {e}",
242 + )
243 + # Attempt to remove read-only block
244 + try:
245 + es_client.indices.put_settings(
246 + index=index,
247 + body={"index.blocks.write": None},
248 + )
249 + logger.info(
250 + f"Removed read-only block from index {index}. Retrying update.",
251 + )
252 +
253 + # Retry the update operation
254 + es_client.update(
255 + index=index,
256 + id=id,
257 + body={"doc": {"case_created": "True"}},
258 + )
259 + logger.info(
260 + f"Added case_created flag to index {index} for suspicious login: {id}",
261 + )
262 +
263 + # Reenable the write block
264 + es_client.indices.put_settings(
265 + index=index,
266 + body={"index.blocks.write": True},
267 + )
268 + except Exception as e2:
269 + logger.error(
270 + f"Failed to remove read-only block from index {index}: {e2}",
271 + )
272 + return False
273 +
274 +
275 +async def update_event_analyzed_flag(id: str, index: str):
276 + """
277 + Update the event_analyzed flag in the Elasticsearch document to True.
278 +
279 + :param suspicious_login: The suspicious login to update
280 +
281 + :return: None
282 + """
283 + es_client = await create_wazuh_indexer_client("Wazuh-Indexer")
284 + try:
285 + es_client.update(
286 + index=index,
287 + id=id,
288 + body={
289 + "doc": {
290 + "event_analyzed": "True",
291 + },
292 + },
293 + )
294 + logger.info(f"Updated event_analyzed flag for suspicious login: {id}")
295 + except Exception as e:
296 + logger.error(
297 + f"Failed to update event analyzed flag {e}",
298 + )
299 + # Attempt to remove read-only block
300 + try:
301 + es_client.indices.put_settings(
302 + index=index,
303 + body={"index.blocks.write": None},
304 + )
305 + logger.info(
306 + f"Removed read-only block from index {index}. Retrying update.",
307 + )
308 +
309 + # Retry the update operation
310 + es_client.update(
311 + index=index,
312 + id=id,
313 + body={"doc": {"event_analyzed": "True"}},
314 + )
315 + logger.info(
316 + f"Added event_analyzed flag to index {index} for suspicious login: {id}",
317 + )
318 +
319 + # Reenable the write block
320 + es_client.indices.put_settings(
321 + index=index,
322 + body={"index.blocks.write": True},
323 + )
324 + except Exception as e2:
325 + logger.error(
326 + f"Failed to remove read-only block from index {index}: {e2}",
327 + )
328 + return False
329 +
330 +
331 +async def create_iris_case(suspicious_login: SuspiciousLogin, session: AsyncSession) -> CaseResponse:
332 + """
333 + Create a case in IRIS for the suspicious activity.
334 +
335 + :param user_activity: A list of suspicious activity from the sap_siem table
336 +
337 + :return: None
338 + """
339 + logger.info(f"Creating IRIS case for suspicious activity: {suspicious_login}")
340 + case_name = (
341 + f"Log Source: {suspicious_login.logSource} "
342 + f"Potential SAP SIEM Unauthorized Access: "
343 + f"{suspicious_login.loginID} from {suspicious_login.ip}"
344 + )
345 +
346 + case_description = (
347 + f"Log Source: {suspicious_login.logSource}\n\n"
348 + f"IP Address: {suspicious_login.ip}\n\n"
349 + f"Country: {suspicious_login.country}\n\n"
350 + f"Timestamp: {suspicious_login.event_timestamp}"
351 + )
352 +
353 + case_customer = (await get_customer_alert_settings(suspicious_login.customer_code, session=session)).iris_customer_id
354 +
355 + payload = IrisCasePayload(
356 + case_name=case_name,
357 + case_description=case_description,
358 + case_customer=case_customer,
359 + case_classification=18,
360 + soc_id="1",
361 + create_customer=False,
362 + )
363 + client, case_client = await initialize_client_and_case("DFIR-IRIS")
364 + result = await fetch_and_validate_data(
365 + client,
366 + case_client.add_case,
367 + **payload.dict(),
368 + )
369 +
370 + return CaseResponse(**result)
371 +
372 +
373 +async def sap_siem_suspicious_logins(threshold: int, session: AsyncSession) -> InvokeSAPSiemResponse:
374 + """
375 + Collects SAP SIEM events.
376 +
377 + Args:
378 + sap_siem_request (CollectSapSiemRequest): The request payload containing the necessary information for the SAP SIEM integration.
379 +
380 + Returns:
381 + InvokeSAPSiemResponse: The response model containing the result of the SAP SIEM integration invocation.
382 +
383 + Raises:
384 + HTTPException: If the SAP SIEM integration fails.
385 + """
386 + logger.info("Checking for suspicious logins")
387 +
388 + suspicious_logins = await find_suscpicious_logins(threshold=threshold)
389 + logger.info(f"Suspicious Logins: {suspicious_logins}")
390 +
391 + unique_instaces = set()
392 + case_ids = []
393 + for suspicious_login in suspicious_logins:
394 + await handle_suspicious_login(suspicious_login, unique_instaces, case_ids, session=session)
395 +
396 + # Clear the global set
397 + checked_ips.clear()
398 + return InvokeSAPSiemResponse(
399 + success=True,
400 + message="SAP SIEM Events collected successfully",
401 + )
backend/app/integrations/utils/utils.py
+13 -14
@@ -38,36 +38,35 @@ async def get_customer_integration_response(
38 return customer_integration_response
39
40
41 -def extract_mimecast_auth_keys(
42 - customer_integration: CustomerIntegrations,
43 -) -> Dict[str, str]:
41 +def extract_auth_keys(customer_integration: CustomerIntegrations, service_name: str) -> Dict[str, str]:
42 """
45 - Extracts the authentication keys for Office365 integration from the given customer integration.
43 + Extracts the authentication keys for the given service name from the customer integration.
44
45 Args:
46 customer_integration (CustomerIntegrations): The customer integration object.
47 + service_name (str): The name of the service to extract the authentication keys for.
48
49 Returns:
51 - Dict[str, str]: A dictionary containing the authentication keys for Office365 integration.
50 + Dict[str, str]: A dictionary containing the authentication keys for the service.
51
52 Raises:
54 - HTTPException: If no authentication keys are found for Office365 integration.
53 + HTTPException: If no authentication keys are found for the service.
54 """
56 - mimecast_auth_keys = {}
55 + auth_keys = {}
56 try:
57 for subscription in customer_integration.integration_subscriptions:
59 - if subscription.integration_service.service_name == "Mimecast":
58 + if subscription.integration_service.service_name == service_name:
59 for auth_key in subscription.integration_auth_keys:
61 - mimecast_auth_keys[auth_key.auth_key_name] = auth_key.auth_value
62 - if not mimecast_auth_keys:
60 + auth_keys[auth_key.auth_key_name] = auth_key.auth_value
61 + if not auth_keys:
62 raise HTTPException(
63 status_code=404,
65 - detail="No auth keys found for Mimecast integration. Please create auth keys for Mimecast integration.",
64 + detail=f"No auth keys found for {service_name} integration. Please create auth keys for {service_name} integration.",
65 )
66 except Exception as e:
68 - logger.error(f"Error extracting auth keys for Mimecast integration: {e}")
67 + logger.error(f"Error extracting auth keys for {service_name} integration: {e}")
68 raise HTTPException(
69 status_code=404,
71 - detail="No auth keys found for Mimecast integration. Please create auth keys for Mimecast integration.",
70 + detail=f"No auth keys found for {service_name} integration. Please create auth keys for {service_name} integration.",
71 )
73 - return mimecast_auth_keys
72 + return auth_keys
backend/app/routers/sap_siem.py new
+23
@@ -0,0 +1,23 @@
1 +from fastapi import APIRouter
2 +
3 +from app.integrations.sap_siem.routes.provision import (
4 + integration_sap_siem_provision_scheduler_router,
5 +)
6 +from app.integrations.sap_siem.routes.sap_siem import integration_sap_siem_router
7 +
8 +# Instantiate the APIRouter
9 +router = APIRouter()
10 +
11 +# Include the SAP SIEM related routes
12 +router.include_router(
13 + integration_sap_siem_router,
14 + prefix="/sap_siem",
15 + tags=["sap_siem"],
16 +)
17 +
18 +# Include the SAP SIEM provisioning related routes
19 +router.include_router(
20 + integration_sap_siem_provision_scheduler_router,
21 + prefix="/sap_siem",
22 + tags=["sap_siem"],
23 +)
backend/app/schedulers/models/scheduler.py
+1
@@ -12,6 +12,7 @@ class JobMetadata(SQLModel, table=True):
12 job_id: str = Field(index=True) # Corresponds to the APScheduler job ID
13 last_success: Optional[datetime] = None
14 time_interval: int # The frequency of the job in minutes
15 + extra_data: Optional[str] = None # Extra data for the job
16 enabled: bool # Indicates if the job is active or not
17
18
backend/app/schedulers/routes/scheduler.py
+4
@@ -1,3 +1,5 @@
1 +from typing import Optional
2 +
3 from fastapi import APIRouter
4 from fastapi import Depends
5 from loguru import logger
@@ -147,6 +149,7 @@ async def pause_job(job_id: str):
149 async def update_job(
150 job_id: str,
151 time_interval: int,
152 + extra_data: Optional[str] = None,
153 session: AsyncSession = Depends(get_db),
154 ):
155 """
@@ -175,6 +178,7 @@ async def update_job(
178 job_id,
179 "update",
180 time_interval=time_interval,
181 + extra_data=extra_data,
182 )
183 logger.info(f"Job {job_id} updated successfully")
184 return {"success": True, "message": "Job updated successfully"}
backend/app/schedulers/scheduler.py
+10
@@ -9,6 +9,13 @@ from app.schedulers.models.scheduler import JobMetadata
9 from app.schedulers.services.agent_sync import agent_sync
10 from app.schedulers.services.invoke_mimecast import invoke_mimecast_integration
11 from app.schedulers.services.invoke_mimecast import invoke_mimecast_integration_ttp
12 +from app.schedulers.services.invoke_sap_siem import invoke_sap_siem_integration_collect
13 +from app.schedulers.services.invoke_sap_siem import (
14 + invoke_sap_siem_integration_multiple_logins_same_ip_analysis,
15 +)
16 +from app.schedulers.services.invoke_sap_siem import (
17 + invoke_sap_siem_integration_suspicious_logins_analysis,
18 +)
19 from app.schedulers.services.monitoring_alert import invoke_suricata_monitoring_alert
20 from app.schedulers.services.monitoring_alert import invoke_wazuh_monitoring_alert
21
@@ -87,6 +94,9 @@ def get_function_by_name(function_name: str):
94 "invoke_mimecast_integration_ttp": invoke_mimecast_integration_ttp,
95 "invoke_wazuh_monitoring_alert": invoke_wazuh_monitoring_alert,
96 "invoke_suricata_monitoring_alert": invoke_suricata_monitoring_alert,
97 + "invoke_sap_siem_integration_collection": invoke_sap_siem_integration_collect,
98 + "invoke_sap_siem_integration_suspicious_logins_analysis": invoke_sap_siem_integration_suspicious_logins_analysis,
99 + "invoke_sap_siem_integration_multiple_logins_same_ip_analysis": invoke_sap_siem_integration_multiple_logins_same_ip_analysis,
100 # Add other function mappings here
101 }
102 return function_map.get(
backend/app/schedulers/services/invoke_sap_siem.py new
+134
@@ -0,0 +1,134 @@
1 +from datetime import datetime
2 +
3 +from dotenv import load_dotenv
4 +from loguru import logger
5 +from sqlalchemy import select
6 +
7 +from app.db.db_session import get_db_session
8 +from app.db.db_session import get_sync_db_session
9 +from app.integrations.models.customer_integration_settings import CustomerIntegrations
10 +from app.integrations.monitoring_alert.routes.monitoring_alert import (
11 + run_sap_siem_multiple_logins_same_ip_analysis,
12 +)
13 +from app.integrations.monitoring_alert.routes.monitoring_alert import (
14 + run_sap_siem_suspicious_logins_analysis,
15 +)
16 +from app.integrations.sap_siem.routes.sap_siem import collect_sap_siem_route
17 +from app.integrations.sap_siem.schema.sap_siem import InvokeSapSiemRequest
18 +from app.integrations.sap_siem.schema.sap_siem import InvokeSAPSiemResponse
19 +from app.schedulers.models.scheduler import JobMetadata
20 +from app.schedulers.utils.universal import get_scheduled_job_metadata
21 +
22 +load_dotenv()
23 +
24 +
25 +async def invoke_sap_siem_integration_collect() -> InvokeSAPSiemResponse:
26 + """
27 + Invokes the SAP SIEM integration for collection.
28 + """
29 + logger.info("Invoking SAP SIEM integration scheduled job.")
30 + customer_codes = []
31 + async with get_db_session() as session:
32 + stmt = select(CustomerIntegrations).where(
33 + CustomerIntegrations.integration_service_name == "SAP SIEM",
34 + )
35 + result = await session.execute(stmt)
36 + customer_codes = [row.customer_code for row in result.scalars()]
37 + logger.info(f"customer_codes: {customer_codes}")
38 + for customer_code in customer_codes:
39 + await collect_sap_siem_route(
40 + InvokeSapSiemRequest(
41 + customer_code=customer_code,
42 + integration_name="SAP SIEM",
43 + time_range=f"{(await get_scheduled_job_metadata('invoke_sap_siem_integration_collection')).time_interval}m",
44 + ),
45 + session,
46 + )
47 + # Close the session
48 + await session.close()
49 + with get_sync_db_session() as session:
50 + # Synchronous ORM operations
51 + job_metadata = session.query(JobMetadata).filter_by(job_id="invoke_sap_siem_integration_collection").one_or_none()
52 + if job_metadata:
53 + job_metadata.last_success = datetime.utcnow()
54 + session.add(job_metadata)
55 + session.commit()
56 + else:
57 + # Handle the case where job_metadata does not exist
58 + print("JobMetadata for 'invoke_mimecast_integration' not found.")
59 +
60 + return InvokeSAPSiemResponse(success=True, message="SAP SIEM integration invoked.")
61 +
62 +
63 +async def invoke_sap_siem_integration_suspicious_logins_analysis() -> InvokeSAPSiemResponse:
64 + """
65 + Invokes the SAP SIEM integration for suspicious logins analysis.
66 + """
67 + logger.info("Invoking SAP SIEM integration for suspicious logins analysis scheduled job.")
68 + customer_codes = []
69 + async with get_db_session() as session:
70 + stmt = select(CustomerIntegrations).where(
71 + CustomerIntegrations.integration_service_name == "SAP SIEM",
72 + )
73 + result = await session.execute(stmt)
74 + customer_codes = [row.customer_code for row in result.scalars()]
75 + logger.info(f"customer_codes: {customer_codes}")
76 + for customer_code in customer_codes:
77 + extra_data = (await get_scheduled_job_metadata("invoke_sap_siem_integration_suspicious_logins_analysis")).extra_data
78 + threshold = int(extra_data) if extra_data is not None else 3
79 + await run_sap_siem_suspicious_logins_analysis(
80 + threshold=threshold,
81 + session=session,
82 + )
83 + # Close the session
84 + await session.close()
85 + with get_sync_db_session() as session:
86 + # Synchronous ORM operations
87 + job_metadata = session.query(JobMetadata).filter_by(job_id="invoke_sap_siem_integration_suspicious_logins_analysis").one_or_none()
88 + if job_metadata:
89 + job_metadata.last_success = datetime.utcnow()
90 + session.add(job_metadata)
91 + session.commit()
92 + else:
93 + # Handle the case where job_metadata does not exist
94 + print("JobMetadata for 'invoke_sap_siem_integration_suspicious_logins_analysis' not found.")
95 +
96 + return InvokeSAPSiemResponse(success=True, message="SAP SIEM integration invoked for suspicious logins analysis.")
97 +
98 +
99 +async def invoke_sap_siem_integration_multiple_logins_same_ip_analysis() -> InvokeSAPSiemResponse:
100 + """
101 + Invokes the SAP SIEM integration for multiple logins from the same IP analysis.
102 + """
103 + logger.info("Invoking SAP SIEM integration for multiple logins from the same IP analysis scheduled job.")
104 + customer_codes = []
105 + async with get_db_session() as session:
106 + stmt = select(CustomerIntegrations).where(
107 + CustomerIntegrations.integration_service_name == "SAP SIEM",
108 + )
109 + result = await session.execute(stmt)
110 + customer_codes = [row.customer_code for row in result.scalars()]
111 + logger.info(f"customer_codes: {customer_codes}")
112 + for customer_code in customer_codes:
113 + extra_data = (await get_scheduled_job_metadata("invoke_sap_siem_integration_multiple_logins_same_ip_analysis")).extra_data
114 + threshold = int(extra_data) if extra_data is not None else 1
115 + await run_sap_siem_multiple_logins_same_ip_analysis(
116 + threshold=threshold,
117 + session=session,
118 + )
119 + # Close the session
120 + await session.close()
121 + with get_sync_db_session() as session:
122 + # Synchronous ORM operations
123 + job_metadata = (
124 + session.query(JobMetadata).filter_by(job_id="invoke_sap_siem_integration_multiple_logins_same_ip_analysis").one_or_none()
125 + )
126 + if job_metadata:
127 + job_metadata.last_success = datetime.utcnow()
128 + session.add(job_metadata)
129 + session.commit()
130 + else:
131 + # Handle the case where job_metadata does not exist
132 + print("JobMetadata for 'invoke_sap_siem_integration_multiple_logins_same_ip_analysis' not found.")
133 +
134 + return InvokeSAPSiemResponse(success=True, message="SAP SIEM integration invoked for multiple logins from the same IP analysis.")
backend/app/schedulers/utils/universal.py
+26
@@ -2,8 +2,12 @@ import os
2
3 import requests
4 from dotenv import load_dotenv
5 +from loguru import logger
6 +from sqlalchemy import select
7
8 from app.auth.services.universal import get_scheduler_password
9 +from app.db.db_session import get_db_session
10 +from app.schedulers.models.scheduler import JobMetadata
11
12 load_dotenv()
13
@@ -43,3 +47,25 @@ def scheduler_login():
47 else:
48 print("Failed to retrieve token")
49 return None
50 +
51 +
52 +async def get_scheduled_job_metadata(job_id: str) -> JobMetadata:
53 + """
54 + Retrieves the metadata for a scheduled job.
55 +
56 + Args:
57 + job_id (str): The ID of the scheduled job.
58 +
59 + Returns:
60 + dict: The metadata for the scheduled job.
61 + Returns None if the metadata retrieval fails.
62 + """
63 + async with get_db_session() as session:
64 + stmt = select(JobMetadata).where(JobMetadata.job_id == job_id)
65 + result = await session.execute(stmt)
66 + job_metadata = result.scalars().first()
67 + if job_metadata:
68 + return job_metadata
69 + else:
70 + logger.info(f"JobMetadata for {job_id} not found.")
71 + return None
backend/copilot.py
+4
@@ -17,6 +17,7 @@ from app.db.db_setup import create_tables
17 from app.db.db_setup import ensure_admin_user
18 from app.db.db_setup import ensure_scheduler_user
19 from app.db.db_setup import ensure_scheduler_user_removed
20 +from app.db.db_setup import update_tables
21 from app.middleware.exception_handlers import custom_http_exception_handler
22 from app.middleware.exception_handlers import validation_exception_handler
23 from app.middleware.exception_handlers import value_error_handler
@@ -41,6 +42,7 @@ from app.routers import logs
42 from app.routers import mimecast
43 from app.routers import monitoring_alert
44 from app.routers import office365
45 +from app.routers import sap_siem
46 from app.routers import scheduler
47 from app.routers import shuffle
48 from app.routers import smtp
@@ -114,6 +116,7 @@ api_router.include_router(office365.router)
116 api_router.include_router(mimecast.router)
117 api_router.include_router(scheduler.router)
118 api_router.include_router(monitoring_alert.router)
119 +api_router.include_router(sap_siem.router)
120
121 # Include the APIRouter in the FastAPI app
122 app.include_router(api_router)
@@ -123,6 +126,7 @@ app.include_router(api_router)
126 async def init_db():
127 # create_tables(engine)
128 await create_tables(async_engine)
129 + await update_tables(async_engine)
130 await create_roles(async_engine)
131 await create_available_integrations(async_engine)
132 await ensure_admin_user(async_engine)
frontend/vite.config.mts
+3 -3
@@ -39,12 +39,12 @@ export default defineConfig({
39 ? {
40 key: fs.readFileSync("/certs/key.pem"),
41 cert: fs.readFileSync("/certs/cert.pem")
42 - }
42 + }
43 : false,
44 proxy: {
45 "/api": {
46 - target: "http://copilot-backend:5000",
47 - //target: "http://127.0.0.1:5000", // for local development
46 + //target: "http://copilot-backend:5000",
47 + target: "http://127.0.0.1:5000", // for local development
48 changeOrigin: true
49 }
50 }