Suricata alerts (#146)
* add iris customer id to the alert search within iris to only filter on alert basaed on the customer * Update alert analysis response model * Remove commented out code * precommit fixes
taylor_socfortress committed
Feb 12, 2024 at 07:35 UTC
daaef99813abc81e0cb9eadde5e655e4d02517be
10 files changed
+206
-185
README.md
+11
-8
@@ -81,33 +81,36 @@ By default, an `admin` account is created. The password is printed in stdout the
81
🚀 **YouTube Tutorial:** [SOCFortress CoPilot - Getting Started](https://youtu.be/hu1X9MCW7j0)
82
83
#### SSL
84
+
85
By default Copilot uses a self-signed certificate valid for 365 days from install. You can replace the certificate and
85
-key files with your own. These files should be mounted in the `copilot-frontend` container and you can set the path to
86
+key files with your own. These files should be mounted in the `copilot-frontend` container and you can set the path to
87
your certificate and key files in the `docker-compose.yml` file using the `TLS_CERT_PATH` and `TLS_KEY_PATH`
88
respectively.
89
90
For Example
91
+
92
```bash
93
# Generate a certificate e.g.
94
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365
95
```
96
97
Then update the `docker-compose.yml` file to mount the certificate and key files and set the `TLS_CERT_PATH` and `TLS_KEY_PATH` environment variables.
98
+
99
```yaml
97
- copilot-frontend:
98
- image: ghcr.io/socfortress/copilot-frontend:latest
99
- volumes:
100
+copilot-frontend:
101
+ image: ghcr.io/socfortress/copilot-frontend:latest
102
+ volumes:
103
- PATH_TO_YOUR_CERTS:/etc/letsencrypt
101
- environment:
104
+ environment:
105
- SERVER_HOST=${SERVER_HOST:-localhost} # Set the domain name of your server
106
- TLS_CERT_PATH=/etc/letsencrypt/live/${SERVER_HOST}/fullchain.pem # Set the path to your certificate
107
- TLS_KEY_PATH=/etc/letsencrypt/live/${SERVER_HOST}/privkey.pem # Set the path to your key
105
- ports:
108
+ ports:
109
- "80:80"
110
- "443:443"
111
```
112
110
-```yaml
113
+````yaml
114
115
### Upgrading Copilot
116
@@ -121,7 +124,7 @@ docker compose pull
124
125
# Start the container again
126
docker compose up -d
124
-```
127
+````
128
129
## Connectors
130
backend/app/integrations/monitoring_alert/routes/monitoring_alert.py
+9
-9
@@ -12,6 +12,9 @@ from app.auth.utils import AuthHandler
12
from app.db.db_session import get_db
13
from app.db.universal_models import CustomersMeta
14
from app.integrations.monitoring_alert.models.monitoring_alert import MonitoringAlerts
15
+from app.integrations.monitoring_alert.schema.monitoring_alert import (
16
+ AlertAnalysisResponse,
17
+)
18
from app.integrations.monitoring_alert.schema.monitoring_alert import GraylogPostRequest
19
from app.integrations.monitoring_alert.schema.monitoring_alert import (
20
GraylogPostResponse,
@@ -22,9 +25,6 @@ from app.integrations.monitoring_alert.schema.monitoring_alert import (
25
from app.integrations.monitoring_alert.schema.monitoring_alert import (
26
MonitoringWazuhAlertsRequestModel,
27
)
25
-from app.integrations.monitoring_alert.schema.monitoring_alert import (
26
- WazuhAnalysisResponse,
27
-)
28
from app.integrations.monitoring_alert.services.suricata import analyze_suricata_alerts
29
from app.integrations.monitoring_alert.services.wazuh import analyze_wazuh_alerts
30
@@ -130,12 +130,12 @@ async def create_monitoring_alert(
130
131
@monitoring_alerts_router.post(
132
"/run_analysis/wazuh",
133
- response_model=WazuhAnalysisResponse,
133
+ response_model=AlertAnalysisResponse,
134
)
135
async def run_wazuh_analysis(
136
request: MonitoringWazuhAlertsRequestModel,
137
session: AsyncSession = Depends(get_db),
138
-) -> WazuhAnalysisResponse:
138
+) -> AlertAnalysisResponse:
139
"""
140
This route is used to run analysis on the monitoring alerts.
141
@@ -170,7 +170,7 @@ async def run_wazuh_analysis(
170
# Call the analyze_wazuh_alerts function to analyze the alerts
171
await analyze_wazuh_alerts(monitoring_alerts, customer_meta, session)
172
173
- return WazuhAnalysisResponse(
173
+ return AlertAnalysisResponse(
174
success=True,
175
message="Analysis completed successfully",
176
)
@@ -178,12 +178,12 @@ async def run_wazuh_analysis(
178
179
@monitoring_alerts_router.post(
180
"/run_analysis/suricata",
181
- response_model=WazuhAnalysisResponse,
181
+ response_model=AlertAnalysisResponse,
182
)
183
async def run_suricata_analysis(
184
request: MonitoringWazuhAlertsRequestModel,
185
session: AsyncSession = Depends(get_db),
186
-) -> WazuhAnalysisResponse:
186
+) -> AlertAnalysisResponse:
187
"""
188
This route is used to run analysis on the monitoring alerts.
189
@@ -218,7 +218,7 @@ async def run_suricata_analysis(
218
# Call the analyze_wazuh_alerts function to analyze the alerts
219
await analyze_suricata_alerts(monitoring_alerts, customer_meta, session)
220
221
- return WazuhAnalysisResponse(
221
+ return AlertAnalysisResponse(
222
success=True,
223
message="Analysis completed successfully",
224
)
backend/app/integrations/monitoring_alert/schema/monitoring_alert.py
+34
-7
@@ -174,7 +174,7 @@ class GraylogPostResponse(BaseModel):
174
)
175
176
177
-class WazuhAnalysisResponse(BaseModel):
177
+class AlertAnalysisResponse(BaseModel):
178
success: bool = Field(
179
...,
180
description="Indicates if the request was successful",
@@ -249,6 +249,11 @@ class FilterAlertsRequest(BaseModel):
249
description="The status of the alert. Default to assigned.",
250
example=3,
251
)
252
+ alert_customer_id: int = Field(
253
+ ...,
254
+ description="The customer id of the alert.",
255
+ example=1,
256
+ )
257
258
259
class WazuhIrisAlertContext(BaseModel):
@@ -422,6 +427,9 @@ class SuricataIrisAsset(BaseModel):
427
example=1,
428
)
429
430
+ def to_dict(self):
431
+ return self.dict(exclude_none=True)
432
+
433
434
class SuricataIrisIoc(BaseModel):
435
ioc_value: str = Field(
@@ -467,6 +475,30 @@ class SuricataIrisAlertContext(BaseModel):
475
description="Application protocol of the alert",
476
example="TCP",
477
)
478
+ agent_labels_customer: str = Field(
479
+ ...,
480
+ description="Customer of the endpoint",
481
+ example="SOCFortress",
482
+ )
483
+ customer_iris_id: Optional[int] = Field(
484
+ None,
485
+ description="IRIS ID of the customer",
486
+ )
487
+ customer_name: Optional[str] = Field(
488
+ None,
489
+ description="Name of the customer",
490
+ )
491
+ customer_cases_index: Optional[str] = Field(
492
+ None,
493
+ description="IRIS case index name in the Wazuh-Indexer",
494
+ )
495
+ time_field: Optional[str] = Field(
496
+ "timestamp_utc",
497
+ description="The timefield of the alert to be used when creating the IRIS alert.",
498
+ )
499
+
500
+ def to_dict(self):
501
+ return self.dict(exclude_none=True)
502
503
504
class SuricataIrisAlertPayload(BaseModel):
@@ -480,13 +512,8 @@ class SuricataIrisAlertPayload(BaseModel):
512
description="Description of the alert",
513
example="Intrusion Detected by Firewall",
514
)
483
- alert_source: str = Field(..., description="Source of the alert", example="Wazuh")
515
+ alert_source: str = Field(..., description="Source of the alert", example="Suricata")
516
assets: List[SuricataIrisAsset] = Field(..., description="List of affected assets")
485
- alert_source_link: str = Field(
486
- ...,
487
- description="Link to the alert within Grafana",
488
- example="https://grafana.com",
489
- )
517
alert_status_id: int = Field(..., description="Status ID of the alert", example=3)
518
alert_severity_id: int = Field(
519
...,
backend/app/integrations/monitoring_alert/services/suricata.py
+84
-103
@@ -6,14 +6,11 @@ from fastapi import HTTPException
6
from loguru import logger
7
from sqlalchemy.ext.asyncio import AsyncSession
8
9
-from app.agents.routes.agents import get_agent
10
-from app.agents.schema.agents import AgentsResponse
9
from app.connectors.dfir_iris.utils.universal import fetch_and_validate_data
10
from app.connectors.dfir_iris.utils.universal import initialize_client_and_alert
11
from app.connectors.wazuh_indexer.utils.universal import create_wazuh_indexer_client
12
from app.db.universal_models import CustomersMeta
13
from app.integrations.alert_creation.general.schema.alert import CreateAlertRequest
16
-from app.integrations.alert_creation.general.schema.alert import IrisAsset
14
from app.integrations.alert_creation.general.schema.alert import IrisIoc
15
from app.integrations.alert_creation.general.schema.alert import ValidIocFields
16
from app.integrations.alert_creation.general.services.alert_multi_exclude import (
@@ -26,6 +23,9 @@ from app.integrations.alert_escalation.services.general_alert import (
23
add_alert_to_document,
24
)
25
from app.integrations.monitoring_alert.models.monitoring_alert import MonitoringAlerts
26
+from app.integrations.monitoring_alert.schema.monitoring_alert import (
27
+ AlertAnalysisResponse,
28
+)
29
from app.integrations.monitoring_alert.schema.monitoring_alert import (
30
FilterAlertsRequest,
31
)
@@ -33,18 +33,11 @@ from app.integrations.monitoring_alert.schema.monitoring_alert import SuricataAl
33
from app.integrations.monitoring_alert.schema.monitoring_alert import (
34
SuricataIrisAlertContext,
35
)
36
-from app.integrations.monitoring_alert.schema.monitoring_alert import SuricataIrisAsset
37
-from app.integrations.monitoring_alert.schema.monitoring_alert import (
38
- WazuhAnalysisResponse,
39
-)
36
from app.integrations.monitoring_alert.schema.monitoring_alert import (
41
- WazuhIrisAlertContext,
42
-)
43
-from app.integrations.monitoring_alert.schema.monitoring_alert import (
44
- WazuhIrisAlertPayload,
37
+ SuricataIrisAlertPayload,
38
)
39
+from app.integrations.monitoring_alert.schema.monitoring_alert import SuricataIrisAsset
40
from app.integrations.monitoring_alert.utils.db_operations import remove_alert_id
47
-from app.integrations.utils.alerts import get_asset_type_id
41
from app.integrations.utils.alerts import validate_ioc_type
42
from app.utils import get_customer_alert_settings
43
@@ -61,7 +54,7 @@ def valid_ioc_fields() -> Set[str]:
54
55
56
async def construct_alert_source_link(
64
- alert_details: CreateAlertRequest,
57
+ alert_details: SuricataIrisAlertContext,
58
session: AsyncSession,
59
) -> str:
60
"""
@@ -75,12 +68,11 @@ async def construct_alert_source_link(
68
str
69
The alert source link.
70
"""
78
- # Check if the alert has a process id and that it is not "No process ID found"
79
- if hasattr(alert_details, "process_id") and alert_details.process_id != "No process ID found":
80
- query_string = f"%22query%22:%22process_id:%5C%22{alert_details.process_id}%5C%22%20AND%20"
81
- else:
82
- query_string = f"%22query%22:%22_id:%5C%22{alert_details.id}%5C%22%20AND%20"
83
-
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"
76
grafana_url = (
77
await get_customer_alert_settings(
78
customer_code=alert_details.agent_labels_customer,
@@ -89,9 +81,9 @@ async def construct_alert_source_link(
81
).grafana_url
82
83
return (
92
- f"{grafana_url}/explore?left=%5B%22now-6h%22,%22now%22,%22WAZUH%22,%7B%22refId%22:%22A%22,"
84
+ f"{grafana_url}/explore?left=%5B%22now-6h%22,%22now%22,%22SURICATA%22,%7B%22refId%22:%22A%22,"
85
f"{query_string}"
94
- f"agent_name:%5C%22{alert_details.agent_name}%5C%22%22,"
86
+ f"src_ip:%5C%22{alert_details.src_ip}%5C%22%22,"
87
"%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,"
88
"%22bucketAggs%22:%5B%5D,%22timeField%22:%22timestamp%22%7D%5D"
89
)
@@ -121,10 +113,9 @@ async def build_ioc_payload(alert_details: CreateAlertRequest) -> Optional[IrisI
113
114
115
async def build_asset_payload(
124
- agent_data: AgentsResponse,
125
- alert_details: CreateAlertRequest,
116
+ alert_details: SuricataIrisAlertContext,
117
session: AsyncSession,
127
-) -> IrisAsset:
118
+) -> SuricataIrisAsset:
119
"""
120
Build the payload for an IrisAsset object based on the agent data and alert details.
121
@@ -136,23 +127,23 @@ async def build_asset_payload(
127
IrisAsset: The constructed IrisAsset object.
128
"""
129
# Get the agent_id based on the hostname from the Agents table
139
- if agent_data.success:
140
- return IrisAsset(
141
- asset_name=agent_data.agents[0].hostname,
142
- asset_ip=agent_data.agents[0].ip_address,
130
+ logger.info(f"Building asset payload for alert: {alert_details}")
131
+ if alert_details is not None:
132
+ return SuricataIrisAsset(
133
+ asset_name=alert_details.src_ip,
134
+ asset_ip=alert_details.src_ip,
135
asset_description=await construct_alert_source_link(
136
alert_details,
137
session=session,
138
),
147
- asset_type_id=await get_asset_type_id(agent_data.agents[0].os),
148
- asset_tags=f"agent_id:{agent_data.agents[0].agent_id}",
139
+ asset_type_id=2,
140
)
150
- return IrisAsset()
141
+ return SuricataIrisAsset()
142
143
144
async def fetch_wazuh_indexer_details(alert_id: str, index: str) -> SuricataAlertModel:
145
"""
155
- Fetch the Wazuh alert details from the Wazuh-Indexer.
146
+ Fetch the Suricata alert details from the Wazuh-Indexer.
147
148
Args:
149
alert_id (str): The alert ID.
@@ -162,7 +153,7 @@ async def fetch_wazuh_indexer_details(alert_id: str, index: str) -> SuricataAler
153
CollectAlertsResponse: The response from the Wazuh-Indexer.
154
"""
155
logger.info(
165
- f"Fetching Wazuh alert details for alert_id: {alert_id} and index: {index}",
156
+ f"Fetching Suricata alert details for alert_id: {alert_id} and index: {index}",
157
)
158
159
es_client = await create_wazuh_indexer_client("Wazuh-Indexer")
@@ -172,7 +163,7 @@ async def fetch_wazuh_indexer_details(alert_id: str, index: str) -> SuricataAler
163
164
165
async def fetch_alert_details(alert: MonitoringAlerts) -> SuricataAlertModel:
175
- logger.info(f"Analyzing Wazuh alert: {alert.alert_id}")
166
+ logger.info(f"Analyzing Suricata alert: {alert.alert_id}")
167
alert_details = await fetch_wazuh_indexer_details(alert.alert_id, alert.alert_index)
168
logger.info(f"Alert details: {alert_details}")
169
return alert_details
@@ -199,7 +190,7 @@ async def check_event_exclusion(
190
logger.info("Alert is not excluded due to multi exclusion.")
191
192
202
-async def check_if_open_alert_exists_in_iris(alert_details: SuricataAlertModel) -> list:
193
+async def check_if_open_alert_exists_in_iris(alert_details: SuricataAlertModel, session: AsyncSession) -> list:
194
"""
195
Check if the alert exists in IRIS.
196
@@ -211,8 +202,16 @@ async def check_if_open_alert_exists_in_iris(alert_details: SuricataAlertModel)
202
bool: True if the alert exists in IRIS, False otherwise.
203
"""
204
client, alert_client = await initialize_client_and_alert("DFIR-IRIS")
205
+ customer_iris_id = (
206
+ await get_customer_alert_settings(
207
+ # customer_code=alert_details._source["agent_labels_customer"],
208
+ customer_code="00002",
209
+ session=session,
210
+ )
211
+ ).iris_customer_id
212
request = FilterAlertsRequest(
213
alert_tags=alert_details._source["alert_signature_id"],
214
+ alert_customer_id=customer_iris_id,
215
)
216
params = construct_params(request)
217
alert_exists = await fetch_and_validate_data(
@@ -239,6 +238,7 @@ def construct_params(request: FilterAlertsRequest) -> dict:
238
"sort": request.sort,
239
"alert_tags": request.alert_tags,
240
"alert_status_id": request.alert_status_id,
241
+ "alert_customer_id": request.alert_customer_id,
242
# Add more parameters here as needed
243
}
244
@@ -247,10 +247,9 @@ def construct_params(request: FilterAlertsRequest) -> dict:
247
248
249
async def build_alert_context_payload(
250
- alert_details: CreateAlertRequest,
251
- agent_data: AgentsResponse,
250
+ alert_details: SuricataIrisAlertContext,
251
session: AsyncSession,
253
-) -> WazuhIrisAlertContext:
252
+) -> SuricataIrisAlertContext:
253
"""
254
Builds the payload for the alert context.
255
@@ -260,9 +259,9 @@ async def build_alert_context_payload(
259
session (AsyncSession): The async session.
260
261
Returns:
263
- WazuhIrisAlertContext: The built alert context payload.
262
+ SuricataIrisAlertContext: The built alert context payload.
263
"""
265
- return WazuhIrisAlertContext(
264
+ return SuricataIrisAlertContext(
265
customer_iris_id=(
266
await get_customer_alert_settings(
267
customer_code=alert_details.agent_labels_customer,
@@ -281,67 +280,53 @@ async def build_alert_context_payload(
280
session=session,
281
)
282
).iris_index,
284
- alert_name=alert_details.rule_description,
285
- alert_level=alert_details.rule_level,
283
+ alert_id=alert_details.alert_id,
284
+ alert_name=alert_details.alert_name,
285
+ alert_level=alert_details.alert_level,
286
rule_id=alert_details.rule_id,
287
- rule_mitre_id=getattr(alert_details, "rule_mitre_id", "No rule mitre id found"),
288
- rule_mitre_tactic=getattr(
289
- alert_details,
290
- "rule_mitre_tactic",
291
- "No rule mitre tactic found",
292
- ),
293
- rule_mitre_technique=getattr(
294
- alert_details,
295
- "rule_mitre_technique",
296
- "No rule mitre technique found",
297
- ),
287
+ src_ip=alert_details.src_ip,
288
+ dest_ip=alert_details.dest_ip,
289
+ app_proto=alert_details.app_proto,
290
+ agent_labels_customer=alert_details.agent_labels_customer,
291
)
292
293
294
async def build_alert_payload(
302
- alert_details: CreateAlertRequest,
303
- agent_data,
295
+ alert_details: SuricataIrisAlertContext,
296
ioc_payload: Optional[IrisIoc],
297
session: AsyncSession,
306
-) -> WazuhIrisAlertPayload:
298
+) -> SuricataIrisAlertPayload:
299
"""
300
Builds the payload for an alert based on the provided alert details, agent data, IoC payload, and session.
301
302
Args:
311
- alert_details (CreateAlertRequest): The details of the alert.
303
+ alert_details (SuricataAlertModel): The details of the alert.
304
agent_data: The agent data associated with the alert.
305
ioc_payload (Optional[IrisIoc]): The IoC payload associated with the alert.
306
session (AsyncSession): The session used for database operations.
307
308
Returns:
317
- WazuhIrisAlertPayload: The built alert payload.
309
+ SuricataIrisAlertPayload: The built alert payload.
310
"""
311
asset_payload = await build_asset_payload(
320
- agent_data,
312
alert_details=alert_details,
313
session=session,
314
)
315
+ logger.info(f"Asset payload: {asset_payload}")
316
+
317
context_payload = await build_alert_context_payload(
318
alert_details=alert_details,
326
- agent_data=agent_data,
319
session=session,
320
)
329
- timefield = (
330
- await get_customer_alert_settings(
331
- customer_code=alert_details.agent_labels_customer,
332
- session=session,
333
- )
334
- ).timefield
335
- # Get the timefield value from the alert_details
336
- if hasattr(alert_details, timefield):
337
- alert_details.time_field = getattr(alert_details, timefield)
321
+
322
logger.info(f"Alert has context: {context_payload}")
323
+
324
if ioc_payload:
325
logger.info(f"Alert has IoC: {ioc_payload}")
341
- return WazuhIrisAlertPayload(
342
- alert_title=alert_details.rule_description,
343
- alert_description=alert_details.rule_description,
344
- alert_source="COPILOT WAZUH ANALYSIS",
326
+ return SuricataIrisAlertPayload(
327
+ alert_title=alert_details.alert_name,
328
+ alert_description=alert_details.alert_name,
329
+ alert_source="COPILOT SURICATA ANALYSIS",
330
assets=[asset_payload],
331
alert_status_id=3,
332
alert_severity_id=5,
@@ -358,10 +343,10 @@ async def build_alert_payload(
343
)
344
else:
345
logger.info("Alert does not have IoC")
361
- return WazuhIrisAlertPayload(
362
- alert_title=alert_details.rule_description,
363
- alert_description=alert_details.rule_description,
364
- alert_source="COPILOT WAZUH ANALYSIS",
346
+ return SuricataIrisAlertPayload(
347
+ alert_title=alert_details.alert_name,
348
+ alert_description=alert_details.alert_name,
349
+ alert_source="COPILOT SURICATA ANALYSIS",
350
assets=[asset_payload],
351
alert_status_id=3,
352
alert_severity_id=5,
@@ -381,13 +366,13 @@ async def create_alert_details(
366
alert_details: SuricataAlertModel,
367
) -> SuricataIrisAlertContext:
368
"""
384
- Create an alert details object from the Wazuh alert details.
369
+ Create an alert details object from the Suricata alert details.
370
371
Args:
387
- alert_details (SuricataAlertModel): The Wazuh alert details.
372
+ alert_details (SuricataAlertModel): The Suricata alert details.
373
374
Returns:
390
- CreateAlertRequest: The alert details object.
375
+ SuricataIrisAlertContext: The alert details object.
376
"""
377
logger.info(f"Creating alert details for alert: {alert_details}")
378
return SuricataIrisAlertContext(
@@ -403,6 +388,8 @@ async def create_alert_details(
388
"app_proto",
389
"No application protocol found",
390
),
391
+ agent_labels_customer=alert_details._source["agent_labels_customer"],
392
+ time_field=alert_details._source.get("timestamp_utc", alert_details._source.get("timestamp")),
393
)
394
395
@@ -424,19 +411,13 @@ async def create_and_update_alert_in_iris(
411
alert_details = await create_alert_details(alert_details)
412
ioc_payload = await build_ioc_payload(alert_details)
413
logger.info(f"Alert details: {alert_details}")
427
- # ! TODO: REVIST THIS TOMORROW
414
iris_alert_payload = await build_alert_payload(
415
alert_details=alert_details,
430
- # ! I DONT NEED TO BUILD THE AGENT DATA CAUSE I GET THIS FROM THE FUNCTION
431
- agent_data=SuricataIrisAsset(
432
- asset_name=alert_details.src_ip,
433
- asset_ip=alert_details.src_ip,
434
- asset_description="Source IP of the alert",
435
- asset_type_id=9,
436
- ),
416
ioc_payload=ioc_payload,
417
session=session,
418
)
419
+ logger.info(f"Alert payload: {iris_alert_payload}")
420
+
421
client, alert_client = await initialize_client_and_alert("DFIR-IRIS")
422
result = await fetch_and_validate_data(
423
client,
@@ -445,18 +426,19 @@ async def create_and_update_alert_in_iris(
426
)
427
alert_id = result["data"]["alert_id"]
428
logger.info(f"Successfully created alert {alert_id} in IRIS.")
429
+
430
await fetch_and_validate_data(
431
client,
432
alert_client.update_alert,
433
alert_id,
452
- {"alert_tags": f"{alert_details._source.alert_signature_id}"},
434
+ {"alert_tags": f"{alert_details.alert_id}"},
435
)
436
# Update the alert with the asset payload
437
await fetch_and_validate_data(
438
client,
439
alert_client.update_alert,
440
alert_id,
459
- {"assets": [dict(IrisAsset(**iris_alert_payload.assets[0].to_dict()))]},
441
+ {"assets": [dict(SuricataIrisAsset(**iris_alert_payload.assets[0].to_dict()))]},
442
)
443
if ioc_payload:
444
await fetch_and_validate_data(
@@ -507,9 +489,9 @@ async def analyze_suricata_alerts(
489
monitoring_alerts: MonitoringAlerts,
490
customer_meta: CustomersMeta,
491
session: AsyncSession,
510
-) -> WazuhAnalysisResponse:
492
+) -> AlertAnalysisResponse:
493
"""
512
- Analyze the given Wazuh alerts and create an alert if necessary. Otherwise update the existing alert with the asset.
494
+ Analyze the given Suricata alerts and create an alert if necessary. Otherwise update the existing alert with the asset.
495
496
1. For each alert, extract the metadata from the Wazuh-Indexer.
497
2. Check if the alert exists in IRIS. If it does, update the alert with the asset. If it does not, create the alert in IRIS.
@@ -521,12 +503,12 @@ async def analyze_suricata_alerts(
503
session (AsyncSession): The database session.
504
505
Returns:
524
- WazuhAnalysisResponse: The analysis response.
506
+ AlertAnalysisResponse: The analysis response.
507
"""
526
- logger.info(f"Analyzing Wazuh alerts with customer_meta: {customer_meta}")
508
+ logger.info(f"Analyzing Suricata alerts with customer_meta: {customer_meta}")
509
for alert in monitoring_alerts:
510
alert_details = await fetch_alert_details(alert)
529
- iris_alert_id = await check_if_open_alert_exists_in_iris(alert_details)
511
+ iris_alert_id = await check_if_open_alert_exists_in_iris(alert_details, session=session)
512
if iris_alert_id == []:
513
logger.info(
514
f"Alert {alert_details._id} does not exist in IRIS. Creating alert.",
@@ -535,7 +517,7 @@ async def analyze_suricata_alerts(
517
alert_details,
518
session,
519
)
538
- return None
520
+ logger.info(f"Alert {iris_alert_id} created in IRIS.")
521
await remove_alert_id(alert.alert_id, session)
522
es_client = await create_wazuh_indexer_client("Wazuh-Indexer")
523
await add_alert_to_document(
@@ -552,6 +534,7 @@ async def analyze_suricata_alerts(
534
logger.info(
535
f"Alert {iris_alert_id} exists in IRIS. Updating alert with the asset.",
536
)
537
+
538
# Fetch the current list of assets from the alert to avoid overwriting them
539
client, alert_client = await initialize_client_and_alert("DFIR-IRIS")
540
current_assets = await get_current_assets(
@@ -560,13 +543,11 @@ async def analyze_suricata_alerts(
543
iris_alert_id,
544
)
545
alert_details = await create_alert_details(alert_details)
563
- agent_details = await get_agent(alert_details.agent_id, session)
546
asset_payload = await build_asset_payload(
565
- agent_data=agent_details,
547
alert_details=alert_details,
548
session=session,
549
)
569
- current_assets.append(dict(IrisAsset(**asset_payload.to_dict())))
550
+ current_assets.append(dict(SuricataIrisAsset(**asset_payload.to_dict())))
551
current_assets = await remove_duplicate_assets(current_assets)
552
await update_alert_with_assets(
553
client,
@@ -579,14 +560,14 @@ async def analyze_suricata_alerts(
560
await add_alert_to_document(
561
es_client=es_client,
562
alert=AddAlertRequest(
582
- alert_id=alert_details.id,
583
- index_name=alert_details.index,
563
+ alert_id=alert.alert_id,
564
+ index_name=alert.alert_index,
565
),
566
soc_alert_id=iris_alert_id,
567
session=session,
568
)
569
589
- return WazuhAnalysisResponse(
570
+ return AlertAnalysisResponse(
571
success=True,
591
- message="Wazuh alerts analyzed successfully",
572
+ message="Suricata alerts analyzed successfully",
573
)
backend/app/integrations/monitoring_alert/services/wazuh.py
+15
-8
@@ -27,12 +27,12 @@ from app.integrations.alert_escalation.services.general_alert import (
27
)
28
from app.integrations.monitoring_alert.models.monitoring_alert import MonitoringAlerts
29
from app.integrations.monitoring_alert.schema.monitoring_alert import (
30
- FilterAlertsRequest,
30
+ AlertAnalysisResponse,
31
)
32
-from app.integrations.monitoring_alert.schema.monitoring_alert import WazuhAlertModel
32
from app.integrations.monitoring_alert.schema.monitoring_alert import (
34
- WazuhAnalysisResponse,
33
+ FilterAlertsRequest,
34
)
35
+from app.integrations.monitoring_alert.schema.monitoring_alert import WazuhAlertModel
36
from app.integrations.monitoring_alert.schema.monitoring_alert import (
37
WazuhIrisAlertContext,
38
)
@@ -195,7 +195,7 @@ async def check_event_exclusion(
195
logger.info("Alert is not excluded due to multi exclusion.")
196
197
198
-async def check_if_open_alert_exists_in_iris(alert_details: WazuhAlertModel) -> list:
198
+async def check_if_open_alert_exists_in_iris(alert_details: WazuhAlertModel, session: AsyncSession) -> list:
199
"""
200
Check if the alert exists in IRIS.
201
@@ -207,7 +207,13 @@ async def check_if_open_alert_exists_in_iris(alert_details: WazuhAlertModel) ->
207
bool: True if the alert exists in IRIS, False otherwise.
208
"""
209
client, alert_client = await initialize_client_and_alert("DFIR-IRIS")
210
- request = FilterAlertsRequest(alert_tags=alert_details._source["rule_id"])
210
+ customer_iris_id = (
211
+ await get_customer_alert_settings(
212
+ customer_code=alert_details._source["agent_labels_customer"],
213
+ session=session,
214
+ )
215
+ ).iris_customer_id
216
+ request = FilterAlertsRequest(alert_tags=alert_details._source["rule_id"], alert_customer_id=customer_iris_id)
217
params = construct_params(request)
218
alert_exists = await fetch_and_validate_data(
219
client,
@@ -233,6 +239,7 @@ def construct_params(request: FilterAlertsRequest) -> dict:
239
"sort": request.sort,
240
"alert_tags": request.alert_tags,
241
"alert_status_id": request.alert_status_id,
242
+ "alert_customer_id": request.alert_customer_id,
243
# Add more parameters here as needed
244
}
245
@@ -491,7 +498,7 @@ async def analyze_wazuh_alerts(
498
monitoring_alerts: MonitoringAlerts,
499
customer_meta: CustomersMeta,
500
session: AsyncSession,
494
-) -> WazuhAnalysisResponse:
501
+) -> AlertAnalysisResponse:
502
"""
503
Analyze the given Wazuh alerts and create an alert if necessary. Otherwise update the existing alert with the asset.
504
@@ -512,7 +519,7 @@ async def analyze_wazuh_alerts(
519
for alert in monitoring_alerts:
520
alert_details = await fetch_alert_details(alert)
521
await check_event_exclusion(alert_details, alert_detail_service, session)
515
- iris_alert_id = await check_if_open_alert_exists_in_iris(alert_details)
522
+ iris_alert_id = await check_if_open_alert_exists_in_iris(alert_details, session=session)
523
if iris_alert_id == []:
524
logger.info(
525
f"Alert {alert_details._id} does not exist in IRIS. Creating alert.",
@@ -571,7 +578,7 @@ async def analyze_wazuh_alerts(
578
session=session,
579
)
580
574
- return WazuhAnalysisResponse(
581
+ return AlertAnalysisResponse(
582
success=True,
583
message="Wazuh alerts analyzed successfully",
584
)
backend/app/schedulers/services/monitoring_alert.py
+7
-7
@@ -12,22 +12,22 @@ from app.integrations.monitoring_alert.routes.monitoring_alert import (
12
)
13
from app.integrations.monitoring_alert.routes.monitoring_alert import run_wazuh_analysis
14
from app.integrations.monitoring_alert.schema.monitoring_alert import (
15
- MonitoringWazuhAlertsRequestModel,
15
+ AlertAnalysisResponse,
16
)
17
from app.integrations.monitoring_alert.schema.monitoring_alert import (
18
- WazuhAnalysisResponse,
18
+ MonitoringWazuhAlertsRequestModel,
19
)
20
from app.schedulers.models.scheduler import JobMetadata
21
22
load_dotenv()
23
24
25
-async def invoke_wazuh_monitoring_alert() -> WazuhAnalysisResponse:
25
+async def invoke_wazuh_monitoring_alert() -> AlertAnalysisResponse:
26
"""
27
Invokes the Wazuh monitoring alerts scheduled job.
28
29
Returns:
30
- WazuhAnalysisResponse: The response indicating the success of invoking the monitoring alerts.
30
+ AlertAnalysisResponse: The response indicating the success of invoking the monitoring alerts.
31
"""
32
logger.info("Invoking Wazuh monitoring alerts scheduled job.")
33
customer_codes = []
@@ -54,13 +54,13 @@ async def invoke_wazuh_monitoring_alert() -> WazuhAnalysisResponse:
54
# Handle the case where job_metadata does not exist
55
logger.error("JobMetadata for 'invoke_wazuh_monitoring_alert' not found.")
56
57
- return WazuhAnalysisResponse(
57
+ return AlertAnalysisResponse(
58
success=True,
59
message="Wazuh monitoring alerts invoked.",
60
)
61
62
63
-async def invoke_suricata_monitoring_alert() -> WazuhAnalysisResponse:
63
+async def invoke_suricata_monitoring_alert() -> AlertAnalysisResponse:
64
"""
65
Invokes the Suricata monitoring alerts scheduled job.
66
@@ -94,7 +94,7 @@ async def invoke_suricata_monitoring_alert() -> WazuhAnalysisResponse:
94
"JobMetadata for 'invoke_suricata_monitoring_alert' not found.",
95
)
96
97
- return WazuhAnalysisResponse(
97
+ return AlertAnalysisResponse(
98
success=True,
99
message="Suricata monitoring alerts invoked.",
100
)
docker-compose.dev.yml
+18
-18
@@ -2,26 +2,26 @@ version: "2"
2
3
services:
4
copilot-backend:
5
- build:
6
- context: backend
7
- dockerfile: Dockerfile
8
- volumes:
9
- - ./data/copilot-backend-data/logs:/opt/logs
10
- # Mount the copilot.db file to persist the database
11
- - ./data/data:/opt/copilot/backend/data
5
+ build:
6
+ context: backend
7
+ dockerfile: Dockerfile
8
+ volumes:
9
+ - ./data/copilot-backend-data/logs:/opt/logs
10
+ # Mount the copilot.db file to persist the database
11
+ - ./data/data:/opt/copilot/backend/data
12
13
copilot-frontend:
14
- build:
15
- context: frontend
16
- dockerfile: Dockerfile
17
- target: dev
18
- volumes:
19
- - ./frontend:/app
20
- environment:
21
- - SERVER_HOST=${SERVER_HOST:-localhost} # Set the domain name of your server
22
- ports:
23
- - "80:80"
24
- - "5173:5173"
14
+ build:
15
+ context: frontend
16
+ dockerfile: Dockerfile
17
+ target: dev
18
+ volumes:
19
+ - ./frontend:/app
20
+ environment:
21
+ - SERVER_HOST=${SERVER_HOST:-localhost} # Set the domain name of your server
22
+ ports:
23
+ - "80:80"
24
+ - "5173:5173"
25
26
networks:
27
default:
docker-compose.yml
+11
-11
@@ -2,19 +2,19 @@ version: "2"
2
3
services:
4
copilot-backend:
5
- image: ghcr.io/socfortress/copilot-backend:latest
6
- volumes:
7
- - ./data/copilot-backend-data/logs:/opt/logs
8
- # Mount the copilot.db file to persist the database
9
- - ./data/data:/opt/copilot/backend/data
5
+ image: ghcr.io/socfortress/copilot-backend:latest
6
+ volumes:
7
+ - ./data/copilot-backend-data/logs:/opt/logs
8
+ # Mount the copilot.db file to persist the database
9
+ - ./data/data:/opt/copilot/backend/data
10
11
copilot-frontend:
12
- image: ghcr.io/socfortress/copilot-frontend:latest
13
- environment:
14
- - SERVER_HOST=${SERVER_HOST:-localhost} # Set the domain name of your server
15
- ports:
16
- - "80:80"
17
- - "443:443"
12
+ image: ghcr.io/socfortress/copilot-frontend:latest
13
+ environment:
14
+ - SERVER_HOST=${SERVER_HOST:-localhost} # Set the domain name of your server
15
+ ports:
16
+ - "80:80"
17
+ - "443:443"
18
19
networks:
20
default:
frontend/src/api/httpClient.ts
+1
-1
@@ -4,7 +4,7 @@ import axios, { type AxiosRequestHeaders } from "axios"
4
// import { useGlobalActions } from "@/composables/useGlobalActions"
5
6
const HttpClient = axios.create({
7
- baseURL: '/api'
7
+ baseURL: "/api"
8
})
9
10
let __TOKEN_REFRESHING = false
frontend/vite.config.mts
+16
-13
@@ -4,7 +4,7 @@ import vue from "@vitejs/plugin-vue"
4
import vueJsx from "@vitejs/plugin-vue-jsx"
5
import svgLoader from "vite-svg-loader"
6
import Components from "unplugin-vue-components/vite"
7
-import fs from 'fs';
7
+import fs from "fs"
8
// import { analyzer } from "vite-bundle-analyzer"
9
10
// https://vitejs.dev/config/
@@ -33,16 +33,19 @@ export default defineConfig({
33
optimizeDeps: {
34
include: ["fast-deep-equal"]
35
},
36
- server: {
37
- https: (fs.existsSync('/certs/key.pem') && fs.existsSync('/certs/cert.pem')) ? {
38
- key: fs.readFileSync('/certs/key.pem'),
39
- cert: fs.readFileSync('/certs/cert.pem'),
40
- } : false,
41
- proxy: {
42
- '/api': {
43
- target: 'http://copilot-backend:5000',
44
- changeOrigin: true,
45
- }
46
- }
47
- }
36
+ server: {
37
+ https:
38
+ fs.existsSync("/certs/key.pem") && fs.existsSync("/certs/cert.pem")
39
+ ? {
40
+ key: fs.readFileSync("/certs/key.pem"),
41
+ cert: fs.readFileSync("/certs/cert.pem")
42
+ }
43
+ : false,
44
+ proxy: {
45
+ "/api": {
46
+ target: "http://copilot-backend:5000",
47
+ changeOrigin: true
48
+ }
49
+ }
50
+ }
51
})