main
py 492 lines 17.4 KB
Raw
1 import os
2 from datetime import datetime
3 from typing import List
4 from typing import Optional
5 from typing import Type
6 from typing import Union
7
8 import aiofiles
9 from fastapi import UploadFile
10 from loguru import logger
11 from pydantic import BaseModel
12 from sqlalchemy.ext.asyncio import AsyncSession
13 from sqlalchemy.future import select
14 from werkzeug.utils import secure_filename
15
16 from app.connectors.cortex.utils.universal import verify_cortex_connection
17 from app.connectors.grafana.utils.universal import verify_grafana_connection
18 from app.connectors.graylog.utils.universal import verify_graylog_connection
19 from app.connectors.influxdb.utils.universal import verify_influxdb_connection
20 from app.connectors.models import Connectors
21 from app.connectors.portainer.utils.universal import verify_portainer_connection
22 from app.connectors.schema import ConnectorResponse
23 from app.connectors.shuffle.utils.universal import verify_shuffle_connection
24 from app.connectors.sublime.utils.universal import verify_sublime_connection
25 from app.connectors.talon.utils.universal import verify_talon_connection
26 from app.connectors.velociraptor.utils.universal import verify_velociraptor_connection
27 from app.connectors.wazuh_indexer.utils.universal import verify_wazuh_indexer_connection
28 from app.connectors.wazuh_manager.utils.universal import verify_wazuh_manager_connection
29 from app.integrations.utils.event_shipper import verify_event_shipper_connection
30 from app.utils import verify_alert_creation_provisioning_connection
31 from app.utils import verify_haproxy_provisioning_connection
32 from app.utils import verify_virustotal_connection
33 from app.utils import verify_wazuh_worker_provisioning_connection
34
35 UPLOAD_FOLDER = "data"
36 UPLOAD_FOLDER = os.path.join(
37 os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
38 UPLOAD_FOLDER,
39 )
40 ALLOWED_EXTENSIONS = set(["yaml"]) # replace with your allowed file extensions
41
42
43 # Create an interface for connector services
44 class ConnectorServiceInterface(BaseModel):
45 async def verify_authentication(
46 self,
47 connector: ConnectorResponse,
48 ) -> Optional[ConnectorResponse]:
49 raise NotImplementedError
50
51
52 # Wazuh Manager Service
53 class WazuhManagerService(ConnectorServiceInterface):
54 async def verify_authentication(
55 self,
56 connector: ConnectorResponse,
57 ) -> Optional[ConnectorResponse]:
58 return await verify_wazuh_manager_connection(connector.connector_name)
59
60
61 # Wazuh Indexer Service
62 class WazuhIndexerService(ConnectorServiceInterface):
63 async def verify_authentication(
64 self,
65 connector: ConnectorResponse,
66 ) -> Optional[ConnectorResponse]:
67 return await verify_wazuh_indexer_connection(connector.connector_name)
68
69
70 # Velociraptor Service
71 class VelociraptorService(ConnectorServiceInterface):
72 async def verify_authentication(
73 self,
74 connector: ConnectorResponse,
75 ) -> Optional[ConnectorResponse]:
76 return await verify_velociraptor_connection(connector.connector_name)
77
78
79 # Graylog Service
80 class GraylogService(ConnectorServiceInterface):
81 async def verify_authentication(
82 self,
83 connector: ConnectorResponse,
84 ) -> Optional[ConnectorResponse]:
85 return await verify_graylog_connection(connector.connector_name)
86
87
88 # Graylog Network Service (for second Graylog instance)
89 class GraylogNetworkService(ConnectorServiceInterface):
90 async def verify_authentication(
91 self,
92 connector: ConnectorResponse,
93 ) -> Optional[ConnectorResponse]:
94 return await verify_graylog_connection(connector.connector_name)
95
96
97 # Cortex Service
98 class CortexService(ConnectorServiceInterface):
99 async def verify_authentication(
100 self,
101 connector: ConnectorResponse,
102 ) -> Optional[ConnectorResponse]:
103 return await verify_cortex_connection(connector.connector_name)
104
105
106 # Shuffle Service
107 class ShuffleService(ConnectorServiceInterface):
108 async def verify_authentication(
109 self,
110 connector: ConnectorResponse,
111 ) -> Optional[ConnectorResponse]:
112 return await verify_shuffle_connection(connector.connector_name)
113
114
115 # Sublime Service
116 class SublimeService(ConnectorServiceInterface):
117 async def verify_authentication(
118 self,
119 connector: ConnectorResponse,
120 ) -> Optional[ConnectorResponse]:
121 return await verify_sublime_connection(connector.connector_name)
122
123
124 # InfluxDB Service
125 class InfluxDBService(ConnectorServiceInterface):
126 async def verify_authentication(
127 self,
128 connector: ConnectorResponse,
129 ) -> Optional[ConnectorResponse]:
130 return await verify_influxdb_connection(connector.connector_name)
131
132
133 # Grafana Service
134 class GrafanaService(ConnectorServiceInterface):
135 async def verify_authentication(
136 self,
137 connector: ConnectorResponse,
138 ) -> Optional[ConnectorResponse]:
139 return await verify_grafana_connection(connector.connector_name)
140
141
142 # Wazuh Worker Provisioning Service
143 class WazuhWorkerProvisioningService(ConnectorServiceInterface):
144 async def verify_authentication(
145 self,
146 connector: ConnectorResponse,
147 ) -> Optional[ConnectorResponse]:
148 return await verify_wazuh_worker_provisioning_connection(
149 connector.connector_name,
150 )
151
152
153 # HAProxy Provisioning Service
154 class HAProxyProvisioningService(ConnectorServiceInterface):
155 async def verify_authentication(
156 self,
157 connector: ConnectorResponse,
158 ) -> Optional[ConnectorResponse]:
159 return await verify_haproxy_provisioning_connection(
160 connector.connector_name,
161 )
162
163
164 # Event Shipper Service
165 class EventShipperService(ConnectorServiceInterface):
166 async def verify_authentication(
167 self,
168 connector: ConnectorResponse,
169 ) -> Optional[ConnectorResponse]:
170 return await verify_event_shipper_connection(connector.connector_name)
171
172
173 # Alert Creation Service
174 class AlertCreationService(ConnectorServiceInterface):
175 async def verify_authentication(
176 self,
177 connector: ConnectorResponse,
178 ) -> Optional[ConnectorResponse]:
179 return await verify_alert_creation_provisioning_connection(
180 connector.connector_name,
181 )
182
183
184 # Virustotal Service
185 class VirustotalService(ConnectorServiceInterface):
186 async def verify_authentication(
187 self,
188 connector: ConnectorResponse,
189 ) -> Optional[ConnectorResponse]:
190 return await verify_virustotal_connection(connector.connector_name)
191
192
193 # Portainer Service
194 class PortainerService(ConnectorServiceInterface):
195 async def verify_authentication(
196 self,
197 connector: ConnectorResponse,
198 ) -> Optional[ConnectorResponse]:
199 return await verify_portainer_connection(connector.connector_name)
200
201
202 # Talon Service
203 class TalonService(ConnectorServiceInterface):
204 async def verify_authentication(
205 self,
206 connector: ConnectorResponse,
207 ) -> Optional[ConnectorResponse]:
208 return await verify_talon_connection(connector.connector_name)
209
210
211 # Factory function to create a service instance based on connector name
212 def get_connector_service(connector_name: str) -> Type[ConnectorServiceInterface]:
213 """
214 Retrieves the service class for the given connector name.
215
216 Args:
217 connector_name (str): The name of the connector.
218
219 Returns:
220 Type[ConnectorServiceInterface]: The service class for the connector, or None if not found.
221 """
222 service_map = {
223 "Wazuh-Manager": WazuhManagerService,
224 "Wazuh-Indexer": WazuhIndexerService,
225 "Velociraptor": VelociraptorService,
226 "Graylog": GraylogService,
227 "Graylog-Network": GraylogNetworkService,
228 "Cortex": CortexService,
229 "Shuffle": ShuffleService,
230 "Sublime": SublimeService,
231 "InfluxDB": InfluxDBService,
232 "Grafana": GrafanaService,
233 "Wazuh Worker Provisioning": WazuhWorkerProvisioningService,
234 "HAProxy Provisioning": HAProxyProvisioningService,
235 "Event Shipper": EventShipperService,
236 "Alert Creation Provisioning": AlertCreationService,
237 "VirusTotal": VirustotalService,
238 "Portainer": PortainerService,
239 "Talon": TalonService,
240 }
241 return service_map.get(connector_name, None)
242
243
244 class ConnectorServices:
245 """
246 Service class for handling operations related to connectors.
247 """
248
249 @classmethod
250 async def fetch_all_connectors(
251 cls,
252 session: AsyncSession,
253 ) -> List[ConnectorResponse]:
254 """
255 Fetches all connectors from the database.
256
257 Args:
258 session (AsyncSession): The database session.
259
260 Returns:
261 List[ConnectorResponse]: A list of ConnectorResponse objects representing the fetched connectors.
262 """
263 try:
264 result = await session.execute(select(Connectors))
265 except Exception as e:
266 logger.exception(f"Failed to fetch all connectors: {e}")
267 exit(0)
268 connectors = result.scalars().all()
269 return [ConnectorResponse.from_orm(connector) for connector in connectors]
270
271 @classmethod
272 async def fetch_connector_by_id(
273 cls,
274 connector_id: int,
275 session: AsyncSession,
276 ) -> Optional[ConnectorResponse]:
277 """
278 Fetches a connector by its ID from the database.
279
280 Args:
281 connector_id (int): The ID of the connector to fetch.
282 session (AsyncSession): The database session.
283
284 Returns:
285 Optional[ConnectorResponse]: The fetched connector, or None if not found.
286 """
287 result = await session.execute(
288 select(Connectors).where(Connectors.id == connector_id),
289 )
290 connector = result.scalar_one_or_none()
291 if connector:
292 return ConnectorResponse.from_orm(connector)
293 return None
294
295 @classmethod
296 async def fetch_connector_by_name(
297 cls,
298 connector_name: str,
299 session: AsyncSession,
300 ) -> Optional[ConnectorResponse]:
301 """
302 Fetches a connector by its name from the database.
303
304 Args:
305 connector_name (str): The name of the connector to fetch.
306 session (AsyncSession): The database session.
307
308 Returns:
309 Optional[ConnectorResponse]: The fetched connector, or None if not found.
310 """
311 try:
312 result = await session.execute(
313 select(Connectors).where(Connectors.connector_name == connector_name),
314 )
315 connector = result.scalar_one_or_none()
316 if connector:
317 return ConnectorResponse.from_orm(connector)
318 return None
319 except Exception as e:
320 logger.error(f"Error fetching connector by name '{connector_name}': {e}")
321 return None
322
323 @classmethod
324 async def verify_connector_by_id(
325 cls,
326 connector_id: int,
327 session: AsyncSession,
328 ) -> Optional[ConnectorResponse]:
329 """
330 Verify a connector by making an API call to it asynchronously.
331
332 Given a connector ID, this method retrieves the corresponding connector
333 record from the database, if it exists, and makes an API call to the connector.
334
335 Args:
336 connector_id (int): The ID of the connector to verify.
337 session (AsyncSession): The SQLAlchemy asynchronous session to use.
338
339 Returns:
340 Optional[ConnectorResponse]: The connector in its Pydantic representation, or None if not found.
341 """
342 query = select(Connectors).where(Connectors.id == connector_id)
343 connector = (await session.execute(query)).scalars().first()
344
345 if not connector:
346 logger.info(f"No connector found for ID: {connector_id}")
347 return None
348
349 try:
350 # Convert the SQLModel object to a Pydantic model
351 connector_response = ConnectorResponse.from_orm(connector)
352
353 # Get the appropriate service for this connector
354 ServiceClass = get_connector_service(connector_response.connector_name)
355
356 if ServiceClass is not None:
357 service_instance = ServiceClass()
358 # If verify_authentication is an async function, you will need to await it
359 connector_response = await service_instance.verify_authentication(
360 connector_response,
361 )
362
363 # If the connector is verified, update the connector record in the database
364 if connector_response["connectionSuccessful"]:
365 connector.connector_verified = True
366 connector.connector_last_updated = datetime.now()
367 session.add(connector)
368 await session.commit()
369 else:
370 # If the connector is not verified, set the connector_verified field to False
371 connector.connector_verified = False
372 connector.connector_last_updated = datetime.now()
373 session.add(connector)
374 await session.commit()
375
376 else:
377 logger.error(
378 f"Connector type {connector_response.connector_name} is not supported",
379 )
380 return None
381
382 return connector_response
383 except Exception as e:
384 logger.exception(f"Failed to create ConnectorResponse object: {e}")
385 return None
386
387 @classmethod
388 async def update_connector_by_id(
389 cls,
390 connector_id: int,
391 connector: ConnectorResponse,
392 session: AsyncSession,
393 ) -> Optional[ConnectorResponse]:
394 """
395 Update a connector by its ID in the database asynchronously.
396
397 Given a connector ID and a Pydantic representation of a connector, this method
398 updates the corresponding connector record in the database, if it exists.
399
400 Args:
401 connector_id (int): The ID of the connector to update.
402 connector (ConnectorResponse): The updated connector in its Pydantic representation.
403 session (AsyncSession): The SQLAlchemy asynchronous session to use.
404
405 Returns:
406 Optional[ConnectorResponse]: The updated connector in its Pydantic representation, or None if not found.
407 """
408 query = select(Connectors).where(Connectors.id == connector_id)
409 connector_record = (await session.execute(query)).scalars().first()
410
411 if not connector_record:
412 logger.info(f"No connector found for ID: {connector_id}")
413 return None
414
415 try:
416 # Update the connector record
417 connector_record.connector_url = connector.connector_url
418 connector_record.connector_username = connector.connector_username
419 connector_record.connector_password = connector.connector_password
420 connector_record.connector_api_key = connector.connector_api_key
421 connector_record.connector_extra_data = connector.connector_extra_data
422 connector_record.connector_last_updated = datetime.now()
423
424 # Commit the changes to the database
425 session.add(connector_record)
426 await session.commit()
427
428 # Convert the SQLModel object to a Pydantic model
429 connector_response = ConnectorResponse.from_orm(connector_record)
430 return connector_response
431 except Exception as e:
432 logger.exception(f"Failed to update connector: {e}")
433 session.rollback()
434 return Exception(f"Failed to update connector: {e}")
435
436 @staticmethod
437 def allowed_file(filename):
438 """
439 Check if a file is allowed based on its extension.
440
441 Args:
442 filename (str): The name of the file.
443
444 Returns:
445 bool: True if the file is allowed, False otherwise.
446 """
447 return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS
448
449 @classmethod
450 async def save_file(
451 cls,
452 file: UploadFile,
453 connector_id: int,
454 session: AsyncSession,
455 ) -> Union[ConnectorResponse, bool]:
456 """
457 Saves the uploaded file to a specified location and updates the connector record in the database.
458
459 Args:
460 file (UploadFile): The file to be saved.
461 session (AsyncSession): The async session for interacting with the database.
462
463 Returns:
464 Union[ConnectorResponse, bool]: Returns a ConnectorResponse object if the file is saved and the connector record is updated successfully.
465 Otherwise, returns False.
466 """
467 if file and cls.allowed_file(file.filename):
468 filename = secure_filename(file.filename)
469 file_path = os.path.join(UPLOAD_FOLDER, filename)
470
471 # Save the file asynchronously
472 async with aiofiles.open(file_path, "wb") as buffer:
473 await buffer.write(
474 await file.read(),
475 ) # Assuming file doesn't need to be read in chunks
476
477 # Update connector using async session and ORM
478 query = select(Connectors).where(Connectors.id == connector_id)
479 connector_record = (await session.execute(query)).scalars().first()
480
481 if connector_record:
482 connector_record.connector_configured = True
483 connector_record.connector_api_key = file_path
484 session.add(connector_record)
485 await session.commit()
486
487 connector_response = ConnectorResponse.from_orm(connector_record)
488 return connector_response
489 else:
490 return False
491 else:
492 return False