@cryptotaxi247 / CoPilot / commits / daaef998

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 })