diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f659cdff..0ea8fca0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -25,7 +25,7 @@ repos: rev: 23.3.0 hooks: - id: black - language_version: python3.9 + language_version: python3.11 - repo: https://github.com/asottile/setup-cfg-fmt rev: v2.3.0 diff --git a/README.md b/README.md index 74a1e023..984e5887 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # CoPilot + SOCFortress CoPilot diff --git a/backend/README.MD b/backend/README.MD index 1c98751d..28316b7f 100644 --- a/backend/README.MD +++ b/backend/README.MD @@ -79,7 +79,6 @@ classDiagram } ``` - # Connector Classes ```mermaid @@ -132,7 +131,7 @@ classDiagram } ``` -# Routes +# Routes ```mermaid graph TD; @@ -165,4 +164,3 @@ graph TD; O --> V[Return Data] ``` - diff --git a/backend/app/__init__.py b/backend/app/__init__.py index 57adf795..6de587f4 100644 --- a/backend/app/__init__.py +++ b/backend/app/__init__.py @@ -37,15 +37,15 @@ migrate = Migrate(app, db) ma = Marshmallow(app) -from app.routes.connectors import bp as connectors_bp # Import the blueprint from app.routes.agents import bp as agents_bp # Import the blueprint -from app.routes.rules import bp as rules_bp # Import the blueprint -from app.routes.graylog import bp as graylog_bp # Import the blueprint from app.routes.alerts import bp as alerts_bp # Import the blueprint -from app.routes.wazuhindexer import bp as wazuhindexer_bp # Import the blueprint +from app.routes.connectors import bp as connectors_bp # Import the blueprint +from app.routes.dfir_iris import bp as dfir_iris_bp # Import the blueprint +from app.routes.graylog import bp as graylog_bp # Import the blueprint +from app.routes.rules import bp as rules_bp # Import the blueprint from app.routes.shuffle import bp as shuffle_bp # Import the blueprint from app.routes.velociraptor import bp as velociraptor_bp # Import the blueprint -from app.routes.dfir_iris import bp as dfir_iris_bp # Import the blueprint +from app.routes.wazuhindexer import bp as wazuhindexer_bp # Import the blueprint app.register_blueprint(connectors_bp) # Register the connectors blueprint app.register_blueprint(agents_bp) # Register the agents blueprint diff --git a/backend/app/models/connectors.py b/backend/app/models/connectors.py index 410c39a1..a1033822 100644 --- a/backend/app/models/connectors.py +++ b/backend/app/models/connectors.py @@ -1,23 +1,26 @@ import importlib import json import os -import pika +from abc import ABC +from abc import abstractmethod from dataclasses import dataclass + +import grpc +import pika +import pyvelociraptor import requests -from abc import ABC, abstractmethod from elasticsearch7 import Elasticsearch +from flask import current_app from loguru import logger -from sqlalchemy.orm.exc import NoResultFound -import pyvelociraptor from pyvelociraptor import api_pb2 from pyvelociraptor import api_pb2_grpc -from werkzeug.utils import secure_filename -import grpc - from sqlalchemy.exc import SQLAlchemyError -from flask import current_app +from sqlalchemy.orm.exc import NoResultFound +from werkzeug.utils import secure_filename -from app.models.models import Connectors, connectors_schema, ConnectorsAvailable +from app.models.models import Connectors +from app.models.models import ConnectorsAvailable +from app.models.models import connectors_schema def dynamic_import(module_name, class_name): @@ -98,7 +101,7 @@ def verify_connection(self): :return: A dictionary containing the status of the connection attempt and information about the cluster's health. """ logger.info( - f"Verifying the wazuh-indexer connection to {self.attributes['connector_url']}" + f"Verifying the wazuh-indexer connection to {self.attributes['connector_url']}", ) try: es = Elasticsearch( @@ -117,7 +120,7 @@ def verify_connection(self): return {"connectionSuccessful": True} except Exception as e: logger.error( - f"Connection to {self.attributes['connector_url']} failed with error: {e}" + f"Connection to {self.attributes['connector_url']} failed with error: {e}", ) return {"connectionSuccessful": False, "clusterHealth": None} @@ -140,7 +143,7 @@ def verify_connection(self): dict: A dictionary containing 'connectionSuccessful' status and 'roles' if the connection is successful. """ logger.info( - f"Verifying the graylog connection to {self.attributes['connector_url']}" + f"Verifying the graylog connection to {self.attributes['connector_url']}", ) try: graylog_roles = requests.get( @@ -153,17 +156,17 @@ def verify_connection(self): ) if graylog_roles.status_code == 200: logger.info( - f"Connection to {self.attributes['connector_url']} successful" + f"Connection to {self.attributes['connector_url']} successful", ) return {"connectionSuccessful": True} else: logger.error( - f"Connection to {self.attributes['connector_url']} failed with error: {graylog_roles.text}" + f"Connection to {self.attributes['connector_url']} failed with error: {graylog_roles.text}", ) return {"connectionSuccessful": False, "roles": None} except Exception as e: logger.error( - f"Connection to {self.attributes['connector_url']} failed with error: {e}" + f"Connection to {self.attributes['connector_url']} failed with error: {e}", ) return {"connectionSuccessful": False, "roles": None} @@ -186,7 +189,7 @@ def verify_connection(self): dict: A dictionary containing 'connectionSuccessful' status and 'authToken' if the connection is successful. """ logger.info( - f"Verifying the wazuh-manager connection to {self.attributes['connector_url']}" + f"Verifying the wazuh-manager connection to {self.attributes['connector_url']}", ) try: wazuh_auth_token = requests.get( @@ -204,12 +207,12 @@ def verify_connection(self): return {"connectionSuccessful": True, "authToken": wazuh_auth_token} else: logger.error( - f"Connection to {self.attributes['connector_url']} failed with error: {wazuh_auth_token.text}" + f"Connection to {self.attributes['connector_url']} failed with error: {wazuh_auth_token.text}", ) return {"connectionSuccessful": False, "authToken": None} except Exception as e: logger.error( - f"Connection to {self.attributes['connector_url']} failed with error: {e}" + f"Connection to {self.attributes['connector_url']} failed with error: {e}", ) return {"connectionSuccessful": False, "authToken": None} @@ -241,11 +244,11 @@ def verify_connection(self): dict: A dictionary containing 'connectionSuccessful' status and 'apps' if the connection is successful. """ logger.info( - f"Verifying the shuffle connection to {self.attributes['connector_url']}" + f"Verifying the shuffle connection to {self.attributes['connector_url']}", ) try: headers = { - "Authorization": f"Bearer {self.attributes['connector_api_key']}" + "Authorization": f"Bearer {self.attributes['connector_api_key']}", } shuffle_apps = requests.get( f"{self.attributes['connector_url']}/api/v1/apps", @@ -254,17 +257,17 @@ def verify_connection(self): ) if shuffle_apps.status_code == 200: logger.info( - f"Connection to {self.attributes['connector_url']} successful" + f"Connection to {self.attributes['connector_url']} successful", ) return {"connectionSuccessful": True} else: logger.error( - f"Connection to {self.attributes['connector_url']} failed with error: {shuffle_apps.text}" + f"Connection to {self.attributes['connector_url']} failed with error: {shuffle_apps.text}", ) return {"connectionSuccessful": False} except Exception as e: logger.error( - f"Connection to {self.attributes['connector_url']} failed with error: {e}" + f"Connection to {self.attributes['connector_url']} failed with error: {e}", ) return {"connectionSuccessful": False} @@ -287,11 +290,11 @@ def verify_connection(self): dict: A dictionary containing 'connectionSuccessful' status and 'response' if the connection is successful. """ logger.info( - f"Verifying the dfir-iris connection to {self.attributes['connector_url']}" + f"Verifying the dfir-iris connection to {self.attributes['connector_url']}", ) try: headers = { - "Authorization": f"Bearer {self.attributes['connector_api_key']}" + "Authorization": f"Bearer {self.attributes['connector_api_key']}", } dfir_iris = requests.get( f"{self.attributes['connector_url']}/api/ping", @@ -301,17 +304,17 @@ def verify_connection(self): # See if 200 is returned if dfir_iris.status_code == 200: logger.info( - f"Connection to {self.attributes['connector_url']} successful" + f"Connection to {self.attributes['connector_url']} successful", ) return {"connectionSuccessful": True} else: logger.error( - f"Connection to {self.attributes['connector_url']} failed with error: {dfir_iris.text}" + f"Connection to {self.attributes['connector_url']} failed with error: {dfir_iris.text}", ) return {"connectionSuccessful": False, "response": None} except Exception as e: logger.error( - f"Connection to {self.attributes['connector_url']} failed with error: {e}" + f"Connection to {self.attributes['connector_url']} failed with error: {e}", ) return {"connectionSuccessful": False, "response": None} @@ -351,7 +354,9 @@ def verify_connection(self): options = (("grpc.ssl_target_name_override", "VelociraptorServer"),) with grpc.secure_channel( - config["api_connection_string"], creds, options + config["api_connection_string"], + creds, + options, ) as channel: stub = api_pb2_grpc.APIStub(channel) client_query = "SELECT * FROM info()" @@ -395,7 +400,7 @@ def verify_connection(self): Verifies the connection to RabbitMQ service. """ logger.info( - f"Verifying the rabbitmq connection to {self.attributes['connector_url']}" + f"Verifying the rabbitmq connection to {self.attributes['connector_url']}", ) try: # For the connector_url, strip out the host and port and use that for the connection @@ -415,7 +420,7 @@ def verify_connection(self): connection = pika.BlockingConnection(parameters) if connection.is_open: logger.info( - f"Connection to {self.attributes['connector_url']} successful" + f"Connection to {self.attributes['connector_url']} successful", ) return {"connectionSuccessful": True} else: @@ -423,7 +428,7 @@ def verify_connection(self): return {"connectionSuccessful": False, "response": None} except Exception as e: logger.error( - f"Connection to {self.attributes['connector_url']} failed with error: {e}" + f"Connection to {self.attributes['connector_url']} failed with error: {e}", ) return {"connectionSuccessful": False, "response": None} diff --git a/backend/app/routes/agents.py b/backend/app/routes/agents.py index e19e971c..ef607b9b 100644 --- a/backend/app/routes/agents.py +++ b/backend/app/routes/agents.py @@ -1,15 +1,16 @@ -from flask import Blueprint, jsonify, request +from flask import Blueprint +from flask import jsonify +from flask import request from loguru import logger -from app.models.connectors import Connector, WazuhManagerConnector -from app.services.agents.agents import AgentService, AgentSyncService - -from app.services.WazuhManager.universal import UniversalService +from app.models.connectors import Connector +from app.models.connectors import WazuhManagerConnector +from app.services.agents.agents import AgentService +from app.services.agents.agents import AgentSyncService from app.services.WazuhManager.agent import WazuhManagerAgentService +from app.services.WazuhManager.universal import UniversalService from app.services.WazuhManager.vulnerability import VulnerabilityService - - bp = Blueprint("agents", __name__) diff --git a/backend/app/routes/alerts.py b/backend/app/routes/alerts.py index 4c87dd88..40babfb9 100644 --- a/backend/app/routes/alerts.py +++ b/backend/app/routes/alerts.py @@ -1,8 +1,12 @@ -from flask import Blueprint, jsonify, request +from flask import Blueprint +from flask import jsonify +from flask import request from loguru import logger -from app.models.connectors import Connector, WazuhManagerConnector -from app.services.agents.agents import AgentService, AgentSyncService +from app.models.connectors import Connector +from app.models.connectors import WazuhManagerConnector +from app.services.agents.agents import AgentService +from app.services.agents.agents import AgentSyncService from app.services.WazuhIndexer.alerts import AlertsService bp = Blueprint("alerts", __name__) diff --git a/backend/app/routes/connectors.py b/backend/app/routes/connectors.py index 80f1adf3..8accfc5c 100644 --- a/backend/app/routes/connectors.py +++ b/backend/app/routes/connectors.py @@ -1,13 +1,13 @@ -from flask import Blueprint, jsonify, request +from flask import Blueprint +from flask import jsonify +from flask import request from loguru import logger -from app.models.models import ( - ConnectorsAvailable, - Connectors, - connectors_available_schema, -) -from app.services.connectors.connectors import ConnectorService from app import db +from app.models.models import Connectors +from app.models.models import ConnectorsAvailable +from app.models.models import connectors_available_schema +from app.services.connectors.connectors import ConnectorService bp = Blueprint("connectors", __name__) @@ -48,7 +48,7 @@ def get_connector_details(id): # Call service function instead of direct function call service = ConnectorService(db) connector_validated = service.validate_connector_exists( - int(id) + int(id), ) # convert id to integer logger.info(connector_validated) if connector_validated["success"] == False: @@ -70,14 +70,16 @@ def update_connector_route(id): id (str): The id of the connector to be updated. Returns: - json: A JSON response containing the success status of the update operation and a message indicating the status. If the update operation was successful, it returns the connector name and the status of the connection verification. + json: A JSON response containing the success status of the update operation and + a message indicating the status. If the update operation was successful, + it returns the connector name and the status of the connection verification. """ api_key_connector = ["Shuffle", "DFIR-IRIS", "Velociraptor"] request_data = request.get_json() service = ConnectorService(db) connector_validated = service.validate_connector_exists( - int(id) + int(id), ) # convert id to integer logger.info(connector_validated) if connector_validated["success"] == False: diff --git a/backend/app/routes/dfir_iris.py b/backend/app/routes/dfir_iris.py index af77f954..e50eaaa4 100644 --- a/backend/app/routes/dfir_iris.py +++ b/backend/app/routes/dfir_iris.py @@ -1,15 +1,18 @@ -from flask import Blueprint, jsonify, request +from flask import Blueprint +from flask import jsonify +from flask import request from loguru import logger -from app.models.connectors import Connector, WazuhManagerConnector -from app.services.Graylog.messages import MessagesService -from app.services.Graylog.metrics import MetricsService -from app.services.Graylog.index import IndexService -from app.services.Graylog.inputs import InputsService +from app.models.connectors import Connector +from app.models.connectors import WazuhManagerConnector +from app.services.DFIR_IRIS.alerts import AlertsService +from app.services.DFIR_IRIS.assets import AssetsService from app.services.DFIR_IRIS.cases import CasesService from app.services.DFIR_IRIS.notes import NotesService -from app.services.DFIR_IRIS.assets import AssetsService -from app.services.DFIR_IRIS.alerts import AlertsService +from app.services.Graylog.index import IndexService +from app.services.Graylog.inputs import InputsService +from app.services.Graylog.messages import MessagesService +from app.services.Graylog.metrics import MetricsService bp = Blueprint("dfir_iris", __name__) @@ -26,6 +29,7 @@ def get_cases(): cases = service.list_cases() return cases + @bp.route("/dfir_iris/cases/", methods=["GET"]) def get_case(case_id): """ @@ -42,6 +46,7 @@ def get_case(case_id): case = service.get_case(case_id=case_id) return case + @bp.route("/dfir_iris/cases//notes", methods=["GET"]) def get_case_notes(case_id): """ @@ -60,6 +65,7 @@ def get_case_notes(case_id): notes = notes_service.get_case_notes(search_term=search_term, cid=int(case_id)) return notes + @bp.route("/dfir_iris/cases//note", methods=["POST"]) def create_case_note(case_id): """ @@ -76,9 +82,14 @@ def create_case_note(case_id): case_id_exists = case_service.check_case_id(case_id=case_id) if case_id_exists["success"] == False: return case_id_exists - created_note = notes_service.create_case_note(cid=int(case_id), note_title=note_title, note_content=note_content) + created_note = notes_service.create_case_note( + cid=int(case_id), + note_title=note_title, + note_content=note_content, + ) return created_note + @bp.route("/dfir_iris/cases//assets", methods=["GET"]) def get_case_assets(case_id): """ @@ -96,6 +107,7 @@ def get_case_assets(case_id): assets = asset_service.get_case_assets(cid=int(case_id)) return assets + @bp.route("/dfir_iris/alerts", methods=["GET"]) def get_alerts(): """ diff --git a/backend/app/routes/graylog.py b/backend/app/routes/graylog.py index b61463c0..738debf1 100644 --- a/backend/app/routes/graylog.py +++ b/backend/app/routes/graylog.py @@ -1,11 +1,14 @@ -from flask import Blueprint, jsonify, request +from flask import Blueprint +from flask import jsonify +from flask import request from loguru import logger -from app.models.connectors import Connector, WazuhManagerConnector -from app.services.Graylog.messages import MessagesService -from app.services.Graylog.metrics import MetricsService +from app.models.connectors import Connector +from app.models.connectors import WazuhManagerConnector from app.services.Graylog.index import IndexService from app.services.Graylog.inputs import InputsService +from app.services.Graylog.messages import MessagesService +from app.services.Graylog.metrics import MetricsService from app.services.WazuhManager.wazuhmanager import WazuhManagerService bp = Blueprint("graylog", __name__) @@ -36,7 +39,7 @@ def get_metrics(): uncommitted_journal_size = service.collect_uncommitted_journal_size() metrics = service.collect_throughput_metrics() return jsonify( - {"uncommitted_journal_size": uncommitted_journal_size, "metrics": metrics} + {"uncommitted_journal_size": uncommitted_journal_size, "metrics": metrics}, ) @@ -81,5 +84,5 @@ def get_inputs(): running_inputs = service.collect_running_inputs() configured_inputs = service.collect_configured_inputs() return jsonify( - {"running_inputs": running_inputs, "configured_inputs": configured_inputs} + {"running_inputs": running_inputs, "configured_inputs": configured_inputs}, ) diff --git a/backend/app/routes/index.py b/backend/app/routes/index.py index 07bf611b..c9d93817 100644 --- a/backend/app/routes/index.py +++ b/backend/app/routes/index.py @@ -1,8 +1,12 @@ -from flask import Blueprint, jsonify, request +from flask import Blueprint +from flask import jsonify +from flask import request from loguru import logger -from app.models.connectors import Connector, WazuhManagerConnector -from app.services.agents.agents import AgentService, AgentSyncService +from app.models.connectors import Connector +from app.models.connectors import WazuhManagerConnector +from app.services.agents.agents import AgentService +from app.services.agents.agents import AgentSyncService from app.services.WazuhIndexer.alerts import AlertsService from app.services.WazuhIndexer.cluster import ClusterService diff --git a/backend/app/routes/rules.py b/backend/app/routes/rules.py index 58a2690d..52c71091 100644 --- a/backend/app/routes/rules.py +++ b/backend/app/routes/rules.py @@ -1,16 +1,15 @@ -from flask import Blueprint, jsonify, request +from flask import Blueprint +from flask import jsonify +from flask import request from loguru import logger -from app.models.connectors import Connector, WazuhManagerConnector +from app.models.connectors import Connector +from app.models.connectors import WazuhManagerConnector from app.models.rules import DisabledRules - -from app.services.WazuhManager.wazuhmanager import WazuhManagerService - -from app.services.WazuhManager.universal import UniversalService - from app.services.WazuhManager.disabled_rule import DisableRuleService - from app.services.WazuhManager.enabled_rule import EnableRuleService +from app.services.WazuhManager.universal import UniversalService +from app.services.WazuhManager.wazuhmanager import WazuhManagerService bp = Blueprint("rules", __name__) diff --git a/backend/app/routes/shuffle.py b/backend/app/routes/shuffle.py index 009d11ee..50969a0e 100644 --- a/backend/app/routes/shuffle.py +++ b/backend/app/routes/shuffle.py @@ -1,8 +1,12 @@ -from flask import Blueprint, jsonify, request +from flask import Blueprint +from flask import jsonify +from flask import request from loguru import logger -from app.models.connectors import Connector, WazuhManagerConnector -from app.services.agents.agents import AgentService, AgentSyncService +from app.models.connectors import Connector +from app.models.connectors import WazuhManagerConnector +from app.services.agents.agents import AgentService +from app.services.agents.agents import AgentSyncService from app.services.Shuffle.workflows import WorkflowsService bp = Blueprint("shuffle", __name__) @@ -20,6 +24,7 @@ def get_workflows(): workflows = service.collect_workflows() return workflows + @bp.route("/shuffle/workflows/executions", methods=["GET"]) def get_workflows_executions(): """ @@ -34,9 +39,12 @@ def get_workflows_executions(): message = "No workflows found" return jsonify({"message": message, "success": False}), 500 for workflow in workflow_details["workflows"]: - workflow["status"] = service.collect_workflow_executions_status(workflow["workflow_id"]) + workflow["status"] = service.collect_workflow_executions_status( + workflow["workflow_id"], + ) return workflow_details + @bp.route("/shuffle/workflows/executions/", methods=["GET"]) def get_workflow_executions(workflow_id): """ diff --git a/backend/app/routes/velociraptor.py b/backend/app/routes/velociraptor.py index 0d5c5d15..e417e966 100644 --- a/backend/app/routes/velociraptor.py +++ b/backend/app/routes/velociraptor.py @@ -1,13 +1,18 @@ -from flask import Blueprint, jsonify, request +from flask import Blueprint +from flask import jsonify +from flask import request from loguru import logger -from app.models.connectors import Connector, WazuhManagerConnector -from app.services.agents.agents import AgentService, AgentSyncService +from app.models.connectors import Connector +from app.models.connectors import WazuhManagerConnector +from app.services.agents.agents import AgentService +from app.services.agents.agents import AgentSyncService from app.services.Velociraptor.artifacts import ArtifactsService from app.services.Velociraptor.universal import UniversalService bp = Blueprint("velociraptor", __name__) + @bp.route("/velociraptor/artifacts", methods=["GET"]) def get_artifacts(): """ @@ -21,11 +26,12 @@ def get_artifacts(): artifacts = service.collect_artifacts() return artifacts + @bp.route("/velociraptor/artifacts/linux", methods=["GET"]) def get_artifacts_linux(): """ Endpoint to list all available artifacts. - It processes each artifact to verify the connection and returns the results where the name + It processes each artifact to verify the connection and returns the results where the name begins with `Linux`. Returns: @@ -35,11 +41,12 @@ def get_artifacts_linux(): linux_artifacts = service.collect_artifacts_linux() return linux_artifacts + @bp.route("/velociraptor/artifacts/windows", methods=["GET"]) def get_artifacts_windows(): """ Endpoint to list all available artifacts. - It processes each artifact to verify the connection and returns the results where the name + It processes each artifact to verify the connection and returns the results where the name begins with `Windows`. Returns: @@ -49,11 +56,12 @@ def get_artifacts_windows(): windows_artifacts = service.collect_artifacts_windows() return windows_artifacts + @bp.route("/velociraptor/artifacts/mac", methods=["GET"]) def get_artifacts_mac(): """ Endpoint to list all available artifacts. - It processes each artifact to verify the connection and returns the results where the name + It processes each artifact to verify the connection and returns the results where the name begins with `MacOS`. Returns: @@ -63,6 +71,7 @@ def get_artifacts_mac(): mac_artifacts = service.collect_artifacts_macos() return mac_artifacts + @bp.route("/velociraptor/artifacts/collection", methods=["POST"]) def collect_artifact(): """ @@ -76,10 +85,23 @@ def collect_artifact(): artifact_name = req_data["artifact_name"] client_name = req_data["client_name"] service = UniversalService() - client_id = service.get_client_id(client_name=client_name)["results"][0]["client_id"] + client_id = service.get_client_id(client_name=client_name)["results"][0][ + "client_id" + ] if client_id is None: - return jsonify({"message": f"{client_name} has not been seen in the last 30 seconds and may not be online with the Velociraptor server.", "success": False}), 500 + return ( + jsonify( + { + "message": f"{client_name} has not been seen in the last 30 seconds and may not be online with the Velociraptor server.", + "success": False, + }, + ), + 500, + ) artifact_service = ArtifactsService() - artifact_results = artifact_service.run_artifact_collection(client_id=client_id, artifact=artifact_name) + artifact_results = artifact_service.run_artifact_collection( + client_id=client_id, + artifact=artifact_name, + ) return artifact_results diff --git a/backend/app/routes/wazuhindexer.py b/backend/app/routes/wazuhindexer.py index 9f926471..7338e371 100644 --- a/backend/app/routes/wazuhindexer.py +++ b/backend/app/routes/wazuhindexer.py @@ -1,11 +1,15 @@ -from flask import Blueprint, jsonify, request +from flask import Blueprint +from flask import jsonify +from flask import request from loguru import logger -from app.models.connectors import Connector, WazuhManagerConnector -from app.services.agents.agents import AgentService, AgentSyncService +from app.models.connectors import Connector +from app.models.connectors import WazuhManagerConnector +from app.services.agents.agents import AgentService +from app.services.agents.agents import AgentSyncService from app.services.WazuhIndexer.alerts import AlertsService -from app.services.WazuhIndexer.index import IndexService from app.services.WazuhIndexer.cluster import ClusterService +from app.services.WazuhIndexer.index import IndexService bp = Blueprint("wazuh_indexer", __name__) @@ -30,6 +34,7 @@ def get_indices_summary(): indices = service.collect_indices_summary() return indices + @bp.route("/wazuh_indexer/allocation", methods=["GET"]) def get_node_allocation(): """ @@ -50,6 +55,7 @@ def get_node_allocation(): indices = service.collect_node_allocation() return indices + @bp.route("/wazuh_indexer/health", methods=["GET"]) def get_cluster_health(): """ @@ -62,6 +68,7 @@ def get_cluster_health(): indices = service.collect_cluster_health() return indices + @bp.route("/wazuh_indexer/shards", methods=["GET"]) def get_shards(): """ diff --git a/backend/app/services/DFIR_IRIS/alerts.py b/backend/app/services/DFIR_IRIS/alerts.py index f4b41934..ef87ce3b 100644 --- a/backend/app/services/DFIR_IRIS/alerts.py +++ b/backend/app/services/DFIR_IRIS/alerts.py @@ -1,10 +1,12 @@ from typing import Dict + import requests -from loguru import logger -from app.services.DFIR_IRIS.universal import UniversalService +from dfir_iris_client.alert import Alert from dfir_iris_client.helper.utils import assert_api_resp from dfir_iris_client.helper.utils import get_data_from_resp -from dfir_iris_client.alert import Alert +from loguru import logger + +from app.services.DFIR_IRIS.universal import UniversalService class AlertsService: @@ -15,12 +17,12 @@ class AlertsService: def __init__(self): self.universal_service = UniversalService("DFIR-IRIS") session_result = self.universal_service.create_session() - - if not session_result['success']: - logger.error(session_result['message']) + + if not session_result["success"]: + logger.error(session_result["message"]) self.iris_session = None else: - self.iris_session = session_result['session'] + self.iris_session = session_result["session"] def list_alerts(self) -> Dict[str, object]: """ @@ -37,9 +39,19 @@ def list_alerts(self) -> Dict[str, object]: logger.info("Collecting cases from DFIR-IRIS") alert = Alert(session=self.iris_session) - result = self.universal_service.fetch_and_parse_data(self.iris_session, alert.filter_alerts) + result = self.universal_service.fetch_and_parse_data( + self.iris_session, + alert.filter_alerts, + ) if not result["success"]: - return {"success": False, "message": "Failed to collect cases from DFIR-IRIS"} + return { + "success": False, + "message": "Failed to collect cases from DFIR-IRIS", + } - return {"success": True, "message": "Successfully collected cases from DFIR-IRIS", "results": result["data"]} + return { + "success": True, + "message": "Successfully collected cases from DFIR-IRIS", + "results": result["data"], + } diff --git a/backend/app/services/DFIR_IRIS/assets.py b/backend/app/services/DFIR_IRIS/assets.py index a1a9a217..7341d6e1 100644 --- a/backend/app/services/DFIR_IRIS/assets.py +++ b/backend/app/services/DFIR_IRIS/assets.py @@ -1,11 +1,13 @@ from typing import Dict + import requests -from loguru import logger -from app.services.DFIR_IRIS.universal import UniversalService from dfir_iris_client.case import Case from dfir_iris_client.helper.utils import assert_api_resp from dfir_iris_client.helper.utils import get_data_from_resp from dfir_iris_client.session import ClientSession +from loguru import logger + +from app.services.DFIR_IRIS.universal import UniversalService class AssetsService: @@ -16,12 +18,12 @@ class AssetsService: def __init__(self): self.universal_service = UniversalService("DFIR-IRIS") session_result = self.universal_service.create_session() - - if not session_result['success']: - logger.error(session_result['message']) + + if not session_result["success"]: + logger.error(session_result["message"]) self.iris_session = None else: - self.iris_session = session_result['session'] + self.iris_session = session_result["session"] def get_case_assets(self, cid: int) -> Dict[str, object]: """ @@ -34,14 +36,23 @@ def get_case_assets(self, cid: int) -> Dict[str, object]: dict: A dictionary containing the success status, a message and potentially the notes of a given case. """ if self.iris_session is None: - return {"success": False, "message": "DFIR-IRIS session was not successfully created."} + return { + "success": False, + "message": "DFIR-IRIS session was not successfully created.", + } logger.info(f"Collecting case {cid} assets from DFIR-IRIS") case = Case(session=self.iris_session) - result = self.universal_service.fetch_and_parse_data(self.iris_session, case.list_assets, cid) + result = self.universal_service.fetch_and_parse_data( + self.iris_session, + case.list_assets, + cid, + ) if not result["success"]: - return {"success": False, "message": "Failed to collect notes from DFIR-IRIS"} - + return { + "success": False, + "message": "Failed to collect notes from DFIR-IRIS", + } + return result - diff --git a/backend/app/services/DFIR_IRIS/cases.py b/backend/app/services/DFIR_IRIS/cases.py index dcd9656c..fd39cc13 100644 --- a/backend/app/services/DFIR_IRIS/cases.py +++ b/backend/app/services/DFIR_IRIS/cases.py @@ -1,11 +1,13 @@ from typing import Dict + import requests -from loguru import logger -from app.services.DFIR_IRIS.universal import UniversalService from dfir_iris_client.case import Case from dfir_iris_client.helper.utils import assert_api_resp from dfir_iris_client.helper.utils import get_data_from_resp from dfir_iris_client.session import ClientSession +from loguru import logger + +from app.services.DFIR_IRIS.universal import UniversalService class CasesService: @@ -16,12 +18,12 @@ class CasesService: def __init__(self): self.universal_service = UniversalService("DFIR-IRIS") session_result = self.universal_service.create_session() - - if not session_result['success']: - logger.error(session_result['message']) + + if not session_result["success"]: + logger.error(session_result["message"]) self.iris_session = None else: - self.iris_session = session_result['session'] + self.iris_session = session_result["session"] def list_cases(self) -> Dict[str, object]: """ @@ -38,12 +40,22 @@ def list_cases(self) -> Dict[str, object]: logger.info("Collecting cases from DFIR-IRIS") case = Case(session=self.iris_session) - result = self.universal_service.fetch_and_parse_data(self.iris_session, case.list_cases) + result = self.universal_service.fetch_and_parse_data( + self.iris_session, + case.list_cases, + ) if not result["success"]: - return {"success": False, "message": "Failed to collect cases from DFIR-IRIS"} + return { + "success": False, + "message": "Failed to collect cases from DFIR-IRIS", + } - return {"success": True, "message": "Successfully collected cases from DFIR-IRIS", "cases": result["data"]} + return { + "success": True, + "message": "Successfully collected cases from DFIR-IRIS", + "cases": result["data"], + } def get_case(self, case_id: int) -> bool: """ @@ -53,16 +65,30 @@ def get_case(self, case_id: int) -> bool: dict: A dictionary containing the success status, a message and potentially the case. """ if self.iris_session is None: - return {"success": False, "message": "DFIR-IRIS session was not successfully created."} + return { + "success": False, + "message": "DFIR-IRIS session was not successfully created.", + } logger.info(f"Collecting case {case_id} from DFIR-IRIS") case = Case(session=self.iris_session) - result = self.universal_service.fetch_and_parse_data(self.iris_session, case.get_case, case_id) + result = self.universal_service.fetch_and_parse_data( + self.iris_session, + case.get_case, + case_id, + ) if not result["success"]: - return {"success": False, "message": f"Failed to collect case {case_id} from DFIR-IRIS"} + return { + "success": False, + "message": f"Failed to collect case {case_id} from DFIR-IRIS", + } - return {"success": True, "message": f"Successfully collected case {case_id} from DFIR-IRIS", "case": result["data"]} + return { + "success": True, + "message": f"Successfully collected case {case_id} from DFIR-IRIS", + "case": result["data"], + } def check_case_id(self, case_id: int) -> bool: """ @@ -72,9 +98,3 @@ def check_case_id(self, case_id: int) -> bool: dict: A dictionary containing the success status, a message and potentially the case. """ return self.get_case(case_id) - - - - - - diff --git a/backend/app/services/DFIR_IRIS/notes.py b/backend/app/services/DFIR_IRIS/notes.py index 7018eba4..11ebe1ac 100644 --- a/backend/app/services/DFIR_IRIS/notes.py +++ b/backend/app/services/DFIR_IRIS/notes.py @@ -1,11 +1,13 @@ from typing import Dict + import requests -from loguru import logger -from app.services.DFIR_IRIS.universal import UniversalService from dfir_iris_client.case import Case from dfir_iris_client.helper.utils import assert_api_resp from dfir_iris_client.helper.utils import get_data_from_resp from dfir_iris_client.session import ClientSession +from loguru import logger + +from app.services.DFIR_IRIS.universal import UniversalService class NotesService: @@ -16,12 +18,12 @@ class NotesService: def __init__(self): self.universal_service = UniversalService("DFIR-IRIS") session_result = self.universal_service.create_session() - - if not session_result['success']: - logger.error(session_result['message']) + + if not session_result["success"]: + logger.error(session_result["message"]) self.iris_session = None else: - self.iris_session = session_result['session'] + self.iris_session = session_result["session"] def get_case_notes(self, search_term: str, cid: int) -> Dict[str, object]: """ @@ -35,24 +37,38 @@ def get_case_notes(self, search_term: str, cid: int) -> Dict[str, object]: dict: A dictionary containing the success status, a message and potentially the notes of a given case. """ if self.iris_session is None: - return {"success": False, "message": "DFIR-IRIS session was not successfully created."} + return { + "success": False, + "message": "DFIR-IRIS session was not successfully created.", + } logger.info(f"Collecting case {cid} from DFIR-IRIS") case = Case(session=self.iris_session) - result = self.universal_service.fetch_and_parse_data(self.iris_session, case.search_notes, search_term, cid) + result = self.universal_service.fetch_and_parse_data( + self.iris_session, + case.search_notes, + search_term, + cid, + ) if not result["success"]: - return {"success": False, "message": "Failed to collect notes from DFIR-IRIS"} - + return { + "success": False, + "message": "Failed to collect notes from DFIR-IRIS", + } + # Loop through the notes and get the details - for note in result['data']: - note_details = self._get_case_note_details(note['note_id'], cid) - if not note_details['success']: - return {"success": False, "message": "Failed to collect notes from DFIR-IRIS"} - note['note_details'] = note_details['notes'] + for note in result["data"]: + note_details = self._get_case_note_details(note["note_id"], cid) + if not note_details["success"]: + return { + "success": False, + "message": "Failed to collect notes from DFIR-IRIS", + } + note["note_details"] = note_details["notes"] return result - + def _get_case_note_details(self, note_id: int, cid: int) -> Dict[str, object]: """ Gets a case's notes from DFIR-IRIS and returns the note details such as the content @@ -65,18 +81,38 @@ def _get_case_note_details(self, note_id: int, cid: int) -> Dict[str, object]: dict: A dictionary containing the success status, a message and potentially the notes of a given case. """ if self.iris_session is None: - return {"success": False, "message": "DFIR-IRIS session was not successfully created."} + return { + "success": False, + "message": "DFIR-IRIS session was not successfully created.", + } logger.info(f"Collecting case {cid} from DFIR-IRIS") case = Case(session=self.iris_session) - result = self.universal_service.fetch_and_parse_data(self.iris_session, case.get_note, note_id, cid) + result = self.universal_service.fetch_and_parse_data( + self.iris_session, + case.get_note, + note_id, + cid, + ) if not result["success"]: - return {"success": False, "message": "Failed to collect notes from DFIR-IRIS"} - - return {"success": True, "message": "Successfully collected notes from DFIR-IRIS", "notes": result["data"]} - - def create_case_note(self, cid: int, note_title: str, note_content: str) -> Dict[str, object]: + return { + "success": False, + "message": "Failed to collect notes from DFIR-IRIS", + } + + return { + "success": True, + "message": "Successfully collected notes from DFIR-IRIS", + "notes": result["data"], + } + + def create_case_note( + self, + cid: int, + note_title: str, + note_content: str, + ) -> Dict[str, object]: """ Creates a case note in DFIR-IRIS @@ -89,20 +125,40 @@ def create_case_note(self, cid: int, note_title: str, note_content: str) -> Dict dict: A dictionary containing the success status, a message and potentially the notes of a given case. """ if self.iris_session is None: - return {"success": False, "message": "DFIR-IRIS session was not successfully created."} + return { + "success": False, + "message": "DFIR-IRIS session was not successfully created.", + } logger.info(f"Creating case {cid} note in DFIR-IRIS") case = Case(session=self.iris_session) # Creating Group for New Note - note_group = self.universal_service.fetch_and_parse_data(self.iris_session, case.add_notes_group, note_title, cid) - + note_group = self.universal_service.fetch_and_parse_data( + self.iris_session, + case.add_notes_group, + note_title, + cid, + ) + if not note_group["success"]: return {"success": False, "message": "Failed to create note in DFIR-IRIS"} - note_group_id = note_group['data']['group_id'] + note_group_id = note_group["data"]["group_id"] custom_attributes = {} - result = self.universal_service.fetch_and_parse_data(self.iris_session, case.add_note, note_title, note_content, note_group_id, custom_attributes, cid) + result = self.universal_service.fetch_and_parse_data( + self.iris_session, + case.add_note, + note_title, + note_content, + note_group_id, + custom_attributes, + cid, + ) if not result["success"]: return {"success": False, "message": "Failed to create note in DFIR-IRIS"} - return {"success": True, "message": "Successfully created note in DFIR-IRIS", "notes": result["data"]} + return { + "success": True, + "message": "Successfully created note in DFIR-IRIS", + "notes": result["data"], + } diff --git a/backend/app/services/DFIR_IRIS/universal.py b/backend/app/services/DFIR_IRIS/universal.py index 8e91ef2a..b4892001 100644 --- a/backend/app/services/DFIR_IRIS/universal.py +++ b/backend/app/services/DFIR_IRIS/universal.py @@ -1,25 +1,26 @@ -from app.models.agents import ( - AgentMetadata, - agent_metadata_schema, - agent_metadatas_schema, -) -from typing import Dict, List -from app import db from datetime import datetime -import requests -from loguru import logger -from elasticsearch7 import Elasticsearch -from app.models.connectors import connector_factory, Connector -import dfir_iris_client -from dfir_iris_client.session import ClientSession from typing import Any from typing import Dict +from typing import List from typing import Optional from typing import Set from typing import Tuple + +import dfir_iris_client +import requests from dfir_iris_client.case import Case from dfir_iris_client.helper.utils import assert_api_resp from dfir_iris_client.helper.utils import get_data_from_resp +from dfir_iris_client.session import ClientSession +from elasticsearch7 import Elasticsearch +from loguru import logger + +from app import db +from app.models.agents import AgentMetadata +from app.models.agents import agent_metadata_schema +from app.models.agents import agent_metadatas_schema +from app.models.connectors import Connector +from app.models.connectors import connector_factory class UniversalService: @@ -28,7 +29,9 @@ class UniversalService: """ def __init__(self, connector_name: str) -> None: - self.connector_url, self.connector_api_key = self.collect_iris_details(connector_name) + self.connector_url, self.connector_api_key = self.collect_iris_details( + connector_name, + ) def collect_iris_details(self, connector_name: str): """ @@ -50,7 +53,7 @@ def collect_iris_details(self, connector_name: str): ) else: return None, None - + def create_session(self) -> Optional[ClientSession]: """ Create a session with DFIR-IRIS. @@ -79,7 +82,7 @@ def create_session(self) -> Optional[ClientSession]: "success": False, "message": "Connection to DFIR-IRIS unsuccessful.", } - + def fetch_and_parse_data(self, session, action, *args): """ General method to fetch and parse data from DFIR-IRIS. diff --git a/backend/app/services/Graylog/index.py b/backend/app/services/Graylog/index.py index 8b990779..de2e1ebe 100644 --- a/backend/app/services/Graylog/index.py +++ b/backend/app/services/Graylog/index.py @@ -1,14 +1,17 @@ -from app.models.agents import ( - AgentMetadata, - agent_metadata_schema, - agent_metadatas_schema, -) -from typing import Dict, List -from app import db from datetime import datetime +from typing import Dict +from typing import List + import requests from loguru import logger -from app.models.connectors import connector_factory, Connector, GraylogConnector + +from app import db +from app.models.agents import AgentMetadata +from app.models.agents import agent_metadata_schema +from app.models.agents import agent_metadatas_schema +from app.models.connectors import Connector +from app.models.connectors import GraylogConnector +from app.models.connectors import connector_factory from app.services.Graylog.universal import UniversalService diff --git a/backend/app/services/Graylog/inputs.py b/backend/app/services/Graylog/inputs.py index 24fd4283..ec5557f3 100644 --- a/backend/app/services/Graylog/inputs.py +++ b/backend/app/services/Graylog/inputs.py @@ -1,14 +1,17 @@ -from app.models.agents import ( - AgentMetadata, - agent_metadata_schema, - agent_metadatas_schema, -) -from typing import Dict, List -from app import db from datetime import datetime +from typing import Dict +from typing import List + import requests from loguru import logger -from app.models.connectors import connector_factory, Connector, GraylogConnector + +from app import db +from app.models.agents import AgentMetadata +from app.models.agents import agent_metadata_schema +from app.models.agents import agent_metadatas_schema +from app.models.connectors import Connector +from app.models.connectors import GraylogConnector +from app.models.connectors import connector_factory from app.services.Graylog.universal import UniversalService diff --git a/backend/app/services/Graylog/messages.py b/backend/app/services/Graylog/messages.py index 2e135a28..2aa61919 100644 --- a/backend/app/services/Graylog/messages.py +++ b/backend/app/services/Graylog/messages.py @@ -1,13 +1,15 @@ -from app.models.agents import ( - AgentMetadata, - agent_metadata_schema, - agent_metadatas_schema, -) -from app import db from datetime import datetime + import requests from loguru import logger -from app.models.connectors import connector_factory, Connector, GraylogConnector + +from app import db +from app.models.agents import AgentMetadata +from app.models.agents import agent_metadata_schema +from app.models.agents import agent_metadatas_schema +from app.models.connectors import Connector +from app.models.connectors import GraylogConnector +from app.models.connectors import connector_factory from app.services.Graylog.universal import UniversalService @@ -46,7 +48,7 @@ def collect_messages(self): # If the response is successful, return the messages as a list if graylog_messages.status_code == 200: logger.info( - f"Received {len(graylog_messages.json()['messages'])} messages from Graylog" + f"Received {len(graylog_messages.json()['messages'])} messages from Graylog", ) return { "message": "Successfully retrieved messages", @@ -56,7 +58,7 @@ def collect_messages(self): # Otherwise, return an error message else: logger.error( - f"Failed to collect messages from Graylog: {graylog_messages.json()}" + f"Failed to collect messages from Graylog: {graylog_messages.json()}", ) return { "message": "Failed to collect messages from Graylog", diff --git a/backend/app/services/Graylog/metrics.py b/backend/app/services/Graylog/metrics.py index 6cf479f8..d6cce10e 100644 --- a/backend/app/services/Graylog/metrics.py +++ b/backend/app/services/Graylog/metrics.py @@ -1,14 +1,17 @@ -from app.models.agents import ( - AgentMetadata, - agent_metadata_schema, - agent_metadatas_schema, -) -from typing import Dict, List -from app import db from datetime import datetime +from typing import Dict +from typing import List + import requests from loguru import logger -from app.models.connectors import connector_factory, Connector, GraylogConnector + +from app import db +from app.models.agents import AgentMetadata +from app.models.agents import agent_metadata_schema +from app.models.agents import agent_metadatas_schema +from app.models.connectors import Connector +from app.models.connectors import GraylogConnector +from app.models.connectors import connector_factory from app.services.Graylog.universal import UniversalService @@ -49,7 +52,9 @@ def collect_uncommitted_journal_size(self): return {"message": "Failed to collect Graylog details", "success": False} else: journal_size = self._collect_metrics_uncommitted_journal_size( - connector_url, connector_username, connector_password + connector_url, + connector_username, + connector_password, ) if journal_size["success"] is False: @@ -83,7 +88,9 @@ def collect_throughput_metrics(self): return {"message": "Failed to collect Graylog details", "success": False} else: throughput_usage = self._collect_metrics_throughput_usage( - connector_url, connector_username, connector_password + connector_url, + connector_username, + connector_password, ) if throughput_usage["success"] is False: @@ -91,7 +98,10 @@ def collect_throughput_metrics(self): return throughput_usage def _collect_metrics_uncommitted_journal_size( - self, connector_url: str, connector_username: str, connector_password: str + self, + connector_url: str, + connector_username: str, + connector_password: str, ): """ Collects the journal size of uncommitted messages from Graylog. @@ -117,13 +127,14 @@ def _collect_metrics_uncommitted_journal_size( uncommitted_journal_size = uncommitted_journal_size_response.json() logger.info( - f"Received {uncommitted_journal_size} uncommitted journal entries from Graylog" + f"Received {uncommitted_journal_size} uncommitted journal entries from Graylog", ) return { "message": "Successfully retrieved journal size", "success": True, "uncommitted_journal_entries": uncommitted_journal_size.get( - "uncommitted_journal_entries", 0 + "uncommitted_journal_entries", + 0, ), } except Exception as e: @@ -134,7 +145,10 @@ def _collect_metrics_uncommitted_journal_size( } def _collect_metrics_throughput_usage( - self, connector_url: str, connector_username: str, connector_password: str + self, + connector_url: str, + connector_username: str, + connector_password: str, ) -> Dict[str, object]: """ Collects throughput usage from Graylog. @@ -151,7 +165,10 @@ def _collect_metrics_throughput_usage( try: throughput_metrics = self._make_throughput_api_call( - connector_url, self.HEADERS, connector_username, connector_password + connector_url, + self.HEADERS, + connector_username, + connector_password, ) return self._parse_throughput_metrics(throughput_metrics) except Exception as e: @@ -194,7 +211,8 @@ def _make_throughput_api_call( return throughput_metrics def _parse_throughput_metrics( - self, throughput_metrics: Dict[str, object] + self, + throughput_metrics: Dict[str, object], ) -> Dict[str, object]: """ Parses throughput metrics. @@ -218,7 +236,7 @@ def _parse_throughput_metrics( results[variable_name] = value logger.info( - f"Received throughput usage from Graylog: {throughput_metrics_list}" + f"Received throughput usage from Graylog: {throughput_metrics_list}", ) return { "message": "Successfully retrieved throughput usage", diff --git a/backend/app/services/Graylog/universal.py b/backend/app/services/Graylog/universal.py index d1857087..64ab7215 100644 --- a/backend/app/services/Graylog/universal.py +++ b/backend/app/services/Graylog/universal.py @@ -1,13 +1,15 @@ -from app.models.agents import ( - AgentMetadata, - agent_metadata_schema, - agent_metadatas_schema, -) -from app import db from datetime import datetime + import requests from loguru import logger -from app.models.connectors import connector_factory, Connector, GraylogConnector + +from app import db +from app.models.agents import AgentMetadata +from app.models.agents import agent_metadata_schema +from app.models.agents import agent_metadatas_schema +from app.models.connectors import Connector +from app.models.connectors import GraylogConnector +from app.models.connectors import connector_factory class UniversalService: diff --git a/backend/app/services/Shuffle/universal.py b/backend/app/services/Shuffle/universal.py index 72461cf2..b4e84035 100644 --- a/backend/app/services/Shuffle/universal.py +++ b/backend/app/services/Shuffle/universal.py @@ -1,15 +1,17 @@ -from app.models.agents import ( - AgentMetadata, - agent_metadata_schema, - agent_metadatas_schema, -) -from typing import Dict, List -from app import db from datetime import datetime +from typing import Dict +from typing import List + import requests -from loguru import logger from elasticsearch7 import Elasticsearch -from app.models.connectors import connector_factory, Connector +from loguru import logger + +from app import db +from app.models.agents import AgentMetadata +from app.models.agents import agent_metadata_schema +from app.models.agents import agent_metadatas_schema +from app.models.connectors import Connector +from app.models.connectors import connector_factory class UniversalService: diff --git a/backend/app/services/Shuffle/workflows.py b/backend/app/services/Shuffle/workflows.py index 862bf051..4d96db52 100644 --- a/backend/app/services/Shuffle/workflows.py +++ b/backend/app/services/Shuffle/workflows.py @@ -1,6 +1,8 @@ from typing import Dict + import requests from loguru import logger + from app.services.Shuffle.universal import UniversalService @@ -12,10 +14,15 @@ class WorkflowsService: def __init__(self): self._collect_shuffle_details() self.session = requests.Session() - self.session.headers.update({"Authorization" : f"Bearer {self.connector_api_key}"}) + self.session.headers.update( + {"Authorization": f"Bearer {self.connector_api_key}"}, + ) def _collect_shuffle_details(self): - self.connector_url, self.connector_api_key = UniversalService().collect_shuffle_details("Shuffle") + ( + self.connector_url, + self.connector_api_key, + ) = UniversalService().collect_shuffle_details("Shuffle") def _are_details_collected(self) -> bool: return all([self.connector_url, self.connector_api_key]) @@ -74,7 +81,7 @@ def _collect_workflows(self) -> Dict[str, object]: "success": True, "workflows": response.json(), } - + def collect_workflow_details(self) -> Dict[str, object]: """ Collects the workflow ID and workflow name from Shuffle. @@ -97,7 +104,7 @@ def collect_workflow_details(self) -> Dict[str, object]: "success": True, "workflows": workflows["workflows"], } - + def _collect_workflow_details(self) -> Dict[str, object]: """ Collects the workflow ID and workflow name from Shuffle. @@ -114,10 +121,9 @@ def _collect_workflow_details(self) -> Dict[str, object]: workflows = response.json() workflow_details = [] for workflow in workflows: - workflow_details.append({ - "workflow_id": workflow["id"], - "workflow_name": workflow["name"] - }) + workflow_details.append( + {"workflow_id": workflow["id"], "workflow_name": workflow["name"]}, + ) return { "message": "Successfully collected workflow details from Shuffle", @@ -148,7 +154,10 @@ def collect_workflow_executions_status(self, workflow_id: str) -> Dict[str, obje "executions": executions["executions"], } - def _collect_workflow_executions_status(self, workflow_id: str) -> Dict[str, object]: + def _collect_workflow_executions_status( + self, + workflow_id: str, + ) -> Dict[str, object]: """ Collects the execution status of a Shuffle Workflow by its ID. @@ -156,7 +165,9 @@ def _collect_workflow_executions_status(self, workflow_id: str) -> Dict[str, obj dict: A dictionary containing the success status, a message and potentially the workflow execution status. """ try: - response = self._send_request(f"{self.connector_url}/api/v1/workflows/{workflow_id}/executions") + response = self._send_request( + f"{self.connector_url}/api/v1/workflows/{workflow_id}/executions", + ) response.raise_for_status() except requests.exceptions.HTTPError as err: return self._handle_request_error(err) @@ -174,5 +185,4 @@ def _collect_workflow_executions_status(self, workflow_id: str) -> Dict[str, obj "message": "Successfully collected workflow executions from Shuffle", "success": True, "executions": status, - } diff --git a/backend/app/services/Velociraptor/artifacts.py b/backend/app/services/Velociraptor/artifacts.py index 275dcff0..08481b1e 100644 --- a/backend/app/services/Velociraptor/artifacts.py +++ b/backend/app/services/Velociraptor/artifacts.py @@ -1,9 +1,11 @@ +import json from typing import Dict + from loguru import logger from pyvelociraptor import api_pb2 from werkzeug.utils import secure_filename + from app.services.Velociraptor.universal import UniversalService -import json class ArtifactsService: @@ -80,7 +82,7 @@ def collect_artifacts_linux(self): def collect_artifacts_windows(self): return self.collect_artifacts_prefixed("Windows.") - + def collect_artifacts_macos(self): return self.collect_artifacts_prefixed("MacOS.") @@ -97,7 +99,7 @@ def run_artifact_collection(self, client_id: str, artifact: str): """ try: query = self._create_query( - f"SELECT collect_client(client_id='{client_id}', artifacts=['{artifact}']) FROM scope()" + f"SELECT collect_client(client_id='{client_id}', artifacts=['{artifact}']) FROM scope()", ) flow = self.universal_service.execute_query(query) logger.info(f"Successfully ran artifact collection on {flow}") @@ -110,7 +112,9 @@ def run_artifact_collection(self, client_id: str, artifact: str): logger.info(f"Successfully watched flow completion on {completed}") results = self.universal_service.read_collection_results( - client_id, flow_id, artifact + client_id, + flow_id, + artifact, ) return results except Exception as err: diff --git a/backend/app/services/Velociraptor/universal.py b/backend/app/services/Velociraptor/universal.py index a261fffd..33cafbd8 100644 --- a/backend/app/services/Velociraptor/universal.py +++ b/backend/app/services/Velociraptor/universal.py @@ -1,20 +1,22 @@ -from app.models.agents import ( - AgentMetadata, - agent_metadata_schema, - agent_metadatas_schema, -) -from typing import Dict, List -from app import db +import json from datetime import datetime +from typing import Dict +from typing import List + +import grpc +import pyvelociraptor import requests -from loguru import logger from elasticsearch7 import Elasticsearch -from app.models.connectors import connector_factory, Connector -import pyvelociraptor +from loguru import logger from pyvelociraptor import api_pb2 from pyvelociraptor import api_pb2_grpc -import grpc -import json + +from app import db +from app.models.agents import AgentMetadata +from app.models.agents import agent_metadata_schema +from app.models.agents import agent_metadatas_schema +from app.models.connectors import Connector +from app.models.connectors import connector_factory class UniversalService: @@ -33,7 +35,9 @@ def setup_velociraptor_connector(self, connector_name: str): Args: connector_name (str): The name of the Velociraptor connector. """ - self.connector_url, self.connector_api_key = self.collect_velociraptor_details(connector_name) + self.connector_url, self.connector_api_key = self.collect_velociraptor_details( + connector_name, + ) self.config = pyvelociraptor.LoadConfigFile(self.connector_api_key) def collect_velociraptor_details(self, connector_name: str): @@ -67,7 +71,11 @@ def setup_grpc_channel_and_stub(self): certificate_chain=self.config["client_cert"].encode("utf8"), ) options = (("grpc.ssl_target_name_override", "VelociraptorServer"),) - self.channel = grpc.secure_channel(self.config["api_connection_string"], creds, options) + self.channel = grpc.secure_channel( + self.config["api_connection_string"], + creds, + options, + ) self.stub = api_pb2_grpc.APIStub(self.channel) def create_vql_request(self, vql: str): @@ -117,7 +125,6 @@ def execute_query(self, vql: str): "message": f"Failed to execute query: {e}", } - def watch_flow_completion(self, flow_id: str): """ Watch for the completion of a flow. @@ -131,7 +138,12 @@ def watch_flow_completion(self, flow_id: str): vql = f"SELECT * FROM watch_monitoring(artifact='System.Flow.Completion') WHERE FlowId='{flow_id}' LIMIT 1" return self.execute_query(vql) - def read_collection_results(self, client_id: str, flow_id: str, artifact: str = 'Generic.Client.Info/BasicInformation'): + def read_collection_results( + self, + client_id: str, + flow_id: str, + artifact: str = "Generic.Client.Info/BasicInformation", + ): """ Read the results of a collection. @@ -158,20 +170,22 @@ def get_client_id(self, client_name: str): """ # Formulate queries try: - vql_client_id = f"select client_id from clients(search='host:{client_name}')" - vql_last_seen_at = f"select last_seen_at from clients(search='host:{client_name}')" - + vql_client_id = ( + f"select client_id from clients(search='host:{client_name}')" + ) + vql_last_seen_at = ( + f"select last_seen_at from clients(search='host:{client_name}')" + ) + # Get the last seen timestamp last_seen_at = self._get_last_seen_timestamp(vql_last_seen_at) - + # if last_seen_at is longer than 30 seconds from now, return False if self._is_offline(last_seen_at): return { "success": False, "message": f"{client_name} has not been seen in the last 30 seconds and may not be online with the Velociraptor server.", - "results": [ - {"client_id": None} - ] + "results": [{"client_id": None}], } return self.execute_query(vql_client_id) @@ -179,9 +193,7 @@ def get_client_id(self, client_name: str): return { "success": False, "message": f"Failed to get Client ID for {client_name}: {e}", - "results": [ - {"client_id": None} - ] + "results": [{"client_id": None}], } def _get_last_seen_timestamp(self, vql: str): @@ -206,4 +218,6 @@ def _is_offline(self, last_seen_at: float): Returns: bool: True if the client is offline, False otherwise. """ - return (datetime.now() - datetime.fromtimestamp(last_seen_at / 1000000)).total_seconds() > 30 + return ( + datetime.now() - datetime.fromtimestamp(last_seen_at / 1000000) + ).total_seconds() > 30 diff --git a/backend/app/services/WazuhIndexer/alerts.py b/backend/app/services/WazuhIndexer/alerts.py index ec8d48b2..7f4d02db 100644 --- a/backend/app/services/WazuhIndexer/alerts.py +++ b/backend/app/services/WazuhIndexer/alerts.py @@ -1,9 +1,11 @@ +from typing import Dict +from typing import List + from elasticsearch7 import Elasticsearch from loguru import logger -from typing import Dict, List -from app.services.WazuhIndexer.universal import UniversalService from app.services.WazuhIndexer.index import IndexService +from app.services.WazuhIndexer.universal import UniversalService class AlertsService: @@ -47,7 +49,7 @@ def collect_alerts(self) -> Dict[str, object]: Dict[str, object]: A dictionary containing success status and alerts or an error message. """ if not all( - [self.connector_url, self.connector_username, self.connector_password] + [self.connector_url, self.connector_username, self.connector_password], ): return { "message": "Failed to collect Wazuh-Indexer details", @@ -70,7 +72,7 @@ def collect_alerts(self) -> Dict[str, object]: "index_name": index_name, "total_alerts": len(alerts["alerts"]), "last_10_alerts": alerts["alerts"], - } + }, ) return { @@ -113,8 +115,8 @@ def _build_query() -> Dict[str, object]: "should": [ {"range": {"rule_level": {"gte": 12}}}, {"match": {"syslog_level": "ALERT"}}, - ] - } + ], + }, }, "sort": [{"timestamp_utc": {"order": "desc"}}], } diff --git a/backend/app/services/WazuhIndexer/cluster.py b/backend/app/services/WazuhIndexer/cluster.py index fd21a972..3cdd8349 100644 --- a/backend/app/services/WazuhIndexer/cluster.py +++ b/backend/app/services/WazuhIndexer/cluster.py @@ -1,7 +1,9 @@ from typing import Dict + import requests from elasticsearch7 import Elasticsearch from loguru import logger + from app.services.WazuhIndexer.universal import UniversalService @@ -15,7 +17,11 @@ def __init__(self): self._initialize_es_client() def _collect_wazuhindexer_details(self): - self.connector_url, self.connector_username, self.connector_password = UniversalService().collect_wazuhindexer_details("Wazuh-Indexer") + ( + self.connector_url, + self.connector_username, + self.connector_password, + ) = UniversalService().collect_wazuhindexer_details("Wazuh-Indexer") def _initialize_es_client(self): self.es = Elasticsearch( @@ -28,7 +34,9 @@ def _initialize_es_client(self): ) def _are_details_collected(self) -> bool: - return all([self.connector_url, self.connector_username, self.connector_password]) + return all( + [self.connector_url, self.connector_username, self.connector_password], + ) def collect_node_allocation(self) -> Dict[str, object]: """ @@ -83,7 +91,7 @@ def _format_node_allocation(self, node_allocation): } for node in node_allocation ] - + def collect_cluster_health(self) -> Dict[str, object]: """ Collects the cluster health from the Wazuh-Indexer. @@ -106,7 +114,7 @@ def collect_cluster_health(self) -> Dict[str, object]: "success": True, "cluster_health": index_summary["cluster_health"], } - + def _collect_cluster_health(self) -> Dict[str, object]: """ Collects the cluster health from the Wazuh-Indexer. @@ -124,7 +132,7 @@ def _collect_cluster_health(self) -> Dict[str, object]: except Exception as e: logger.error(f"Failed to collect cluster health: {e}") return {"message": "Failed to collect cluster health", "success": False} - + def collect_shards(self) -> Dict[str, object]: """ Collects the shards from the Wazuh-Indexer. @@ -147,7 +155,7 @@ def collect_shards(self) -> Dict[str, object]: "success": True, "shards": index_summary["shards"], } - + def _collect_shards(self) -> Dict[str, object]: """ Collects the shards from the Wazuh-Indexer. @@ -166,7 +174,7 @@ def _collect_shards(self) -> Dict[str, object]: except Exception as e: logger.error(f"Failed to collect shards: {e}") return {"message": "Failed to collect shards", "success": False} - + def _format_shards(self, shards): return [ { @@ -178,4 +186,3 @@ def _format_shards(self, shards): } for shard in shards ] - diff --git a/backend/app/services/WazuhIndexer/index.py b/backend/app/services/WazuhIndexer/index.py index b37de981..09b82c27 100644 --- a/backend/app/services/WazuhIndexer/index.py +++ b/backend/app/services/WazuhIndexer/index.py @@ -1,7 +1,9 @@ from typing import Dict + import requests from elasticsearch7 import Elasticsearch from loguru import logger + from app.services.WazuhIndexer.universal import UniversalService @@ -15,7 +17,11 @@ def __init__(self): self._initialize_es_client() def _collect_wazuhindexer_details(self): - self.connector_url, self.connector_username, self.connector_password = UniversalService().collect_wazuhindexer_details("Wazuh-Indexer") + ( + self.connector_url, + self.connector_username, + self.connector_password, + ) = UniversalService().collect_wazuhindexer_details("Wazuh-Indexer") def _initialize_es_client(self): self.es = Elasticsearch( @@ -28,7 +34,9 @@ def _initialize_es_client(self): ) def _are_details_collected(self) -> bool: - return all([self.connector_url, self.connector_username, self.connector_password]) + return all( + [self.connector_url, self.connector_username, self.connector_password], + ) def collect_indices_summary(self) -> Dict[str, object]: """ @@ -38,8 +46,11 @@ def collect_indices_summary(self) -> Dict[str, object]: dict: A dictionary containing the success status, a message, and potentially the indices. """ if not self._are_details_collected(): - return {"message": "Failed to collect Wazuh-Indexer details", "success": False} - + return { + "message": "Failed to collect Wazuh-Indexer details", + "success": False, + } + index_summary = self._collect_indices() if not index_summary["success"]: return index_summary @@ -73,7 +84,11 @@ def _collect_indices(self) -> Dict[str, object]: """ try: indices = self.es.cat.indices(format="json") - return {"message": "Successfully collected indices", "success": True, "indices": indices} + return { + "message": "Successfully collected indices", + "success": True, + "indices": indices, + } except Exception as e: logger.error(e) return {"message": "Failed to collect indices", "success": False} diff --git a/backend/app/services/WazuhIndexer/universal.py b/backend/app/services/WazuhIndexer/universal.py index b9e59ab6..afdc8265 100644 --- a/backend/app/services/WazuhIndexer/universal.py +++ b/backend/app/services/WazuhIndexer/universal.py @@ -1,15 +1,17 @@ -from app.models.agents import ( - AgentMetadata, - agent_metadata_schema, - agent_metadatas_schema, -) -from typing import Dict, List -from app import db from datetime import datetime +from typing import Dict +from typing import List + import requests -from loguru import logger from elasticsearch7 import Elasticsearch -from app.models.connectors import connector_factory, Connector +from loguru import logger + +from app import db +from app.models.agents import AgentMetadata +from app.models.agents import agent_metadata_schema +from app.models.agents import agent_metadatas_schema +from app.models.connectors import Connector +from app.models.connectors import connector_factory class UniversalService: diff --git a/backend/app/services/WazuhManager/agent.py b/backend/app/services/WazuhManager/agent.py index d9aeaa25..67260e14 100644 --- a/backend/app/services/WazuhManager/agent.py +++ b/backend/app/services/WazuhManager/agent.py @@ -1,12 +1,19 @@ -from typing import Dict, Optional, List, Any +from typing import Any +from typing import Dict +from typing import List +from typing import Optional + +import requests from loguru import logger + from app.services.WazuhManager.universal import UniversalService -import requests + class WazuhHttpRequests: """ Class to handle HTTP requests to the Wazuh API. """ + def __init__(self, connector_url: str, wazuh_auth_token: str) -> None: """ Args: @@ -17,7 +24,11 @@ def __init__(self, connector_url: str, wazuh_auth_token: str) -> None: self.wazuh_auth_token = wazuh_auth_token self.headers = {"Authorization": f"Bearer {wazuh_auth_token}"} - def delete_request(self, endpoint: str, params: Optional[Dict[str, str]] = None) -> Dict[str, bool]: + def delete_request( + self, + endpoint: str, + params: Optional[Dict[str, str]] = None, + ) -> Dict[str, bool]: """ Function to handle DELETE requests. @@ -43,10 +54,12 @@ def delete_request(self, endpoint: str, params: Optional[Dict[str, str]] = None) logger.error(f"Failed to delete {endpoint}: {e}") return {"agentDeleted": False} + class WazuhManagerAgentService: """ A service class that encapsulates the logic for handling agent related operations in Wazuh Manager. """ + def __init__(self, universal_service: UniversalService) -> None: """ Args: @@ -54,7 +67,10 @@ def __init__(self, universal_service: UniversalService) -> None: """ self.universal_service = universal_service self.auth_token = universal_service.get_auth_token() - self.wazuh_http_requests = WazuhHttpRequests(self.universal_service.connector_url, self.auth_token) + self.wazuh_http_requests = WazuhHttpRequests( + self.universal_service.connector_url, + self.auth_token, + ) def collect_agents(self) -> Optional[List[Dict[str, str]]]: """ @@ -85,7 +101,9 @@ def _get_agent_data(self) -> Optional[Dict[str, Any]]: headers = {"Authorization": f"Bearer {self.auth_token}"} limit = 1000 response = requests.get( - f"{self.universal_service.connector_url}/agents?limit={limit}", headers=headers, verify=False + f"{self.universal_service.connector_url}/agents?limit={limit}", + headers=headers, + verify=False, ) if response.status_code == 200: return response.json()["data"]["affected_items"] diff --git a/backend/app/services/WazuhManager/disabled_rule.py b/backend/app/services/WazuhManager/disabled_rule.py index 619a2c62..c5990e38 100644 --- a/backend/app/services/WazuhManager/disabled_rule.py +++ b/backend/app/services/WazuhManager/disabled_rule.py @@ -1,18 +1,28 @@ -from typing import Dict, Optional, Union, List, Any, Tuple -from loguru import logger -from app.services.WazuhManager.universal import UniversalService +import json +import xml.etree.ElementTree as ET +from typing import Any +from typing import Dict +from typing import List +from typing import Optional +from typing import Tuple +from typing import Union + import requests -from app.models.rules import DisabledRules -from app.models.connectors import connector_factory, Connector -from app import db import xmltodict -import xml.etree.ElementTree as ET -import json +from loguru import logger + +from app import db +from app.models.connectors import Connector +from app.models.connectors import connector_factory +from app.models.rules import DisabledRules +from app.services.WazuhManager.universal import UniversalService + class WazuhHttpRequests: """ Class to handle HTTP requests to the Wazuh API. """ + def __init__(self, connector_url: str, wazuh_auth_token: str) -> None: """ Args: @@ -23,7 +33,11 @@ def __init__(self, connector_url: str, wazuh_auth_token: str) -> None: self.wazuh_auth_token = wazuh_auth_token self.headers = {"Authorization": f"Bearer {wazuh_auth_token}"} - def get_request(self, endpoint: str, params: Optional[Dict[str, str]] = None) -> Dict[str, Union[str, bool]]: + def get_request( + self, + endpoint: str, + params: Optional[Dict[str, str]] = None, + ) -> Dict[str, Union[str, bool]]: """ Function to handle GET requests. @@ -46,9 +60,17 @@ def get_request(self, endpoint: str, params: Optional[Dict[str, str]] = None) -> except Exception as e: logger.error(f"GET request to {endpoint} failed: {e}") - return {"message": f"GET request to {endpoint} failed: {e}", "success": False} - - def put_request(self, endpoint: str, data: str, params: Optional[Dict[str, str]] = None) -> Dict[str, bool]: + return { + "message": f"GET request to {endpoint} failed: {e}", + "success": False, + } + + def put_request( + self, + endpoint: str, + data: str, + params: Optional[Dict[str, str]] = None, + ) -> Dict[str, bool]: """ Function to handle PUT requests. @@ -83,6 +105,7 @@ class DisableRuleService: """ A service class that encapsulates the logic for handling rule disabling related operations in Wazuh Manager. """ + def __init__(self, universal_service: UniversalService) -> None: """ Args: @@ -90,20 +113,37 @@ def __init__(self, universal_service: UniversalService) -> None: """ self.universal_service = universal_service self.auth_token = universal_service.get_auth_token() - self.wazuh_http_requests = WazuhHttpRequests(self.universal_service.connector_url, self.auth_token) + self.wazuh_http_requests = WazuhHttpRequests( + self.universal_service.connector_url, + self.auth_token, + ) - def disable_rule(self, request: Dict[str, Union[str, int]]) -> Dict[str, Union[str, bool]]: + def disable_rule( + self, + request: Dict[str, Union[str, int]], + ) -> Dict[str, Union[str, bool]]: try: self._validate_request(request) rule_id = request["rule_id"] filename = self._fetch_filename(rule_id) file_content = self._fetch_file_content(filename) - previous_level, updated_file_content = self._set_level_1(file_content, rule_id) + previous_level, updated_file_content = self._set_level_1( + file_content, + rule_id, + ) xml_content = self._convert_to_xml(updated_file_content) - self._store_disabled_rule_info(rule_id, previous_level, request["reason"], request["length_of_time"]) + self._store_disabled_rule_info( + rule_id, + previous_level, + request["reason"], + request["length_of_time"], + ) self._upload_updated_rule(filename, xml_content) UniversalService().restart_service() - return {"message": f"Rule {rule_id} successfully disabled in file {filename}.", "success": True} + return { + "message": f"Rule {rule_id} successfully disabled in file {filename}.", + "success": True, + } except Exception as e: logger.error(str(e)) @@ -118,21 +158,35 @@ def _validate_request(self, request: Dict[str, Union[str, int]]): if "length_of_time" not in request: raise ValueError("Request missing length_of_time") request["length_of_time"] = int(request["length_of_time"]) - + def _fetch_filename(self, rule_id: str) -> str: - filename_data = self.wazuh_http_requests.get_request("rules", {"rule_ids": rule_id}) + filename_data = self.wazuh_http_requests.get_request( + "rules", + {"rule_ids": rule_id}, + ) if not filename_data["success"]: raise ValueError(filename_data["message"]) return filename_data["data"]["data"]["affected_items"][0]["filename"] - def _fetch_file_content(self, filename: str) -> Union[Dict[str, str], List[Dict[str, str]]]: - file_content_data = self.wazuh_http_requests.get_request(f"rules/files/{filename}") + def _fetch_file_content( + self, + filename: str, + ) -> Union[Dict[str, str], List[Dict[str, str]]]: + file_content_data = self.wazuh_http_requests.get_request( + f"rules/files/{filename}", + ) if not file_content_data["success"]: raise ValueError(file_content_data["message"]) return file_content_data["data"]["data"]["affected_items"][0]["group"] - def _set_level_1(self, file_content: Union[Dict[str, str], List[Dict[str, str]]], rule_id: str) -> Tuple[str, Union[Dict[str, str], List[Dict[str, str]]]]: - logger.info(f"Setting rule {rule_id} level to 1 for file_content: {file_content}") + def _set_level_1( + self, + file_content: Union[Dict[str, str], List[Dict[str, str]]], + rule_id: str, + ) -> Tuple[str, Union[Dict[str, str], List[Dict[str, str]]]]: + logger.info( + f"Setting rule {rule_id} level to 1 for file_content: {file_content}", + ) previous_level = None if isinstance(file_content, dict): file_content = [file_content] @@ -149,19 +203,31 @@ def _set_level_1(self, file_content: Union[Dict[str, str], List[Dict[str, str]]] break return previous_level, file_content - def _convert_to_xml(self, updated_file_content: Union[Dict[str, str], List[Dict[str, str]]]) -> str: + def _convert_to_xml( + self, + updated_file_content: Union[Dict[str, str], List[Dict[str, str]]], + ) -> str: logger.info(f"Received updated_file_content: {updated_file_content}") xml_content_list = [] for group in updated_file_content: xml_dict = {"group": group} xml_content = xmltodict.unparse(xml_dict, pretty=True) - xml_content = xml_content.replace('', "") + xml_content = xml_content.replace( + '', + "", + ) xml_content_list.append(xml_content) xml_content = "\n".join(xml_content_list) xml_content = xml_content.strip() return xml_content - def _store_disabled_rule_info(self, rule_id: str, previous_level: str, reason: str, length_of_time: str): + def _store_disabled_rule_info( + self, + rule_id: str, + previous_level: str, + reason: str, + length_of_time: str, + ): disabled_rule = DisabledRules( rule_id=rule_id, previous_level=previous_level, @@ -173,7 +239,10 @@ def _store_disabled_rule_info(self, rule_id: str, previous_level: str, reason: s db.session.commit() def _upload_updated_rule(self, filename: str, xml_content: str): - response = self.wazuh_http_requests.put_request(f"rules/files/{filename}", xml_content, {"overwrite": "true"}) + response = self.wazuh_http_requests.put_request( + f"rules/files/{filename}", + xml_content, + {"overwrite": "true"}, + ) if not response["success"]: raise ValueError(response["message"]) - diff --git a/backend/app/services/WazuhManager/enabled_rule.py b/backend/app/services/WazuhManager/enabled_rule.py index 5231b367..1aa2fb4a 100644 --- a/backend/app/services/WazuhManager/enabled_rule.py +++ b/backend/app/services/WazuhManager/enabled_rule.py @@ -1,19 +1,30 @@ -from typing import Dict, Optional, Union, List, Any, Tuple -from loguru import logger -from app.services.WazuhManager.universal import UniversalService +import json +import xml.etree.ElementTree as ET + +# from typing import Tuple +from typing import Any +from typing import Dict +from typing import List +from typing import Optional +from typing import Union + import requests -from app.models.rules import DisabledRules -from app.models.connectors import connector_factory, Connector -from app import db import xmltodict -import xml.etree.ElementTree as ET -import json +from loguru import logger + +from app import db + +# from app.models.connectors import Connector +# from app.models.connectors import connector_factory +from app.models.rules import DisabledRules +from app.services.WazuhManager.universal import UniversalService class WazuhHttpRequests: """ Class to handle HTTP requests to the Wazuh API. """ + def __init__(self, connector_url: str, wazuh_auth_token: str) -> None: """ Args: @@ -24,7 +35,11 @@ def __init__(self, connector_url: str, wazuh_auth_token: str) -> None: self.wazuh_auth_token = wazuh_auth_token self.headers = {"Authorization": f"Bearer {wazuh_auth_token}"} - def get_request(self, endpoint: str, params: Optional[Dict[str, str]] = None) -> Dict[str, Union[str, bool]]: + def get_request( + self, + endpoint: str, + params: Optional[Dict[str, str]] = None, + ) -> Dict[str, Union[str, bool]]: """ Function to handle GET requests. @@ -49,9 +64,17 @@ def get_request(self, endpoint: str, params: Optional[Dict[str, str]] = None) -> except Exception as e: logger.error(f"GET request to {endpoint} failed: {e}") - return {"message": f"GET request to {endpoint} failed: {e}", "success": False} + return { + "message": f"GET request to {endpoint} failed: {e}", + "success": False, + } - def put_request(self, endpoint: str, data: str, params: Optional[Dict[str, str]] = None) -> Dict[str, bool]: + def put_request( + self, + endpoint: str, + data: str, + params: Optional[Dict[str, str]] = None, + ) -> Dict[str, bool]: """ Function to handle PUT requests. @@ -86,6 +109,7 @@ class EnableRuleService: """ A service class that encapsulates the logic for handling rule enabling related operations in Wazuh Manager. """ + def __init__(self, universal_service: UniversalService) -> None: """ Args: @@ -93,7 +117,10 @@ def __init__(self, universal_service: UniversalService) -> None: """ self.universal_service = universal_service self.auth_token = universal_service.get_auth_token() - self.wazuh_http_requests = WazuhHttpRequests(self.universal_service.connector_url, self.auth_token) + self.wazuh_http_requests = WazuhHttpRequests( + self.universal_service.connector_url, + self.auth_token, + ) def enable_rule(self, request: Dict[str, str]) -> Dict[str, Union[str, bool]]: """ @@ -112,7 +139,11 @@ def enable_rule(self, request: Dict[str, str]) -> Dict[str, Union[str, bool]]: logger.info(f"Getting file content of {filename}") file_content = self._fetch_file_content(filename) previous_level = self._get_previous_level(rule_id) - updated_file_content = self._set_level_previous(file_content, rule_id, previous_level) + updated_file_content = self._set_level_previous( + file_content, + rule_id, + previous_level, + ) xml_content = self._json_to_xml(updated_file_content) self._delete_rule_from_db(rule_id) self._put_updated_rule(filename, xml_content) @@ -155,7 +186,10 @@ def _fetch_filename(self, rule_id: str) -> str: Returns: str: The filename of the rule to be enabled. """ - filename_data = self.wazuh_http_requests.get_request("rules", {"rule_ids": rule_id}) + filename_data = self.wazuh_http_requests.get_request( + "rules", + {"rule_ids": rule_id}, + ) if not filename_data["success"]: raise ValueError(filename_data["message"]) return filename_data["data"]["data"]["affected_items"][0]["filename"] @@ -173,7 +207,9 @@ def _fetch_file_content(self, filename: str) -> Any: Returns: Any: The content of the rule file. """ - file_content_data = self.wazuh_http_requests.get_request(f"rules/files/{filename}") + file_content_data = self.wazuh_http_requests.get_request( + f"rules/files/{filename}", + ) if not file_content_data["success"]: raise ValueError(file_content_data["message"]) return file_content_data["data"]["data"]["affected_items"][0]["group"] @@ -196,7 +232,12 @@ def _get_previous_level(self, rule_id: str) -> str: raise ValueError(f"Rule {rule_id} is not disabled.") return disabled_rule.previous_level - def _set_level_previous(self, file_content: Any, rule_id: str, previous_level: str) -> Any: + def _set_level_previous( + self, + file_content: Any, + rule_id: str, + previous_level: str, + ) -> Any: """ Set the level of the rule to be enabled to the previous level. @@ -209,7 +250,7 @@ def _set_level_previous(self, file_content: Any, rule_id: str, previous_level: s Any: The content of the rule with the level set to the previous level. """ logger.info( - f"Setting rule {rule_id} level to {previous_level} for file_content: {file_content}" + f"Setting rule {rule_id} level to {previous_level} for file_content: {file_content}", ) # If 'file_content' is a dictionary (representing a single group), make it a list of one group if isinstance(file_content, dict): @@ -252,7 +293,8 @@ def _json_to_xml(self, file_content: Any) -> str: # Remove the `` from the # beginning of the XML string. xml_content = xml_content.replace( - '', "" + '', + "", ) xml_content_list.append(xml_content) @@ -285,6 +327,10 @@ def _put_updated_rule(self, filename: str, xml_content: str): Raises: RuntimeError: If the PUT operation fails. """ - response = self.wazuh_http_requests.put_request(f"rules/files/{filename}", xml_content, params={"overwrite": "true"}) + response = self.wazuh_http_requests.put_request( + f"rules/files/{filename}", + xml_content, + params={"overwrite": "true"}, + ) if not response["success"]: raise RuntimeError(f"Could not PUT rule {filename}") diff --git a/backend/app/services/WazuhManager/universal.py b/backend/app/services/WazuhManager/universal.py index 0af61fc1..b3868eef 100644 --- a/backend/app/services/WazuhManager/universal.py +++ b/backend/app/services/WazuhManager/universal.py @@ -1,6 +1,9 @@ -from loguru import logger import requests -from app.models.connectors import connector_factory, Connector +from loguru import logger + +from app.models.connectors import Connector +from app.models.connectors import connector_factory + class UniversalService: """ @@ -47,7 +50,7 @@ def get_auth_token(self): auth_token = response.json()["data"]["token"] logger.info(f"Authentication token: {auth_token}") return auth_token - + def restart_service(self): """ Restart the Wazuh Manager service. @@ -63,11 +66,11 @@ def restart_service(self): verify=False, ) if response.status_code == 200: - logger.info(f"Wazuh Manager service restarted") + logger.info("Wazuh Manager service restarted") return {"message": "Wazuh Manager service restarted", "success": True} else: logger.error( - f"Wazuh Manager service restart failed with error: {response.text}" + f"Wazuh Manager service restart failed with error: {response.text}", ) return { "message": "Wazuh Manager service restart failed", diff --git a/backend/app/services/WazuhManager/vulnerability.py b/backend/app/services/WazuhManager/vulnerability.py index 3f05a93d..2294b1b0 100644 --- a/backend/app/services/WazuhManager/vulnerability.py +++ b/backend/app/services/WazuhManager/vulnerability.py @@ -1,12 +1,19 @@ -from typing import Dict, List, Optional, Any +from typing import Any +from typing import Dict +from typing import List +from typing import Optional + +import requests from loguru import logger + from app.services.WazuhManager.universal import UniversalService -import requests + class WazuhHttpRequests: """ Class to handle HTTP requests to the Wazuh API. """ + def __init__(self, connector_url: str, wazuh_auth_token: str) -> None: """ Args: @@ -17,7 +24,11 @@ def __init__(self, connector_url: str, wazuh_auth_token: str) -> None: self.wazuh_auth_token = wazuh_auth_token self.headers = {"Authorization": f"Bearer {wazuh_auth_token}"} - def get_request(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Optional[Dict[str, Any]]: + def get_request( + self, + endpoint: str, + params: Optional[Dict[str, Any]] = None, + ) -> Optional[Dict[str, Any]]: """ Function to handle GET requests. @@ -39,13 +50,17 @@ def get_request(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> return response.json() except Exception as e: - logger.error(f"GET request to {self.connector_url}/{endpoint} failed with error: {e}") + logger.error( + f"GET request to {self.connector_url}/{endpoint} failed with error: {e}", + ) return None + class VulnerabilityService: """ A service class that encapsulates the logic for pulling API data from Wazuh Manager. """ + def __init__(self, universal_service: UniversalService) -> None: """ Args: @@ -53,7 +68,10 @@ def __init__(self, universal_service: UniversalService) -> None: """ self.universal_service = universal_service self.auth_token = universal_service.get_auth_token() - self.wazuh_http_requests = WazuhHttpRequests(self.universal_service.connector_url, self.auth_token) + self.wazuh_http_requests = WazuhHttpRequests( + self.universal_service.connector_url, + self.auth_token, + ) def agent_vulnerabilities(self, agent_id: str) -> List[Dict[str, Any]]: """ @@ -65,14 +83,20 @@ def agent_vulnerabilities(self, agent_id: str) -> List[Dict[str, Any]]: Returns: List[Dict[str, Any]]: A list of processed vulnerabilities. """ - response = self.wazuh_http_requests.get_request(f"vulnerability/{agent_id}", params={"wait_for_complete": True}) + response = self.wazuh_http_requests.get_request( + f"vulnerability/{agent_id}", + params={"wait_for_complete": True}, + ) if response is not None: processed_vulnerabilities = self._process_agent_vulnerabilities(response) return processed_vulnerabilities return [] - def _process_agent_vulnerabilities(self, response: Dict[str, Any]) -> List[Dict[str, Any]]: + def _process_agent_vulnerabilities( + self, + response: Dict[str, Any], + ) -> List[Dict[str, Any]]: """ Process the vulnerabilities of an agent from Wazuh Manager. diff --git a/backend/app/services/agents/agents.py b/backend/app/services/agents/agents.py index 33c9d798..a22a65dc 100644 --- a/backend/app/services/agents/agents.py +++ b/backend/app/services/agents/agents.py @@ -1,14 +1,16 @@ # services.py -from app.models.agents import ( - AgentMetadata, - agent_metadata_schema, - agent_metadatas_schema, -) -from app import db from datetime import datetime + import requests from loguru import logger -from app.models.connectors import connector_factory, Connector, WazuhManagerConnector + +from app import db +from app.models.agents import AgentMetadata +from app.models.agents import agent_metadata_schema +from app.models.agents import agent_metadatas_schema +from app.models.connectors import Connector +from app.models.connectors import WazuhManagerConnector +from app.models.connectors import connector_factory class AgentService: @@ -34,7 +36,8 @@ def get_agent(self, agent_id): agent_id (str): The ID of the agent to retrieve. Returns: - dict: A dictionary representing the serialized data of the agent if found, otherwise a message indicating that the agent was not found. + dict: A dictionary representing the serialized data of the agent if found, otherwise a message indicating + that the agent was not found. """ agent = db.session.query(AgentMetadata).filter_by(agent_id=agent_id).first() if agent is None: @@ -49,7 +52,8 @@ def mark_agent_as_critical(self, agent_id): agent_id (str): The ID of the agent to mark as critical. Returns: - dict: A dictionary representing a success message if the operation was successful, otherwise an error message. + dict: A dictionary representing a success message if the operation was successful, otherwise an error + message. """ agent = db.session.query(AgentMetadata).filter_by(agent_id=agent_id).first() @@ -58,7 +62,7 @@ def mark_agent_as_critical(self, agent_id): agent.mark_as_critical() agent_details = agent_metadata_schema.dump(agent) - if agent_details["critical_asset"] == False: + if agent_details["critical_asset"] is False: return { "message": f"Agent {agent_id} failed to mark agent as critical", "success": False, @@ -73,7 +77,8 @@ def mark_agent_as_non_critical(self, agent_id): agent_id (str): The ID of the agent to mark as non-critical. Returns: - dict: A dictionary representing a success message if the operation was successful, otherwise an error message. + dict: A dictionary representing a success message if the operation was successful, otherwise an error + message. """ agent = db.session.query(AgentMetadata).filter_by(agent_id=agent_id).first() @@ -82,7 +87,7 @@ def mark_agent_as_non_critical(self, agent_id): agent.mark_as_non_critical() agent_details = agent_metadata_schema.dump(agent) - if agent_details["critical_asset"] == True: + if agent_details["critical_asset"] is True: return { "message": f"Agent {agent_id} failed to mark agent as non-critical", "success": False, @@ -101,14 +106,16 @@ def create_agent(self, agent): """ try: agent_last_seen = datetime.strptime( - agent["agent_last_seen"], "%Y-%m-%dT%H:%M:%S+00:00" + agent["agent_last_seen"], + "%Y-%m-%dT%H:%M:%S+00:00", ) # Convert to datetime except ValueError: logger.info( - f"Invalid format for agent_last_seen: {agent['agent_last_seen']}. Fixing..." + f"Invalid format for agent_last_seen: {agent['agent_last_seen']}. Fixing...", ) agent_last_seen = datetime.strptime( - "1970-01-01T00:00:00+00:00", "%Y-%m-%dT%H:%M:%S+00:00" + "1970-01-01T00:00:00+00:00", + "%Y-%m-%dT%H:%M:%S+00:00", ) # Use the epoch time as default agent_metadata = AgentMetadata( @@ -137,7 +144,8 @@ def delete_agent_db(self, agent_id): agent_id (str): The ID of the agent to delete. Returns: - dict: A dictionary representing a success message if the operation was successful, otherwise an error message. + dict: A dictionary representing a success message if the operation was successful, otherwise an error + message. """ agent = db.session.query(AgentMetadata).filter_by(agent_id=agent_id).first() if agent is None: @@ -189,7 +197,9 @@ def collect_wazuh_agents(self, connection_url: str, wazuh_auth_token: str): headers = {"Authorization": f"Bearer {wazuh_auth_token}"} limit = 1000 agents_collected = requests.get( - f"{connection_url}/agents?limit={limit}", headers=headers, verify=False + f"{connection_url}/agents?limit={limit}", + headers=headers, + verify=False, ) if agents_collected.status_code == 200: wazuh_agents_list = [] diff --git a/backend/app/services/connectors/connectors.py b/backend/app/services/connectors/connectors.py index 9d5c11b8..efaa91ac 100644 --- a/backend/app/services/connectors/connectors.py +++ b/backend/app/services/connectors/connectors.py @@ -1,8 +1,14 @@ -from app.models.connectors import connector_factory, Connector, ConnectorFactory -from app.models.models import Connectors, connectors_schema, ConnectorsAvailable -from sqlalchemy.exc import SQLAlchemyError -from loguru import logger from flask import current_app +from loguru import logger +from sqlalchemy.exc import SQLAlchemyError + +# from app.models.connectors import ConnectorFactory +from app.models.connectors import Connector +from app.models.connectors import connector_factory +from app.models.models import Connectors + +# from app.models.models import ConnectorsAvailable +# from app.models.models import connectors_schema class ConnectorService: @@ -53,7 +59,8 @@ def process_connector(self, connector_name: str): def validate_connector_exists(self, connector_id: int): """ - Validates that a connector exists in the database. Returns a dictionary containing the validation status and a message indicating the status. + Validates that a connector exists in the database. Returns a dictionary containing the validation status + and a message indicating the status. Args: connector_id (int): The id of the connector to be validated. @@ -91,7 +98,8 @@ def update_connector(self, connector_id: int, updated_data: dict): updated_data (dict): A dictionary containing the updated data for the connector. Returns: - dict: A dictionary containing the success status and a message indicating the status. If the update operation was successful, it returns the connector name. + dict: A dictionary containing the success status and a message indicating the status. + If the update operation was successful, it returns the connector name. """ try: connector = ( @@ -126,7 +134,8 @@ def verify_connector_connection(self, connector_id: int): connector_id (int): The id of the connector to be verified. Returns: - dict: A dictionary containing the success status and a message indicating the status. If the verification operation was successful, it returns the connector name. + dict: A dictionary containing the success status and a message indicating the status. If the verification + operation was successful, it returns the connector name. """ try: connector = ( @@ -138,7 +147,8 @@ def verify_connector_connection(self, connector_id: int): "success": False, } connector_instance = connector_factory.create( - connector.connector_name, connector.connector_name + connector.connector_name, + connector.connector_name, ) connection_successful = connector_instance.verify_connection() # Connection successful: {'connectionSuccessful': False} @@ -160,7 +170,8 @@ def verify_connector_connection(self, connector_id: int): def validate_request_data(self, request_data: dict): """ - Validates the request data to ensure `connector_url`, `connector_username` and `connector_password` are present. Returns a dictionary containing the validation status and a message indicating the status. + Validates the request data to ensure `connector_url`, `connector_username` and `connector_password` are present. + Returns a dictionary containing the validation status and a message indicating the status. Args: request_data (dict): A dictionary containing the request data. @@ -176,13 +187,15 @@ def validate_request_data(self, request_data: dict): return {"message": "Request data is valid", "success": True} else: return { - "message": "Request data is invalid. Ensure connector_url, connector_username and connector_password are present", + "message": "Request data is invalid. Ensure connector_url, connector_username and connector_password " + "are present", "success": False, } def validate_request_data_api_key(self, request_data: dict): """ - Validates the request data to ensure `connector_url` and `connector_api_key` are present. Returns a dictionary containing the validation status and a message indicating the status. + Validates the request data to ensure `connector_url` and `connector_api_key` are present. Returns a dictionary + containing the validation status and a message indicating the status. Args: request_data (dict): A dictionary containing the request data. @@ -191,7 +204,8 @@ def validate_request_data_api_key(self, request_data: dict): dict: A dictionary containing the validation status and a message indicating the status. """ if request_data.get("connector_url", None) and request_data.get( - "connector_api_key", None + "connector_api_key", + None, ): return {"message": "Request data is valid", "success": True} else: diff --git a/backend/app/static/swagger.json b/backend/app/static/swagger.json index 77ec8796..71af1b30 100644 --- a/backend/app/static/swagger.json +++ b/backend/app/static/swagger.json @@ -11,76 +11,74 @@ ], "tags": [ { - "name": "Connectors", - "description": "Everything about your Connectors", - "externalDocs": { - "description": "Find out more", - "url": "http://swagger.io" - } + "name": "Connectors", + "description": "Everything about your Connectors", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } }, { - "name": "Agents", - "description": "Everything about your Agents", - "externalDocs": { - "description": "Find out more", - "url": "http://swagger.io" - } + "name": "Agents", + "description": "Everything about your Agents", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } }, { - "name": "Rules", - "description": "Everything about your Wazuh Rules", - "externalDocs": { - "description": "Find out more", - "url": "http://swagger.io" - } + "name": "Rules", + "description": "Everything about your Wazuh Rules", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } }, { - "name": "Graylog", - "description": "Everything about Graylog", - "externalDocs": { - "description": "Find out more", - "url": "http://swagger.io" - } + "name": "Graylog", + "description": "Everything about Graylog", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } }, { - "name": "Wazuh-Indexer", - "description": "Everything about Wazuh-Indexer", - "externalDocs": { - "description": "Find out more", - "url": "http://swagger.io" - } + "name": "Wazuh-Indexer", + "description": "Everything about Wazuh-Indexer", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } }, { - "name": "Shuffle", - "description": "Everything about Shuffle", - "externalDocs": { - "description": "Find out more", - "url": "http://swagger.io" - } + "name": "Shuffle", + "description": "Everything about Shuffle", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } }, { - "name": "Velociraptor", - "description": "Everything about Velociraptor", - "externalDocs": { - "description": "Find out more", - "url": "http://swagger.io" - } + "name": "Velociraptor", + "description": "Everything about Velociraptor", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } }, { - "name": "DFIR-IRIS", - "description": "Everything about DFIR-IRIS", - "externalDocs": { - "description": "Find out more", - "url": "http://swagger.io" - } + "name": "DFIR-IRIS", + "description": "Everything about DFIR-IRIS", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } } - ], + ], "paths": { "/connectors": { "get": { - "tags": [ - "Connectors" - ], + "tags": ["Connectors"], "summary": "List all available connectors", "description": "Endpoint to list all available connectors. It processes each connector to verify the connection and returns the results.", "responses": { @@ -95,38 +93,38 @@ "properties": { "connectionSuccessful": { "type": "boolean" - }, - "connector_api_key": { + }, + "connector_api_key": { "type": "string", "nullable": true - }, - "connector_last_updated": { + }, + "connector_last_updated": { "type": "string", "format": "date-time" - }, - "connector_name": { + }, + "connector_name": { "type": "string" - }, - "connector_password": { + }, + "connector_password": { "type": "string", "nullable": true - }, - "connector_type": { + }, + "connector_type": { "type": "string" - }, - "connector_url": { + }, + "connector_url": { "type": "string" - }, - "connector_username": { + }, + "connector_username": { "type": "string", "nullable": true - }, - "id": { + }, + "id": { "type": "integer" - }, - "name": { + }, + "name": { "type": "string" - } + } } } } @@ -138,78 +136,74 @@ }, "/connectors/{id}": { "get": { - "tags": [ - "Connectors" - ], - "summary": "Get the details of a specific connector.", - "operationId": "getConnectorDetails", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "The id of the connector to be fetched.", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "A JSON response containing the details of the connector.", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "connectionSuccessful": { - "type": "boolean" - }, - "connector_api_key": { - "type": "string", - "nullable": true - }, - "connector_last_updated": { - "type": "string", - "format": "date-time" - }, - "connector_name": { - "type": "string" - }, - "connector_password": { - "type": "string", - "nullable": true - }, - "connector_type": { - "type": "string" - }, - "connector_url": { - "type": "string" - }, - "connector_username": { - "type": "string", - "nullable": true - }, - "id": { + "tags": ["Connectors"], + "summary": "Get the details of a specific connector.", + "operationId": "getConnectorDetails", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The id of the connector to be fetched.", + "required": true, + "schema": { "type": "integer" - } } - } } - } - }, - "404": { - "description": "The connector could not be found." + ], + "responses": { + "200": { + "description": "A JSON response containing the details of the connector.", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "connectionSuccessful": { + "type": "boolean" + }, + "connector_api_key": { + "type": "string", + "nullable": true + }, + "connector_last_updated": { + "type": "string", + "format": "date-time" + }, + "connector_name": { + "type": "string" + }, + "connector_password": { + "type": "string", + "nullable": true + }, + "connector_type": { + "type": "string" + }, + "connector_url": { + "type": "string" + }, + "connector_username": { + "type": "string", + "nullable": true + }, + "id": { + "type": "integer" + } + } + } + } + } + }, + "404": { + "description": "The connector could not be found." + } } - } }, "put": { - "tags": [ - "Connectors" - ], + "tags": ["Connectors"], "summary": "Update a connector", "description": "Endpoint to update a connector. If the update operation was successful, it returns the connection verification status for the updated connector.", "operationId": "update_connector_route", @@ -314,9 +308,7 @@ }, "/agents": { "get": { - "tags": [ - "Agents" - ], + "tags": ["Agents"], "summary": "List all available agents", "description": "Endpoint to list all available agents. It processes each agent to verify the connection and returns the results.", "responses": { @@ -338,9 +330,7 @@ }, "/agents/{id}": { "get": { - "tags": [ - "Agents" - ], + "tags": ["Agents"], "summary": "Get details of a specific agent", "description": "Endpoint to get the details of an agent.", "parameters": [ @@ -370,1531 +360,1468 @@ }, "/agents/{id}/critical": { "post": { - "tags": [ - "Agents" - ], - "summary": "Marks an agent as critical", - "description": "Marks an agent as a critical asset in the database.", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "ID of the agent to be marked as critical", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "Agent marked as critical", - "schema": { - "$ref": "#/definitions/Agent" - } - }, - "400": { - "description": "Invalid ID supplied" - }, - "404": { - "description": "Agent not found" - } - } - } - }, - "/agents/{id}/delete": { - "post": { - "tags": [ - "Agents" - ], - "summary": "Deletes an agent", - "description": "Deletes an agent from the database.", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "ID of the agent to be deleted", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "Agent deleted", - "schema": { - "$ref": "#/definitions/Agent" - } - }, - "400": { - "description": "Invalid ID supplied" - }, - "404": { - "description": "Agent not found" - } - } - } - - }, - "/agents/{id}/noncritical": { - "post": { - "tags": [ - "Agents" - ], - "summary": "Unmarks an agent as critical", - "description": "Marks an agent as a non-critical asset in the database.", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "ID of the agent to be unmarked as critical", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "Agent unmarked as critical", - "schema": { - "$ref": "#/definitions/Agent" - } - }, - "400": { - "description": "Invalid ID supplied" - }, - "404": { - "description": "Agent not found" - } - } - } - }, - "/agents/sync": { - "post": { - "summary": "Sync all agents", - "description": "Endpoint to sync all agents.", - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "Operation message" - }, - "success": { - "type": "boolean", - "description": "Indicates if the operation was successful" - }, - "agents_added": { - "type": "array", - "items": { - "type": "object", - "description": "Agent details" - } - } - } - } + "tags": ["Agents"], + "summary": "Marks an agent as critical", + "description": "Marks an agent as a critical asset in the database.", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of the agent to be marked as critical", + "required": true, + "type": "string" } - } - }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } + ], + "responses": { + "200": { + "description": "Agent marked as critical", + "schema": { + "$ref": "#/definitions/Agent" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Agent not found" } - } } - }, - "operationId": "syncAgents", - "tags": [ - "Agents" - ] } - }, - "/agents/{id}/vulnerabilities": { - "get": { - "tags": [ - "Agents" - ], - "summary": "Get vulnerabilities of a specific agent", - "description": "Endpoint to get the vulnerabilities of an agent.", - "parameters": [ - { - "name": "id", - "in": "path", - "required": true, - "description": "ID of the agent to be fetched", - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Vulnerability" - } - } - } - } - } - } - } - }, - "/rule/disable": { + }, + "/agents/{id}/delete": { "post": { - "summary": "Disable a rule", - "description": "Endpoint to disable a rule.", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "rule_id": { - "type": "string", - "description": "The ID of the rule to be disabled." - }, - "reason": { - "type": "string", - "description": "The reason for disabling the rule." - }, - "length_of_time": { - "type": "integer", - "description": "The length of time the rule should be disabled for." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "Operation message" - }, - "success": { - "type": "boolean", - "description": "Indicates if the operation was successful" - } - } - } + "tags": ["Agents"], + "summary": "Deletes an agent", + "description": "Deletes an agent from the database.", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of the agent to be deleted", + "required": true, + "type": "string" } - } - }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } + ], + "responses": { + "200": { + "description": "Agent deleted", + "schema": { + "$ref": "#/definitions/Agent" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Agent not found" } - } } - }, - "operationId": "disableRule", - "tags": [ - "Rules" - ] } - }, - "/rule/enable": { + }, + "/agents/{id}/noncritical": { "post": { - "summary": "Enable a rule", - "description": "Endpoint to enable a rule.", - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "rule_id": { - "type": "string", - "description": "The ID of the rule to be enabled." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string", - "description": "Operation message" - }, - "success": { - "type": "boolean", - "description": "Indicates if the operation was successful" - } - } - } - } - } - }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "enableRule", - "tags": [ - "Rules" - ] - } - }, - "/graylog/messages": { - "get": { - "summary": "Get messages from Graylog", - "description": "Endpoint to get messages from Graylog.", - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "messages": { - "type": "array", - "items": { - "type": "object", - "description": "Message details" - } - } - } - } + "tags": ["Agents"], + "summary": "Unmarks an agent as critical", + "description": "Marks an agent as a non-critical asset in the database.", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "ID of the agent to be unmarked as critical", + "required": true, + "type": "string" } - } - }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } + ], + "responses": { + "200": { + "description": "Agent unmarked as critical", + "schema": { + "$ref": "#/definitions/Agent" + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Agent not found" } - } } - }, - "operationId": "getGraylogMessages", - "tags": [ - "Graylog" - ] } - }, - "/graylog/metrics": { - "get": { - "summary": "Get metrics from Graylog", - "description": "Endpoint to get metrics from Graylog.", - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "metrics": { - "type": "array", - "items": { - "type": "object", - "description": "Metric details" - } - } - } - } + }, + "/agents/sync": { + "post": { + "summary": "Sync all agents", + "description": "Endpoint to sync all agents.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Operation message" + }, + "success": { + "type": "boolean", + "description": "Indicates if the operation was successful" + }, + "agents_added": { + "type": "array", + "items": { + "type": "object", + "description": "Agent details" + } + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } - } }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "getGraylogMetrics", - "tags": [ - "Graylog" - ] + "operationId": "syncAgents", + "tags": ["Agents"] } - }, - "/graylog/indices": { + }, + "/agents/{id}/vulnerabilities": { "get": { - "summary": "Get indices from Graylog", - "description": "Endpoint to get indices from Graylog.", - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "indices": { - "type": "array", - "items": { - "type": "object", - "description": "Index details" - } - } - } - } + "tags": ["Agents"], + "summary": "Get vulnerabilities of a specific agent", + "description": "Endpoint to get the vulnerabilities of an agent.", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "ID of the agent to be fetched", + "schema": { + "type": "string" + } } - } - }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Vulnerability" + } + } + } + } } - } } - }, - "operationId": "getGraylogIndices", - "tags": [ - "Graylog" - ] } - }, - "/graylog/indices/{index_name}/delete": { - "delete": { - "tags": [ - "Graylog" - ], - "summary": "Deletes a Graylog index", - "description": "Endpoint to delete a Graylog index.", - "parameters": [ - { - "name": "index_name", - "in": "path", - "description": "The name of the index to be deleted.", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "Successful operation", - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "success": { - "type": "boolean" - } + }, + "/rule/disable": { + "post": { + "summary": "Disable a rule", + "description": "Endpoint to disable a rule.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "rule_id": { + "type": "string", + "description": "The ID of the rule to be disabled." + }, + "reason": { + "type": "string", + "description": "The reason for disabling the rule." + }, + "length_of_time": { + "type": "integer", + "description": "The length of time the rule should be disabled for." + } + } + } + } } - } }, - "400": { - "description": "Bad request", - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "success": { - "type": "boolean" - } + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Operation message" + }, + "success": { + "type": "boolean", + "description": "Indicates if the operation was successful" + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } - } }, - "404": { - "description": "Index not found", - "schema": { - "type": "object", - "properties": { - "message": { - "type": "string" - }, - "success": { - "type": "boolean" - } - } - } - } - } + "operationId": "disableRule", + "tags": ["Rules"] } - }, - "/graylog/inputs": { - "get": { - "summary": "Get inputs from Graylog", - "description": "Endpoint to get inputs from Graylog.", - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "inputs": { - "type": "array", - "items": { - "type": "object", - "description": "Input details" - } - } - } - } + }, + "/rule/enable": { + "post": { + "summary": "Enable a rule", + "description": "Endpoint to enable a rule.", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "rule_id": { + "type": "string", + "description": "The ID of the rule to be enabled." + } + } + } + } } - } }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "getGraylogInputs", - "tags": [ - "Graylog" - ] - } - }, - "/alerts": { - "get": { - "summary": "Get alerts", - "description": "Endpoint to get alerts.", - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "alerts": { - "type": "array", - "items": { - "type": "object", - "description": "Alert details" - } - } - } - } - } - } - }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "getAlerts", - "tags": [ - "Wazuh-Indexer" - ] - } - }, - "/wazuh_indexer/allocation": { - "get": { - "summary": "Get node allocation of the Wazuh-Indexer nodes", - "description": "Endpoint to get node allocation.", - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "indices": { - "type": "array", - "items": { - "type": "object", - "description": "Node details" - } - } - } - } - } - } - }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "getNodeAllocation", - "tags": [ - "Wazuh-Indexer" - ] - } - }, - "/wazuh_indexer/indices": { - "get": { - "summary": "Get indices from Wazuh-Indexer", - "description": "Endpoint to get indices from Wazuh-Indexer.", - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "indices": { - "type": "array", - "items": { - "type": "object", - "description": "Index details" - } - } - } - } + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string", + "description": "Operation message" + }, + "success": { + "type": "boolean", + "description": "Indicates if the operation was successful" + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } - } }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "getWazuhIndices", - "tags": [ - "Wazuh-Indexer" - ] + "operationId": "enableRule", + "tags": ["Rules"] } - }, - "/wazuh_indexer/health": { + }, + "/graylog/messages": { "get": { - "summary": "Get health of the Wazuh-Indexer nodes", - "description": "Endpoint to get health.", - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "cluster_name": { - "type": "string" - }, - "status": { - "type": "string" - }, - "number_of_nodes": { - "type": "integer" - }, - "number_of_data_nodes": { - "type": "integer" - }, - "active_primary_shards": { - "type": "integer" - }, - "active_shards": { - "type": "integer" - }, - "relocating_shards": { - "type": "integer" - }, - "initializing_shards": { - "type": "integer" - }, - "unassigned_shards": { - "type": "integer" - }, - "delayed_unassigned_shards": { - "type": "integer" - }, - "number_of_pending_tasks": { - "type": "integer" - }, - "number_of_in_flight_fetch": { - "type": "integer" - }, - "task_max_waiting_in_queue_millis": { - "type": "integer" - }, - "active_shards_percent_as_number": { - "type": "integer" - } + "summary": "Get messages from Graylog", + "description": "Endpoint to get messages from Graylog.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "messages": { + "type": "array", + "items": { + "type": "object", + "description": "Message details" + } + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } - } } - } }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "getHealth", - "tags": [ - "Wazuh-Indexer" - ] + "operationId": "getGraylogMessages", + "tags": ["Graylog"] } - }, - "/wazuh_indexer/shards": { + }, + "/graylog/metrics": { "get": { - "summary": "Get shards from Wazuh-Indexer", - "description": "Endpoint to get shards from Wazuh-Indexer.", - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "shards": { - "type": "array", - "items": { - "type": "object", - "description": "Shard details" - } - } - } - } + "summary": "Get metrics from Graylog", + "description": "Endpoint to get metrics from Graylog.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "metrics": { + "type": "array", + "items": { + "type": "object", + "description": "Metric details" + } + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } - } }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "getShards", - "tags": [ - "Wazuh-Indexer" - ] + "operationId": "getGraylogMetrics", + "tags": ["Graylog"] } - }, - "/shuffle/workflows": { + }, + "/graylog/indices": { "get": { - "summary": "Get all workflows", - "description": "Endpoint to get all workflows.", - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "workflows": { - "type": "array", - "items": { - "type": "object", - "description": "Workflow details" - } - } - } - } + "summary": "Get indices from Graylog", + "description": "Endpoint to get indices from Graylog.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "indices": { + "type": "array", + "items": { + "type": "object", + "description": "Index details" + } + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } - } }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "getAllWorkflows", - "tags": [ - "Shuffle" - ] + "operationId": "getGraylogIndices", + "tags": ["Graylog"] } - }, - "/shuffle/workflows/executions": { - "get": { - "summary": "Get all workflow executions", - "description": "Endpoint to get all workflow executions.", - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "executions": { - "type": "array", - "items": { - "type": "object", - "description": "Workflow execution details" - } - } - } - } + }, + "/graylog/indices/{index_name}/delete": { + "delete": { + "tags": ["Graylog"], + "summary": "Deletes a Graylog index", + "description": "Endpoint to delete a Graylog index.", + "parameters": [ + { + "name": "index_name", + "in": "path", + "description": "The name of the index to be deleted.", + "required": true, + "type": "string" } - } - }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } + ], + "responses": { + "200": { + "description": "Successful operation", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "success": { + "type": "boolean" + } + } + } + }, + "400": { + "description": "Bad request", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "success": { + "type": "boolean" + } + } + } + }, + "404": { + "description": "Index not found", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "success": { + "type": "boolean" + } + } + } } - } } - }, - "operationId": "getAllWorkflowExecutions", - "tags": [ - "Shuffle" - ] } - }, - "/shuffle/workflows/executions/{workflow_id}": { + }, + "/graylog/inputs": { "get": { - "summary": "Get workflow executions by workflow id", - "description": "Endpoint to get workflow executions by workflow id.", - "parameters": [ - { - "name": "workflow_id", - "in": "path", - "description": "The workflow id", - "required": true, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "executions": { - "type": "array", - "items": { - "type": "object", - "description": "Workflow execution details" - } - } - } - } + "summary": "Get inputs from Graylog", + "description": "Endpoint to get inputs from Graylog.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "inputs": { + "type": "array", + "items": { + "type": "object", + "description": "Input details" + } + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } - } }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "getWorkflowExecutionsByWorkflowId", - "tags": [ - "Shuffle" - ] + "operationId": "getGraylogInputs", + "tags": ["Graylog"] } - }, - "/velociraptor/artifacts": { + }, + "/alerts": { "get": { - "summary": "Get all artifacts", - "description": "Endpoint to get all artifacts.", - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "artifacts": { - "type": "array", - "items": { - "type": "object", - "description": "Artifact details" - } - } - } - } + "summary": "Get alerts", + "description": "Endpoint to get alerts.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "alerts": { + "type": "array", + "items": { + "type": "object", + "description": "Alert details" + } + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } - } }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "getAllArtifacts", - "tags": [ - "Velociraptor" - ] + "operationId": "getAlerts", + "tags": ["Wazuh-Indexer"] } - }, - "/velociraptor/artifacts/linux": { + }, + "/wazuh_indexer/allocation": { "get": { - "summary": "Get all linux artifacts", - "description": "Endpoint to get all linux artifacts.", - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "artifacts": { - "type": "array", - "items": { - "type": "object", - "description": "Artifact details" - } - } - } - } + "summary": "Get node allocation of the Wazuh-Indexer nodes", + "description": "Endpoint to get node allocation.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "indices": { + "type": "array", + "items": { + "type": "object", + "description": "Node details" + } + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } - } }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "getAllLinuxArtifacts", - "tags": [ - "Velociraptor" - ] + "operationId": "getNodeAllocation", + "tags": ["Wazuh-Indexer"] } - }, - "/velociraptor/artifacts/windows": { + }, + "/wazuh_indexer/indices": { "get": { - "summary": "Get all windows artifacts", - "description": "Endpoint to get all windows artifacts.", - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "artifacts": { - "type": "array", - "items": { - "type": "object", - "description": "Artifact details" - } - } - } - } + "summary": "Get indices from Wazuh-Indexer", + "description": "Endpoint to get indices from Wazuh-Indexer.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "indices": { + "type": "array", + "items": { + "type": "object", + "description": "Index details" + } + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } - } }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "getAllWindowsArtifacts", - "tags": [ - "Velociraptor" - ] + "operationId": "getWazuhIndices", + "tags": ["Wazuh-Indexer"] } - }, - "/velociraptor/artifacts/mac": { + }, + "/wazuh_indexer/health": { "get": { - "summary": "Get all mac artifacts", - "description": "Endpoint to get all mac artifacts.", - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "artifacts": { - "type": "array", - "items": { - "type": "object", - "description": "Artifact details" - } - } - } - } - } - } - }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "getAllMacArtifacts", - "tags": [ - "Velociraptor" - ] - } - }, - "/velociraptor/artifacts/collection": { - "post": { - "summary": "Create a new artifact collection", - "description": "Endpoint to create a new artifact collection.", - "requestBody": { - "description": "Artifact collection details", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "artifact_name": { - "type": "string", - "description": "The name of the artifact collection." - }, - "client_name": { - "type": "string", - "description": "The name of the client to collect the artifact for." - } - } - } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "collection": { - "type": "object", - "description": "Artifact collection details" - } + "summary": "Get health of the Wazuh-Indexer nodes", + "description": "Endpoint to get health.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "cluster_name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "number_of_nodes": { + "type": "integer" + }, + "number_of_data_nodes": { + "type": "integer" + }, + "active_primary_shards": { + "type": "integer" + }, + "active_shards": { + "type": "integer" + }, + "relocating_shards": { + "type": "integer" + }, + "initializing_shards": { + "type": "integer" + }, + "unassigned_shards": { + "type": "integer" + }, + "delayed_unassigned_shards": { + "type": "integer" + }, + "number_of_pending_tasks": { + "type": "integer" + }, + "number_of_in_flight_fetch": { + "type": "integer" + }, + "task_max_waiting_in_queue_millis": { + "type": "integer" + }, + "active_shards_percent_as_number": { + "type": "integer" + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } - } } - } }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "createArtifactCollection", - "tags": [ - "Velociraptor" - ] + "operationId": "getHealth", + "tags": ["Wazuh-Indexer"] } - }, - "/dfir_iris/cases": { + }, + "/wazuh_indexer/shards": { "get": { - "summary": "Get all cases", - "description": "Endpoint to get all cases.", - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "cases": { - "type": "array", - "items": { - "type": "object", - "description": "Case details" - } - } - } - } + "summary": "Get shards from Wazuh-Indexer", + "description": "Endpoint to get shards from Wazuh-Indexer.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "shards": { + "type": "array", + "items": { + "type": "object", + "description": "Shard details" + } + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } - } }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "getAllCases", - "tags": [ - "DFIR Iris" - ] + "operationId": "getShards", + "tags": ["Wazuh-Indexer"] } - }, - "/dfir_iris/cases/{case_id}": { + }, + "/shuffle/workflows": { "get": { - "summary": "Get a case", - "description": "Endpoint to get a case.", - "parameters": [ - { - "name": "case_id", - "in": "path", - "description": "The ID of the case.", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "case": { - "type": "object", - "description": "Case details" - } + "summary": "Get all workflows", + "description": "Endpoint to get all workflows.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "workflows": { + "type": "array", + "items": { + "type": "object", + "description": "Workflow details" + } + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } } - } - } - } - }, - "404": { - "description": "Case not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } } - } }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "getCase", - "tags": [ - "DFIR Iris" - ] + "operationId": "getAllWorkflows", + "tags": ["Shuffle"] } - }, - "/dfir_iris/cases/{case_id}/notes": { + }, + "/shuffle/workflows/executions": { "get": { - "summary": "Get all notes for a case", - "description": "Endpoint to get all notes for a case.", - "parameters": [ - { - "name": "case_id", - "in": "path", - "description": "The ID of the case.", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "notes": { - "type": "array", - "items": { - "type": "object", - "description": "Note details" - } - } - } - } - } - } - }, - "404": { - "description": "Case not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } + "summary": "Get all workflow executions", + "description": "Endpoint to get all workflow executions.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "executions": { + "type": "array", + "items": { + "type": "object", + "description": "Workflow execution details" + } + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } - } }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "getAllNotesForCase", - "tags": [ - "DFIR Iris" - ] + "operationId": "getAllWorkflowExecutions", + "tags": ["Shuffle"] } - }, - "/dfir_iris/cases/{case_id}/note": { - "post": { - "summary": "Create a note for a case", - "description": "Endpoint to create a note for a case.", - "parameters": [ - { - "name": "case_id", - "in": "path", - "description": "The ID of the case.", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "requestBody": { - "description": "Note details", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "note_title": { - "type": "string", - "description": "The title of the note" - }, - "note_content": { - "type": "string", - "description": "The content of the note" - } - }, - "description": "Note details" + }, + "/shuffle/workflows/executions/{workflow_id}": { + "get": { + "summary": "Get workflow executions by workflow id", + "description": "Endpoint to get workflow executions by workflow id.", + "parameters": [ + { + "name": "workflow_id", + "in": "path", + "description": "The workflow id", + "required": true, + "schema": { + "type": "string" + } } - } - } - }, - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "note": { - "type": "object", - "properties": { - "title": { - "type": "string", - "description": "The title of the note" - }, - "content": { - "type": "string", - "description": "The content of the note" - } - }, - "description": "Note details" - } - } - } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "executions": { + "type": "array", + "items": { + "type": "object", + "description": "Workflow execution details" + } + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } - } }, - "404": { - "description": "Case not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } + "operationId": "getWorkflowExecutionsByWorkflowId", + "tags": ["Shuffle"] + } + }, + "/velociraptor/artifacts": { + "get": { + "summary": "Get all artifacts", + "description": "Endpoint to get all artifacts.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "artifacts": { + "type": "array", + "items": { + "type": "object", + "description": "Artifact details" + } + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } - } }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "createNoteForCase", - "tags": [ - "DFIR Iris" - ] + "operationId": "getAllArtifacts", + "tags": ["Velociraptor"] } - }, - "/dfir_iris/cases/{case_id}/assets": { + }, + "/velociraptor/artifacts/linux": { "get": { - "summary": "Get all assets for a case", - "description": "Endpoint to get all assets for a case.", - "parameters": [ - { - "name": "case_id", - "in": "path", - "description": "The ID of the case.", - "required": true, - "schema": { - "type": "integer" - } - } - ], - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "assets": { - "type": "array", - "items": { - "type": "object", - "description": "Asset details" - } - } - } - } + "summary": "Get all linux artifacts", + "description": "Endpoint to get all linux artifacts.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "artifacts": { + "type": "array", + "items": { + "type": "object", + "description": "Artifact details" + } + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } - } }, - "404": { - "description": "Case not found", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } + "operationId": "getAllLinuxArtifacts", + "tags": ["Velociraptor"] + } + }, + "/velociraptor/artifacts/windows": { + "get": { + "summary": "Get all windows artifacts", + "description": "Endpoint to get all windows artifacts.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "artifacts": { + "type": "array", + "items": { + "type": "object", + "description": "Artifact details" + } + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } - } }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "getAllAssetsForCase", - "tags": [ - "DFIR Iris" - ] + "operationId": "getAllWindowsArtifacts", + "tags": ["Velociraptor"] } - }, - "/dfir_iris/alerts": { + }, + "/velociraptor/artifacts/mac": { "get": { - "summary": "Get all alerts", - "description": "Endpoint to get all alerts.", - "parameters": [ - { - "name": "limit", - "in": "query", - "description": "The maximum number of alerts to return.", - "required": false, - "schema": { - "type": "integer" - } - }, - { - "name": "offset", - "in": "query", - "description": "The offset to start returning alerts from.", - "required": false, - "schema": { - "type": "integer" - } - }, - { - "name": "sort", - "in": "query", - "description": "The field to sort alerts by.", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "order", - "in": "query", - "description": "The order to sort alerts by.", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "filter", - "in": "query", - "description": "The filter to apply to the alerts.", - "required": false, - "schema": { - "type": "string" - } - } - ], - "responses": { - "200": { - "description": "Successful operation", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "alerts": { - "type": "array", - "items": { - "type": "object", - "description": "Alert details" - } - } - } - } + "summary": "Get all mac artifacts", + "description": "Endpoint to get all mac artifacts.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "artifacts": { + "type": "array", + "items": { + "type": "object", + "description": "Artifact details" + } + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } } - } }, - "default": { - "description": "Unexpected error", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/Error" - } - } - } - } - }, - "operationId": "getAllAlerts", - "tags": [ - "DFIR Iris" - ] + "operationId": "getAllMacArtifacts", + "tags": ["Velociraptor"] } - } + }, + "/velociraptor/artifacts/collection": { + "post": { + "summary": "Create a new artifact collection", + "description": "Endpoint to create a new artifact collection.", + "requestBody": { + "description": "Artifact collection details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "artifact_name": { + "type": "string", + "description": "The name of the artifact collection." + }, + "client_name": { + "type": "string", + "description": "The name of the client to collect the artifact for." + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "collection": { + "type": "object", + "description": "Artifact collection details" + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "operationId": "createArtifactCollection", + "tags": ["Velociraptor"] + } + }, + "/dfir_iris/cases": { + "get": { + "summary": "Get all cases", + "description": "Endpoint to get all cases.", + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "cases": { + "type": "array", + "items": { + "type": "object", + "description": "Case details" + } + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "operationId": "getAllCases", + "tags": ["DFIR Iris"] + } + }, + "/dfir_iris/cases/{case_id}": { + "get": { + "summary": "Get a case", + "description": "Endpoint to get a case.", + "parameters": [ + { + "name": "case_id", + "in": "path", + "description": "The ID of the case.", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "case": { + "type": "object", + "description": "Case details" + } + } + } + } + } + }, + "404": { + "description": "Case not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "operationId": "getCase", + "tags": ["DFIR Iris"] + } + }, + "/dfir_iris/cases/{case_id}/notes": { + "get": { + "summary": "Get all notes for a case", + "description": "Endpoint to get all notes for a case.", + "parameters": [ + { + "name": "case_id", + "in": "path", + "description": "The ID of the case.", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "notes": { + "type": "array", + "items": { + "type": "object", + "description": "Note details" + } + } + } + } + } + } + }, + "404": { + "description": "Case not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "operationId": "getAllNotesForCase", + "tags": ["DFIR Iris"] + } + }, + "/dfir_iris/cases/{case_id}/note": { + "post": { + "summary": "Create a note for a case", + "description": "Endpoint to create a note for a case.", + "parameters": [ + { + "name": "case_id", + "in": "path", + "description": "The ID of the case.", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "requestBody": { + "description": "Note details", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "note_title": { + "type": "string", + "description": "The title of the note" + }, + "note_content": { + "type": "string", + "description": "The content of the note" + } + }, + "description": "Note details" + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "note": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "The title of the note" + }, + "content": { + "type": "string", + "description": "The content of the note" + } + }, + "description": "Note details" + } + } + } + } + } + }, + "404": { + "description": "Case not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "operationId": "createNoteForCase", + "tags": ["DFIR Iris"] + } + }, + "/dfir_iris/cases/{case_id}/assets": { + "get": { + "summary": "Get all assets for a case", + "description": "Endpoint to get all assets for a case.", + "parameters": [ + { + "name": "case_id", + "in": "path", + "description": "The ID of the case.", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "assets": { + "type": "array", + "items": { + "type": "object", + "description": "Asset details" + } + } + } + } + } + } + }, + "404": { + "description": "Case not found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "operationId": "getAllAssetsForCase", + "tags": ["DFIR Iris"] + } + }, + "/dfir_iris/alerts": { + "get": { + "summary": "Get all alerts", + "description": "Endpoint to get all alerts.", + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "The maximum number of alerts to return.", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "offset", + "in": "query", + "description": "The offset to start returning alerts from.", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "sort", + "in": "query", + "description": "The field to sort alerts by.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "order", + "in": "query", + "description": "The order to sort alerts by.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "filter", + "in": "query", + "description": "The filter to apply to the alerts.", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "alerts": { + "type": "array", + "items": { + "type": "object", + "description": "Alert details" + } + } + } + } + } + } + }, + "default": { + "description": "Unexpected error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Error" + } + } + } + } + }, + "operationId": "getAllAlerts", + "tags": ["DFIR Iris"] + } + } }, "components": { "schemas": { @@ -1931,15 +1858,8 @@ "description": "The operating system of the agent." } }, - "required": [ - "agent_id", - "hostname", - "id", - "ip_address", - "last_seen", - "os" - ] + "required": ["agent_id", "hostname", "id", "ip_address", "last_seen", "os"] } } - } + } } diff --git a/backend/copilot.py b/backend/copilot.py index 79392e22..61ff3827 100644 --- a/backend/copilot.py +++ b/backend/copilot.py @@ -1,9 +1,8 @@ -from flask import Flask -from app import db -from app import app - +# from flask import Flask from loguru import logger +from app import app +from app import db logger.add( "debug.log", diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 00000000..e09e597e --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,108 @@ +aiohttp==3.8.4 +aiosignal==1.3.1 +alembic==1.11.1 +antlr4-python3-runtime==4.9.3 +appdirs==1.4.4 +arrow==1.2.3 +async-timeout==4.0.2 +attrs==21.4.0 +blinker==1.6.2 +blueprint==3.4.2 +cattrs==23.1.2 +certifi==2023.5.7 +cffi==1.15.1 +charset-normalizer==3.2.0 +click==8.1.4 +colorama==0.4.6 +colour==0.1.5 +cpe==1.2.1 +cryptography==41.0.1 +cybox==2.1.0.21 +deepdiff==6.3.1 +drawsvg==2.2.0 +elasticsearch7==7.10.1 +environs==9.5.0 +et-xmlfile==1.1.0 +Flask==2.3.2 +Flask-Cors==4.0.0 +flask-marshmallow==0.15.0 +Flask-Migrate==4.0.4 +Flask-SQLAlchemy==3.0.5 +flask-swagger-ui==4.11.1 +fqdn==1.5.1 +frozenlist==1.3.3 +greenlet==2.0.2 +grpcio==1.56.0 +grpcio-tools==1.56.0 +idna==3.4 +isoduration==20.11.0 +itsdangerous==2.1.2 +Jinja2==3.1.2 +jsonpointer==2.4 +jsonschema==4.17.3 +loguru==0.7.0 +lxml==4.9.3 +maec==4.1.0.17 +Mako==1.2.4 +Markdown==3.4.3 +markdown-it-py==3.0.0 +MarkupSafe==2.1.3 +marshmallow==3.19.0 +marshmallow-sqlalchemy==0.29.0 +mdurl==0.1.2 +mitreattack-python==2.0.14 +mixbox==1.0.5 +multidict==6.0.4 +netaddr==0.8.0 +numpy==1.25.1 +openai==0.27.8 +openpyxl==3.1.2 +ordered-set==4.1.0 +packaging==23.1 +pandas==2.0.3 +pika==1.3.2 +Pillow==10.0.0 +platformdirs==3.8.1 +pluralizer==1.2.0 +pooch==1.7.0 +protobuf==4.23.4 +psycopg2-binary==2.9.6 +pycountry==22.3.5 +pycparser==2.21 +Pygments==2.15.1 +pyrsistent==0.19.3 +python-dateutil==2.8.2 +python-dotenv==1.0.0 +pytz==2023.3 +pyvelociraptor==0.1.8 +PyYAML==6.0 +requests==2.31.0 +requests-cache==1.1.0 +rfc3339-validator==0.1.4 +rfc3986-validator==0.1.1 +rich==13.4.2 +simplejson==3.19.1 +six==1.16.0 +SQLAlchemy==2.0.18 +stix==1.2.0.11 +stix2==3.0.1 +stix2-elevator==4.1.7 +stix2-patterns==2.0.0 +stix2-validator==3.1.3 +stixmarx==1.0.8 +tabulate==0.9.0 +taxii2-client==2.3.0 +tqdm==4.65.0 +typer==0.9.0 +typing_extensions==4.7.1 +tzdata==2023.3 +uri-template==1.3.0 +url-normalize==1.4.3 +urllib3==1.26.16 +weakrefmethod==1.0.3 +webcolors==1.13 +Werkzeug==2.3.6 +win32-setctime==1.1.0 +XlsxWriter==3.1.2 +xmltodict==0.13.0 +yarl==1.9.2 diff --git a/backend/settings.py b/backend/settings.py index c34bf1f8..5bb14af3 100644 --- a/backend/settings.py +++ b/backend/settings.py @@ -20,6 +20,7 @@ SECRET_KEY = env.str("SECRET_KEY", "not-a-secret") SQLALCHEMY_DATABASE_URI = env.str("SQLALCHEMY_DATABASE_URI", f"sqlite:///{db_path}") SQLALCHEMY_TRACK_MODIFICATIONS = env.bool( - "SQLALCHEMY_TRACK_MODIFICATIONS", default=False + "SQLALCHEMY_TRACK_MODIFICATIONS", + default=False, ) UPLOAD_FOLDER = env.str("UPLOAD_FOLDER", str(Path.home() / "Desktop/copilot_uploads"))